如何在Linq中处理“并行”序列?

时间:2009-05-04 20:23:29

标签: c# linq

假设我有两个枚举,我知道它们具有相同数量的元素,并且每个元素与另一个枚举中相同位置的元素“对应”。有没有办法同时处理这两个枚举,以便我可以同时访问每个枚举的相应元素?

使用理论上的LINQ语法,我想到的是:

from x in seq1, y in seq2
select new {x.foo, y.bar}

4 个答案:

答案 0 :(得分:3)

您正在寻找的功能称为“Zip”。它像拉链一样工作。它将在.NET 4.0 iirc中。在此期间,您可能需要查看BclExtras库。 (伙计,我是这个lib的真正拥护者,哈哈)。

IEnumerable<Tuple<TSeq1, TSeq2>> tuples = from t in seq1.Zip(seq2)
                                          select t;

如果你只是想完成,你必须得到两个序列枚举器并使用传统循环“并行”运行它们。

答案 1 :(得分:3)

由于Neil Williams删除了他的答案,我会继续发布implementation by Jon Skeet的链接。

解释相关部分:

public static IEnumerable<KeyValuePair<TFirst,TSecond>> Zip<TFirst,TSecond>
    (this IEnumerable<TFirst> source, IEnumerable<TSecond> secondSequence)
{
    using (IEnumerator<TSecond> secondIter = secondSequence.GetEnumerator())
    {
        foreach (TFirst first in source)
        {
            if (!secondIter.MoveNext())
            {
                throw new ArgumentException
                    ("First sequence longer than second");
            }
            yield return new KeyValuePair<TFirst, TSecond>(first, secondIter.Current);
        }
        if (secondIter.MoveNext())
        {
            throw new ArgumentException
                ("Second sequence longer than first");
        }
    }        
}

请注意KeyValuePair<>是我的补充,我通常不喜欢这种方式使用它。相反,我会定义通用的PairTuple类型。但是,它们不包含在当前版本的框架中,我不想使用额外的类定义来混淆这个示例。

答案 2 :(得分:0)

在4.0中添加了一个“Zip”方法来解决这个问题(就像一个拉链,拉紧相邻的元素。)在此之前,最可读的(虽然不是最佳的)方式可能是这样的,除非懒惰的评估非常重要:

var indexedA = seqA.ToArray();
var indexedB = seqB.ToArray();

for(int i = 0; i < indexedA.Length && i < indexedB.Length; i++)
{
    var thisA = indexedA[i];
    var thisB = indexedB[i];
    // whatever
}

答案 3 :(得分:-2)

<强>更新

Eric Lippert最近发布了这个帖子:http://blogs.msdn.com/ericlippert/archive/2009/05/07/zip-me-up.aspx

这特别有趣,因为他在C#4中发布了新扩展的来源:

public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>
    (this IEnumerable<TFirst> first, 
    IEnumerable<TSecond> second, 
    Func<TFirst, TSecond, TResult> resultSelector) 
{
    if (first == null) throw new ArgumentNullException("first");
    if (second == null) throw new ArgumentNullException("second");
    if (resultSelector == null) throw new ArgumentNullException("resultSelector");
    return ZipIterator(first, second, resultSelector);
}

private static IEnumerable<TResult> ZipIterator<TFirst, TSecond, TResult>
    (IEnumerable<TFirst> first, 
    IEnumerable<TSecond> second, 
    Func<TFirst, TSecond, TResult> resultSelector) 
{
    using (IEnumerator<TFirst> e1 = first.GetEnumerator())
        using (IEnumerator<TSecond> e2 = second.GetEnumerator())
            while (e1.MoveNext() && e2.MoveNext())
                yield return resultSelector(e1.Current, e2.Current);
}

原始回答:

您指的是加入吗?

from x in seq1
join y in seq2
on x.foo equals y.foo
select new {x, y}

还有pLinq - 并行执行linq语句(跨多个线程)。


修改

啊 - 谢谢你澄清这个问题,虽然我真的认为我的答案不值得投票。

听起来你想要的是:

from x in seq1
join y in seq2
on x.Index equals y.Index
select new {x.Foo, y.Bar}

不幸的是,你不能用Linq做到这一点 - 它扩展了IEnumerable,它只有currentnext属性,因此没有索引属性。

显然你可以在C#中使用嵌套的for循环和if块轻松地完成这项工作,但你不能用Linq我不敢。

在linq语法中模仿这种方法的唯一方法是人为添加索引:

int counter = 0;
var indexed1 = (
    from x in seq1
    select { item = x, index = counter++ } ).ToList();
//note the .ToList forces execution, this won't work if lazy

counter = 0;
var indexed2 = (
    from x in seq2
    select { item = x, index = counter++ } ).ToList();

var result = 
    from x in indexed1 
    join y in indexed2
    on x.index = y.index
    select new {x.item.Foo, y.item.Bar}