在有序集(数组,列表)上使用LINQ,有没有办法选择或以其他方式使用两个连续项?我在想象语法:
list.SelectTwo((x, y) => ...)
其中x
和y
是列表/数组中索引i
和i + 1
的项目。
可能没有办法做到这一点,我接受这种可能性,但我至少想说我试图找到答案。
我知道我可以使用其他东西和LINQ来实现这一点。
提前谢谢。
答案 0 :(得分:10)
Another answer使用LINQ的Skip
和Zip
提供了一个干净整洁的解决方案。
这是绝对正确的,但我想指出它两次枚举来源。根据每个用例的不同,这可能或不重要。如果它对你的情况很重要,那么这是一个功能相同的更长的替代方案,但列举了一次来源:
static class EnumerableUtilities
{
public static IEnumerable<TResult> SelectTwo<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (selector == null) throw new ArgumentNullException(nameof(selector));
return SelectTwoImpl(source, selector);
}
private static IEnumerable<TResult> SelectTwoImpl<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
using (var iterator = source.GetEnumerator())
{
var item2 = default(TSource);
var i = 0;
while (iterator.MoveNext())
{
var item1 = item2;
item2 = iterator.Current;
i++;
if (i >= 2)
{
yield return selector(item1, item2);
}
}
}
}
}
示例:
var seq = new[] {"A", "B", "C", "D"}.SelectTwo((a, b) => a + b);
结果序列包含"AB"
,"BC"
,"CD"
。
答案 1 :(得分:3)
你可以做到
list.Skip(i).Take(2)
这将返回仅包含两个连续项目的IEnumerable<T>
。
答案 2 :(得分:3)
System.Linq.Enumerable.Zip
通过为每个IEnumerable
配对i
个元素,将两个i
组合在一起。所以你需要Zip
你的列表中的移位版本。
作为一个很好的扩展方法:
using System.Collections.Generic;
using System.Linq;
static class ExtMethods
{
public static IEnumerable<TResult> SelectTwo<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
return Enumerable.Zip(source, source.Skip(1), selector);
}
}
示例:
Enumerable.Range(1,5).SelectTwo((a,b) => $"({a},{b})");
结果:
(1,2) (2,3) (3,4) (4,5)
答案 3 :(得分:1)
我认为您可以在有序列表中选择项目和下一项数据,如下所示:
var theList = new List<T>();
theList
.Select((item, index) => new { CurrIndex = index, item.Prop1, item.Prop2, theList[index + 1].Prop1 })
.Where(newItem => {some condition on the item});
但是,所选项目的index
应小于列表大小 - 1。
答案 4 :(得分:1)
如果源序列有一个索引器,即至少是IReadOnlyList<T>
(数组,问题中提到的列表),并且想法是在连续的对上拆分序列(这一点并不十分清楚)问题),然后就可以像这样完成
var pairs = Enumerable.Range(0, list.Count / 2)
.Select(i => Tuple.Create(list[2 * i], list[2 * i + 1]));
答案 5 :(得分:0)
您可以使用允许您使用项目索引的Select
特殊重载,以及GroupBy
方法将列表拆分为组。每组都有两个项目。这是一个扩展方法:
public static class ExtensionMethods
{
public static IEnumerable<TResult> SelectTwo<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
return source.Select((item, index) => new {item, index})
.GroupBy(x => x.index/2)
.Select(g => g.Select(i => i.item).ToArray())
.Select(x => selector(x[0], x[1]));
}
}
你可以像这样使用它:
var list = new[] {1, 2, 3, 4, 5, 6};
var result = list.SelectTwo((x, y) => x + y).ToList();
这将返回{3,7,11}
请注意,上述方法在开始产生结果之前将数据分组到内存中。如果您有大型数据集,您可能希望采用流式处理方法(在枚举来源时生成数据),这是一个示例:
public static class ExtensionMethods
{
public static IEnumerable<TResult> SelectTwo<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
bool first_item_got = false;
TSource first_item = default(TSource);
foreach (var item in source)
{
if (first_item_got)
{
yield return selector(first_item, item);
}
else
{
first_item = item;
}
first_item_got = !first_item_got;
}
}
}