如何查找连续相同的值项目作为Linq组

var schedules = new List{ new Item { Id=1, Name = "S" }, new Item { Id=2, Name = "P" }, new Item { Id=3, Name = "X" }, new Item { Id=4, Name = "X" }, new Item { Id=5, Name = "P" }, new Item { Id=6, Name = "P" }, new Item { Id=7, Name = "P" }, new Item { Id=8, Name = "S" } }; 

我想在新列表中选择相同的值和相同的顺序,如下所示:

 var groupedAndSelectedList = new List<List>{ new List { new Item { Id=3, Name = "X" }, new Item { Id=4, Name = "X" }, }, new List { new Item { Id=5, Name = "P" }, new Item { Id=6, Name = "P" }, new Item { Id=7, Name = "P" }, } } 

如果项目是单项,如new Item { Id=3, Name = "A" }我不需要得到它。

分组选择列表中的所有X或P元素。 但是我希望如果物品位于另一个物品之后或之前。

这可能使用linq吗?

你在这里寻找的是GroupWhile方法。

归功于用户LB的解决方案。 去给他的原始答案UpDoot https://stackoverflow.com/a/20469961/30155

  var schedules = new List{ new Item { Id=1, Name = "S" }, new Item { Id=2, Name = "P" }, new Item { Id=3, Name = "X" }, new Item { Id=4, Name = "X" }, new Item { Id=5, Name = "P" }, new Item { Id=6, Name = "P" }, new Item { Id=7, Name = "P" }, new Item { Id=8, Name = "S" } }; var results = schedules .GroupWhile((preceding, next) => preceding.Name == next.Name) //Group items, while the next is equal to the preceding one .Where(s => s.Count() > 1) //Only include results where the generated sublist have more than 1 element. .ToList(); foreach (var sublist in results) { foreach (Item i in sublist) { Console.WriteLine($"{i.Name} - {i.Id}"); } Console.WriteLine(""); } Console.ReadLine(); 

您可以将实现作为扩展方法添加到所有IEnumerable

 public static class Extensions { public static IEnumerable> GroupWhile(this IEnumerable seq, Func condition) { T prev = seq.First(); List list = new List() { prev }; foreach (T item in seq.Skip(1)) { if (condition(prev, item) == false) { yield return list; list = new List(); } list.Add(item); prev = item; } yield return list; } } 

您可以通过维护到目前为止找到的项目数来实现。 这有助于您找到连续的项目,因为count(name) - index值对于它们是不变的:

 IDictionary count = new Dictionary(); var groups = schedules .Select((s, i) => new { Item = s , Index = i }) .GroupBy(p => { var name = p.Item.Name; int current; if (!count.TryGetValue(name, out current)) { current = 0; count.Add(name, current); } count[name] = current + 1; return new { Name = name, Order = current - p.Index }; }) .Select(g => g.ToList()) .Where(g => g.Count > 1) .ToList(); 

这会为您的示例生成所需的输出:

 { Item = Id=3 Name=X, Index = 2 } { Item = Id=4 Name=X, Index = 3 } ----- { Item = Id=5 Name=P, Index = 4 } { Item = Id=6 Name=P, Index = 5 } { Item = Id=7 Name=P, Index = 6 } 

演示。

注意:如果Order = current - p.Index表达式看起来有点像“魔术”,请考虑删除最终的SelectWhere子句,并枚举组键。

@dasblinkenlight提供了一个只使用LINQ的答案。 使用纯粹存在的LINQ方法的任何答案都可能很难看,可能表现不佳,并且可能不是高度可重用的。 (这不是对这个答案的批评。这是对LINQ的批评。)

@ eoin-campbell提供了一个使用自定义LINQ方法的答案。 但是,我认为可以改进以更接近地匹配现有LINQ GroupBy函数的function,例如自定义比较器(当您需要执行诸如对键的不区分大小写的比较之类的事情)。 下面的Partition方法看起来和感觉像GroupBy函数,但满足连续项的要求。

您可以通过执行以下操作来使用此方法来实现目标。 请注意,如果您没有连续性要求,它看起来与您编写此文件的方式完全相同,但它使用的是Partition而不是GroupBy

 var partitionsWithMoreThan1 = schedules.Partition(o => o.Name) .Where(p => p.Count() > 1) .Select(p => p.ToList()) .ToList(); 

这是方法:

 static class EnumerableExtensions { ///  /// Partitions the elements of a sequence into smaller collections according to a specified /// key selector function, optionally comparing the keys by using a specified comparer. /// Unlike GroupBy, this method does not produce a single collection for each key value. /// Instead, this method produces a collection for each consecutive set of matching keys. ///  /// The type of the elements of . /// The type of the key returned by . /// An  whose elements to partition. /// A function to extract the key for each element. /// An  to compare keys. ///  /// An IEnumerable{IGrouping{TKey, TSource}} in C# /// or IEnumerable(Of IGrouping(Of TKey, TSource)) in Visual Basic /// where each  object contains a collection of objects and a key. ///  public static IEnumerable> Partition(this IEnumerable source, Func keySelector, IEqualityComparer comparer = null) { if (comparer == null) comparer = EqualityComparer.Default; using (var enumerator = source.GetEnumerator()) { if (enumerator.MoveNext()) { var item = enumerator.Current; var partitionKey = keySelector(item); var itemsInPartition = new List {item}; var lastPartitionKey = partitionKey; while (enumerator.MoveNext()) { item = enumerator.Current; partitionKey = keySelector(item); if (comparer.Equals(partitionKey, lastPartitionKey)) { itemsInPartition.Add(item); } else { yield return new Grouping(lastPartitionKey, itemsInPartition); itemsInPartition = new List {item}; lastPartitionKey = partitionKey; } } yield return new Grouping(lastPartitionKey, itemsInPartition); } } } // it's a shame there's no ready-made public implementation that will do this private class Grouping : IGrouping { public Grouping(TKey key, List items) { _items = items; Key = key; } public TKey Key { get; } public IEnumerator GetEnumerator() { return _items.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _items.GetEnumerator(); } private readonly List _items; } } 

根据评论澄清(现在的问题真的不清楚),我认为这是需要的。

它使用一种扩展方法,将GroupByRuns的密钥组合在一起, GroupByRuns ,它基于GroupByWhile组,通过测试连续项目,这是基于ScanPair ,这是我的APL灵感Scan运算符的变体,类似于Aggregate ,但返回中间结果,并使用ValueTuple (Key, Value)将键与沿途的值配对。

 public static IEnumerable> GroupByRuns(this IEnumerable src, Func keySelector, Func resultSelector, IEqualityComparer cmp = null) { cmp = cmp ?? EqualityComparer.Default; return src.GroupByWhile((prev, cur) => cmp.Equals(keySelector(prev), keySelector(cur)), resultSelector); } public static IEnumerable> GroupByRuns(this IEnumerable src, Func keySelector) => src.GroupByRuns(keySelector, e => e); public static IEnumerable> GroupByRuns(this IEnumerable src) => src.GroupByRuns(e => e, e => e); public static IEnumerable> GroupByWhile(this IEnumerable src, Func testFn, Func resultFn) => src.ScanPair(1, (kvp, cur) => testFn(kvp.Value, cur) ? kvp.Key : kvp.Key + 1) .GroupBy(kvp => kvp.Key, kvp => resultFn(kvp.Value)); public static IEnumerable<(TKey Key, T Value)> ScanPair(this IEnumerable src, TKey seedKey, Func<(TKey Key, T Value),T,TKey> combineFn) { using (var srce = src.GetEnumerator()) { if (srce.MoveNext()) { var prevkv = (seedKey, srce.Current); while (srce.MoveNext()) { yield return prevkv; prevkv = (combineFn(prevkv, srce.Current), srce.Current); } yield return prevkv; } } } 

我意识到这是很多扩展代码,但是通过使用一般的ScanPair基础,您可以构建其他专门的分组方法,例如GroupBySequential

现在你只需要GroupByRunsName并选择具有多个成员的运行,然后将每个运行转换为List ,将整个事件转换为List

 var ans = schedules.GroupByRuns(s => s.Name) .Where(sg => sg.Count() > 1) .Select(sg => sg.ToList()) .ToList(); 

注意:对于@Aominè,他在使用Take(2).Count()或@MichaelGunter使用Skip(1).Any() .Any Skip(1).Any() ,在GroupBy子组(内部类型Grouping Skip(1).Any()之后对使用优化Count() > 1进行了有趣的尝试Skip(1).Any() )每个实现IListCount()方法只是直接从Grouping.count字段获取计数。