C# 按模式重新排列列表

C# rearrange List by pattern

我有 List<TimeAndCode> 个对象。每个对象都包含一个 Code 和一个 TimeSpan 值。

public struct TimeAndCode {
    public TimeSpan Time { get; set; }
    public string Code { get; set; }
}

出现的代码被预定义为"CO""GO""BT"

Time属性命中相同的值时,列表的顺序需要一定的格式。例如(JSON 可读性表示法)

[
    {"Time" : "08:00:00", "Code" : "CO" },
    {"Time" : "09:00:00", "Code" : "GO" },
    {"Time" : "09:30:00", "Code" : "CO" },
    {"Time" : "09:30:00", "Code" : "GO" },
    {"Time" : "09:30:00", "Code" : "CO" },
    {"Time" : "09:30:00", "Code" : "GO" },
    {"Time" : "09:30:00", "Code" : "BT" },
    {"Time" : "12:30:00", "Code" : "CO" },
    {"Time" : "12:30:00", "Code" : "GO" },
    {"Time" : "13:30:00", "Code" : "CO" },
    {"Time" : "13:30:00", "Code" : "GO" },
    {"Time" : "13:30:00", "Code" : "BT" },
    {"Time" : "13:30:00", "Code" : "CO" },
    {"Time" : "13:30:00", "Code" : "GO" }
]

因此模式是 CO -> GOGO -> BTCO -> GO -> BT -> CO -> GO

可能有一个很好的旧 if else 解决方案,但我正在寻找一个不错且易于使用的 LINQ 解决方案。 (例如 list.OrderBy( x => x.Time ).RearrangeBy( x => x.Code, pattern );

编辑

只有当代码处于同一时间段时,才能进行自定义排序。

最简单的模式是:

CO -> GO -> CO -> GO -> CO -> GO 

但也有可能存在BT:

CO -> GO -> BT -> CO -> GO -> BT -> CO -> GO -> BT -> CO

但也有可能只有一个BT:

CO -> GO -> BT -> GO -> GO -> CO -> GO -> CO -> GO
CO -> GO -> CO -> GO -> BT -> CO -> GO -> CO -> GO
CO -> GO -> CO -> GO -> CO -> GO -> BT -> CO -> GO
CO -> GO -> BT -> CO -> GO -> CO -> GO -> BT -> CO -> GO

因此主要模式是 CO -> GO -> BT

编辑 2 已解决

我创建了一个扩展方法。可能这段代码对某人有用。 (改进空间)

public static IEnumerable<TSource> RearrangeByPattern<TSource, TKey>(this IEnumerable<TSource> list, Func<TSource, TKey> keySelector, IEnumerable<TKey> pattern)
{
    var groups = list.GroupBy(keySelector).OrderBy(x => pattern.IndexOf(x.Key));
    var maxOccurences = groups.Select(x => x.Count()).Max();

    var result = new List<TSource>();
    for (var i = 0; i < maxOccurences; i++)
    {
        foreach (var group in groups)
        {
            if (group.Count() > i)
            {
                result.Add(group.ElementAt(i));
            }
        }
    }

    return result;
}

现在我可以这样使用它了:

var list = new List<TimeAndCode>();
... // add values
var ordered = list.OrderBy(x => x.Date).RearrangeByPattern( x => x.Code, new string[] { "CO", "GO", "BT" });

这是一个建议,其中 TimeAndCode.Codeenum 而不是 string

它将始终以重复的 COGOBT 模式对具有相同时间跨度的条目进行排序;这意味着如果例如具有相同时间跨度的五个条目具有以下 Code 选择:2 x CO1 x BT2 x GO,它将始终将它们排序为 COGO , BT, CO, GO(相对于 CO, GO, CO, GO, BT).

我通过基于时间跨度、索引(在嵌套组内生成)和每个条目的数字 Code 值生成 OrderBy 属性 来实现此目的。

使用以下类型:

public struct TimeAndCode
{
    public TimeSpan Time { get; set; }
    public Code Code { get; set; }
}

public enum Code
{
    Undefined,
    CO,
    GO,
    BT
}

我们可以写出如下表达式:

List<TimeAndCode> result = list
    .GroupBy(entry => entry.Code)
    .SelectMany(gr => gr
        .GroupBy(entry => entry.Time)
        .SelectMany(gr => gr.Select((entry, index) => (
            OrderBy: entry.Time.ToString() + index + (int)entry.Code,
            TimeAndCode: entry))))
    .OrderBy(entry => entry.OrderBy)
    .Select(entry => entry.TimeAndCode)
    .ToList();

其中 listList<TimeAndCode>


使用示例输入如下:

List<TimeAndCode> list = new List<TimeAndCode> 
{
    new TimeAndCode { Time = new TimeSpan(09, 00, 00), Code = Code.GO },
    new TimeAndCode { Time = new TimeSpan(13, 30, 00), Code = Code.BT },
    new TimeAndCode { Time = new TimeSpan(09, 30, 00), Code = Code.GO },
    new TimeAndCode { Time = new TimeSpan(09, 30, 00), Code = Code.GO },
    new TimeAndCode { Time = new TimeSpan(13, 30, 00), Code = Code.GO },
    new TimeAndCode { Time = new TimeSpan(08, 00, 00), Code = Code.CO },
    new TimeAndCode { Time = new TimeSpan(09, 30, 00), Code = Code.BT },
    new TimeAndCode { Time = new TimeSpan(12, 30, 00), Code = Code.CO },
    new TimeAndCode { Time = new TimeSpan(09, 30, 00), Code = Code.CO },
    new TimeAndCode { Time = new TimeSpan(12, 30, 00), Code = Code.GO },
    new TimeAndCode { Time = new TimeSpan(13, 30, 00), Code = Code.CO },
    new TimeAndCode { Time = new TimeSpan(13, 30, 00), Code = Code.CO },
    new TimeAndCode { Time = new TimeSpan(09, 30, 00), Code = Code.CO },
    new TimeAndCode { Time = new TimeSpan(13, 30, 00), Code = Code.GO },
};

,应用Linq表达式后,我们可以打印结果

foreach (var entry in result)
{
    Console.WriteLine("Time: " + entry.Time + " Code: " + entry.Code);
}

并得到以下输出:

Time: 08:00:00 Code: CO
Time: 09:00:00 Code: GO
Time: 09:30:00 Code: CO
Time: 09:30:00 Code: GO
Time: 09:30:00 Code: BT
Time: 09:30:00 Code: CO
Time: 09:30:00 Code: GO
Time: 12:30:00 Code: CO
Time: 12:30:00 Code: GO
Time: 13:30:00 Code: CO
Time: 13:30:00 Code: GO
Time: 13:30:00 Code: BT
Time: 13:30:00 Code: CO
Time: 13:30:00 Code: GO

这是 Astrid 的变体:

var r =  list.GroupBy(tc => tc)
        .SelectMany(g => g.Select((tc, i) => (tc, i)))
        .OrderBy(t => (t.tc.Time, t.i, t.tc.Code))
        .Select(t => t.tc);

使用以下先驱设置:

public record TimeAndCode(TimeSpan Time, Code Code);

public enum Code { CO, GO, BT }

    ...

    var list = new List<TimeAndCode> 
        {
            new (TimeSpan.FromHours(8), Code.CO),
            new (TimeSpan.FromHours(9), Code.GO),
            new (TimeSpan.FromHours(9.5), Code.CO),
            new (TimeSpan.FromHours(9.5), Code.GO),
            new (TimeSpan.FromHours(9.5), Code.GO),
            new (TimeSpan.FromHours(9.5), Code.CO),
            new (TimeSpan.FromHours(9.5), Code.BT),
            new (TimeSpan.FromHours(12.5), Code.CO),
            new (TimeSpan.FromHours(12.5), Code.GO),
            new (TimeSpan.FromHours(13.5), Code.CO),
            new (TimeSpan.FromHours(13.5), Code.BT),
            new (TimeSpan.FromHours(13.5), Code.GO),
            new (TimeSpan.FromHours(13.5), Code.CO),
            new (TimeSpan.FromHours(13.5), Code.GO),
        };


TimeAndCode 是一条记录,这意味着它获得了一些用于排序和比较的有用属性。可以简单分组,因为它自动等于另一个具有相同数据的TimeAndCode

按时间和代码分组得到 list-of-lists;两个 9:30 CO 进入列表。将其传递给 Select((tc,i) 意味着 i 第一个为 0,第二个为 1 等等。我们所做的就是将其提升为 tci 因为我们稍后会需要它们,我们 SelectMany 撤消允许我们计算相同的 GroupBy Time/Code

我们OrderBy另一个元组;元组按顺序按它们的值排序,所以要按 3 个事物 a、b 和 c 排序,我们可以 OrderBy (a, b, c)

的元组

最后剩下要做的就是 select tc 返回 TimeAndCode 的列表,按您想要的排序

像 Astrid 一样,它也没有将 BT 放在 9:30 的末尾 - 它放在中间,就像 13:30 一样..但你没有提供解释为什么9:30的BT在最后...