如何查找连续相同的值项目作为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
表达式看起来有点像“魔术”,请考虑删除最终的Select
和Where
子句,并枚举组键。
@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
。
现在你只需要GroupByRuns
的Name
并选择具有多个成员的运行,然后将每个运行转换为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()
)每个实现IList
和Count()
方法只是直接从Grouping.count
字段获取计数。