如何用Linq实现值变化时将项拆分至不同分组
问题描述
我想用Linq对列表项进行分组,要求每当Command类型发生变化时,之前连续的相同Command项归入一个单独分组。
尝试用GroupBy方法,但它会把所有相同Command的项归为一组,完全忽略了顺序。我的代码如下:
var groups = data .GroupBy(step => step.Command) .Select(g => new KeyValuePair<Command, IEnumerable<MyClass>>(g.Key, g.ToList())) .ToList();
数据示例
var data = new List<MyClass> { new MyClass() { Index = 0, Command = Command.M }, new MyClass() { Index = 0, Command = Command.M }, new MyClass() { Index = 0, Command = Command.M }, new MyClass() { Index = 1, Command = Command.M }, new MyClass() { Index = 1, Command = Command.M }, new MyClass() { Index = 1, Command = Command.RC }, new MyClass() { Index = 1, Command = Command.RC }, new MyClass() { Index = 2, Command = Command.RC }, new MyClass() { Index = 2, Command = Command.M }, new MyClass() { Index = 2, Command = Command.M }, new MyClass() { Index = 2, Command = Command.RC }, new MyClass() { Index = 2, Command = Command.M }, new MyClass() { Index = 3, Command = Command.M }, new MyClass() { Index = 4, Command = Command.RC }, };
当前实际输出
Group: Index: 0, Command: M Index: 0, Command: M Index: 0, Command: M Index: 1, Command: M Index: 1, Command: M Index: 2, Command: M Index: 2, Command: M Index: 2, Command: M Index: 3, Command: M Group: Index: 1, Command: RC Index: 1, Command: RC Index: 2, Command: RC Index: 2, Command: RC Index: 4, Command: RC
期望输出
Group: Index: 0, Command: M Index: 0, Command: M Index: 0, Command: M Index: 1, Command: M Index: 1, Command: M Group: Index: 1, Command: RC Index: 1, Command: RC Index: 2, Command: RC Group: Index: 2, Command: M Index: 2, Command: M Group: Index: 2, Command: RC Group: Index: 2, Command: M Index: 3, Command: M Group: Index: 4, Command: RC
相关类定义
public enum Command { M, RC } public class MyClass { public Command Command { get; set; } public int Index { get; set; } }
解决方案
GroupBy是基于键值的全局分组,无法处理连续相同键的场景,需要手动跟踪分组的变化。以下是两种可行实现:
方式一:直接用Aggregate累积分组
通过Aggregate逐步构建分组,每次检查当前项的Command是否与最后一个分组的Command一致,不一致则新建分组:
var groups = data.Aggregate(new List<List<MyClass>>(), (list, item) => { if (list.Count == 0 || list.Last().First().Command != item.Command) { list.Add(new List<MyClass>()); } list.Last().Add(item); return list; }) .Select(g => new KeyValuePair<Command, IEnumerable<MyClass>>(g.First().Command, g)) .ToList();
方式二:自定义扩展方法(复用性更强)
如果需要多次使用连续分组逻辑,可以封装成扩展方法:
public static class EnumerableExtensions { public static IEnumerable<IGrouping<TKey, TElement>> GroupByContiguous<TElement, TKey>( this IEnumerable<TElement> source, Func<TElement, TKey> keySelector, IEqualityComparer<TKey> comparer = null) { comparer ??= EqualityComparer<TKey>.Default; using var enumerator = source.GetEnumerator(); if (!enumerator.MoveNext()) yield break; var currentKey = keySelector(enumerator.Current); var currentGroup = new List<TElement> { enumerator.Current }; while (enumerator.MoveNext()) { var item = enumerator.Current; var key = keySelector(item); if (comparer.Equals(currentKey, key)) { currentGroup.Add(item); } else { yield return new Grouping<TKey, TElement>(currentKey, currentGroup); currentKey = key; currentGroup = new List<TElement> { item }; } } // 返回最后一个分组 yield return new Grouping<TKey, TElement>(currentKey, currentGroup); } // 辅助类实现IGrouping接口 private class Grouping<TKey, TElement> : IGrouping<TKey, TElement> { public TKey Key { get; } private readonly IEnumerable<TElement> _elements; public Grouping(TKey key, IEnumerable<TElement> elements) { Key = key; _elements = elements; } public IEnumerator<TElement> GetEnumerator() => _elements.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } }
使用时直接调用扩展方法:
var groups = data.GroupByContiguous(x => x.Command) .Select(g => new KeyValuePair<Command, IEnumerable<MyClass>>(g.Key, g.ToList())) .ToList();
两种方式都能得到你期望的连续分组结果。
内容的提问来源于stack exchange,提问作者theateist
相关产品推荐
相关产品推荐

