var schedules = new List<Item>{
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<Item>>{
new List<Item> {
new Item { Id=3, Name = "X" },
new Item { Id=4, Name = "X" },
},
new List<Item> {
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可以吗?
答案 0 :(得分:6)
您在这里寻找的是GroupWhile<T>
方法。
向用户L.B表示解决方案。去给他的原始答案UpDoot https://stackoverflow.com/a/20469961/30155
var schedules = new List<Item>{
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<T>
,如此。
public static class Extensions
{
public static IEnumerable<IEnumerable<T>> GroupWhile<T>(this IEnumerable<T> seq, Func<T, T, bool> condition)
{
T prev = seq.First();
List<T> list = new List<T>() { prev };
foreach (T item in seq.Skip(1))
{
if (condition(prev, item) == false)
{
yield return list;
list = new List<T>();
}
list.Add(item);
prev = item;
}
yield return list;
}
}
答案 1 :(得分:1)
您可以通过维护到目前为止找到的项目数来实现。这有助于您找到连续的项目,因为count(name) - index
的值对它们来说是不变的:
IDictionary<string,int> count = new Dictionary<string,int>();
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
表达式看起来有点像#34; magic&#34;,请考虑删除最终的Select
和Where
子句,以及枚举组密钥。
答案 2 :(得分:0)
@dasblinkenlight提供了一个只使用LINQ的答案。使用纯粹存在的LINQ方法的任何答案可能都很丑,可能表现不佳,并且可能不是高度可重用的。 (这不是对这个答案的批评。这是对LINQ的批评。)
GroupBy
函数的功能,例如自定义比较器(当您需要执行诸如对键的不区分大小写的比较之类的事情时)。下面的Partition
方法看起来和感觉类似于GroupBy
函数,但符合连续项的要求。
您可以通过执行以下操作来使用此方法来实现目标。请注意,如果您没有提交要求,它看起来与您编写此文件的方式完全相同,但它使用的是Partition
而不是GroupBy
。
var partitionsWithMoreThan1 = schedules.Partition(o => o.Name)
.Where(p => p.Count() > 1)
.Select(p => p.ToList())
.ToList();
以下是方法:
static class EnumerableExtensions
{
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TSource">The type of the elements of <paramref name="source"/>.</typeparam>
/// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
/// <param name="source">An <see cref="IEnumerable{T}"/> whose elements to partition.</param>
/// <param name="keySelector">A function to extract the key for each element.</param>
/// <param name="comparer">An <see cref="IEqualityComparer{T}"/> to compare keys.</param>
/// <returns>
/// An <b>IEnumerable{IGrouping{TKey, TSource}}</b> in C#
/// or <b>IEnumerable(Of IGrouping(Of TKey, TSource))</b> in Visual Basic
/// where each <see cref="IGrouping{TKey,TElement}"/> object contains a collection of objects and a key.
/// </returns>
public static IEnumerable<IGrouping<TKey, TSource>> Partition<TKey, TSource>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer = null)
{
if (comparer == null)
comparer = EqualityComparer<TKey>.Default;
using (var enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{
var item = enumerator.Current;
var partitionKey = keySelector(item);
var itemsInPartition = new List<TSource> {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<TKey, TSource>(lastPartitionKey, itemsInPartition);
itemsInPartition = new List<TSource> {item};
lastPartitionKey = partitionKey;
}
}
yield return new Grouping<TKey, TSource>(lastPartitionKey, itemsInPartition);
}
}
}
// it's a shame there's no ready-made public implementation that will do this
private class Grouping<TKey, TSource> : IGrouping<TKey, TSource>
{
public Grouping(TKey key, List<TSource> items)
{
_items = items;
Key = key;
}
public TKey Key { get; }
public IEnumerator<TSource> GetEnumerator()
{
return _items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _items.GetEnumerator();
}
private readonly List<TSource> _items;
}
}
答案 3 :(得分:-1)
根据评论澄清(问题真的现在还不清楚),我认为这就是所需要的。
它使用一种扩展方法,通过测试基于GroupByRuns
的连续项,将基于GroupByWhile
组的基于ScanPair
组合在一起,Scan
我的APL启发Aggregate
运算符的变体,类似ValueTuple
,但返回中间结果,并使用(Key, Value)
public static IEnumerable<IGrouping<int, TRes>> GroupByRuns<T, TKey, TRes>(this IEnumerable<T> src, Func<T,TKey> keySelector, Func<T,TRes> resultSelector, IEqualityComparer<TKey> cmp = null) {
cmp = cmp ?? EqualityComparer<TKey>.Default;
return src.GroupByWhile((prev, cur) => cmp.Equals(keySelector(prev), keySelector(cur)), resultSelector);
}
public static IEnumerable<IGrouping<int, T>> GroupByRuns<T, TKey>(this IEnumerable<T> src, Func<T,TKey> keySelector) => src.GroupByRuns(keySelector, e => e);
public static IEnumerable<IGrouping<int, T>> GroupByRuns<T>(this IEnumerable<T> src) => src.GroupByRuns(e => e, e => e);
public static IEnumerable<IGrouping<int, TRes>> GroupByWhile<T, TRes>(this IEnumerable<T> src, Func<T,T,bool> testFn, Func<T,TRes> 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<T, TKey>(this IEnumerable<T> 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();
:
Count() > 1
注意:对于@Aominè,在Take(2).Count()
个子组(内部类型)之后,使用Skip(1).Any()
或@MichaelGunter使用GroupBy
优化Grouping
的人很有意思IList
)每个工具Count()
和Grouping.count
方法直接从treemapify
字段获取计数。