linq 如何使用自定义日期字符串创建排序逻辑

jxct1oxe  于 2022-12-06  发布在  其他
关注(0)|答案(3)|浏览(144)

我想写一个排序逻辑来排序下面的字符串作为一个自定义类的输出:
现在输出:3m_20,2m_20,1m_20,10d_20,5d_20,0d_20,0d_0,5d_0,10d_0,1m_0,2m_0,3m_0
所需输出:0d_0,0d_20,5d_0,5d_20,10d_0,10d_20,1m_0,1m_20,2m_0,2m_20,3m_0,3m_20
我发现很难按上述方式进行复杂的排序。请帮助我如何按要求排序?
我试过在Id的基础上用IComparable对它排序,但它的排序是在第一个字符的基础上。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Rextester
{
    public class Program
    {
        public static void Main(string[] args)
        {
            DateDefinition d1 = new DateDefinition { Horizon = "0d",Days=20};
            DateDefinition d1_0 = new DateDefinition { Horizon = "0d",Days=0};
            DateDefinition d2 = new DateDefinition { Horizon = "5d",Days=20};
            DateDefinition d2_0 = new DateDefinition { Horizon = "5d",Days=0};
            DateDefinition d3 = new DateDefinition { Horizon = "10d",Days=20};
            DateDefinition d3_0 = new DateDefinition { Horizon = "10d",Days=0};
            DateDefinition d4 = new DateDefinition { Horizon = "1m",Days=20};
            DateDefinition d4_0 = new DateDefinition { Horizon = "1m",Days=0};
            DateDefinition d5 = new DateDefinition { Horizon = "2m",Days=20};
            DateDefinition d5_0 = new DateDefinition { Horizon = "2m",Days=0};
            DateDefinition d6 = new DateDefinition { Horizon = "3m",Days=20};
            DateDefinition d6_0 = new DateDefinition { Horizon = "3m",Days=0};
        
            var definitions = new List<DateDefinition> {d6,d5,d4,d3,d2,d1,d1_0,d2_0,d3_0,d4_0,d5_0,d6_0};
        
            definitions.Sort();
        
            foreach(var d in definitions)
            {
                Console.WriteLine(d.Id);
            } 
        }
    }

    public class DateDefinition : IComparable<DateDefinition>
    {
        public string Horizon { get; set; }
        public int Days { get; set; }
        public string Id
        {
            get { return Horizon + "_" + Days.ToString(); }
        }
        
        public int CompareTo(DateDefinition other)
        {
            if (ReferenceEquals(other, this)) return 0;
            if(ReferenceEquals(other,null)) return -1;
            return string.Compare(Id,other.Id, StringComparison.InvariantCultureIgnoreCase);
        }
    }    
}

输出到上面的代码:
0d_0 0d_20 10d_0 10d_20 1m_0 1m_20 2m_0 2m_20 3m_0 3m_20 5d_0 5d_20
需要输出如下:
0d_0 0d_20 5d_0 5d_20 10d_0 10d_20 1m_0 1m_20 2m_0 2m_20 3m_0 3m_20
重要提示:请注意,在上述上下文中,d=天,m=月。

gdrx4gfi

gdrx4gfi1#

CompareTo方法中,您使用的是string.Compare,因此它按字母顺序比较两个示例的计算Id属性。按字母顺序,“10d_0”在“5d_0”之前。
您希望进行某种数值比较,但需要将以月为单位的展望期值转换为以天为单位的展望期值(反之亦然),才能进行有效的比较。
将Horizon属性定义为字符串似乎有点困难,因为它似乎包含一个数字和单位(d表示天数,m表示月份)。您需要解析这些内容并将其转换为数字,或者将Horizon重新定义为数值HorizonValue和字符串HorizonUnits。

1dkrff03

1dkrff032#

您需要CompareTo()来实现您的自定义业务规则。据我所知,您的算法将是:

public int CompareTo(DateDefinition other)
{
    // I'm using the C# 8 range operator. Equivalent to Horizon.Length-1.
    var thisTimeUnit = Horizon[^1]; 
    var otherTimeUnit = other.Horizon[^1];
    
    // See if the time units are unequal. If there are more than 2 time units, you'll
    // want to associate a rank order with each and compare the rank orders.
    if (thisTimeUnit == 'm' && otherTimeUnit == 'd')
        return 1;
    else if (thisTimeUnit == 'd' && otherTimeUnit == 'm')
        return -1;

    // Sort by the numeric value of horizon if they are different
    var thisHorizon = int.Parse(Horizon[0..^1]);
    var otherHorizon = int.Parse(other.Horizon[0..^1]);
    
    if (thisHorizon != otherHorizon)
        return thisHorizon - otherHorizon;
    
    // If we get here, we have to sort by Days because Horizon is the same
    return Days - other.Days;
}
wh6knrhe

wh6knrhe3#

类别DateDefinition必须实作IComparable<DateDefinition>,排序才能如预期般运作。
在下面的示例中,我向Horizon属性添加了一些逻辑。setter将传递的字符串拆分为一个数字和一个单位,并将它们存储在char? _horizonUnit; int? _horizonNumber;字段中;
getter组合这些部分以重新创建原始字符串。

public class DateDefinition : IComparable<DateDefinition>
{
    private char? _horizonUnit;
    private int? _horizonNumber;

    public string Horizon
    {
        get => _horizonNumber.HasValue ? _horizonNumber.ToString() + _horizonUnit : null;
        set {
            if (String.IsNullOrEmpty(value)) {
                _horizonUnit = null;
                _horizonNumber = null;
            } else {
                _horizonUnit = value[^1];
                if (Int32.TryParse(value[..^1], out int i)) {
                    _horizonNumber = i;
                } else {
                    _horizonNumber = null;
                    _horizonUnit = null;
                    throw new ArgumentException("Horizon does not contain a valid number");
                }
            }
        }
    }

    public int Days { get; set; }

    public string Id => $"{Horizon}_{Days}";

    public override string ToString() => Id;

    public int CompareTo(DateDefinition? other)
    {
        if (other == null) {
            return 1;
        } else {
            int comp = (_horizonUnit ?? (char)0).CompareTo(other._horizonUnit ?? (char)0);
            if (comp != 0) {
                return comp;
            }
            comp = (_horizonNumber ?? 0).CompareTo(other._horizonNumber ?? 0);
            if (comp != 0) {
                return comp;
            }
            return Days.CompareTo(other.Days);
        }
    }
}

如果this小于other,则ComparesTo返回-1;如果两者相等,则ComparesTo返回0;如果this大于other,则ComparesTo返回+1。
首先,它比较单位。碰巧'd'小于'm',因此,内置的Char.CompareTo返回所需的订单。如果单位相等,我们比较展望期编号。如果它们也相等,我们比较Days
请注意,我已经重写了ToString,让它返回Id。因此,现在您可以简单地编写Console.WriteLine(d);。这对于调试也更好,因为调试器现在显示DateDefinition对象的Id
还要注意,我使用了C#的新Indexes and Ranges特性。如果您不能使用它们,可以使用String.Subtring代替(这有点麻烦)。

相关问题