如何将IEnumerable中的每两项作为一对?

时间:2011-03-08 23:29:39

标签: c# list tuples ienumerable

IEnumerable<string>看起来像{"First", "1", "Second", "2", ... }

我需要遍历列表并创建{Tuples所在的IEnumerable<Tuple<string, string>>

"First", "1"

"Second", "2"

所以我需要从列表中创建对,我必须得到如上所述的对。

6 个答案:

答案 0 :(得分:12)

实现此目的的延迟扩展方法是:

public static IEnumerable<Tuple<T, T>> Tupelize<T>(this IEnumerable<T> source)
{
    using (var enumerator = source.GetEnumerator())
        while (enumerator.MoveNext())
        {
            var item1 = enumerator.Current;

            if (!enumerator.MoveNext())
                throw new ArgumentException();

            var item2 = enumerator.Current;

            yield return new Tuple<T, T>(item1, item2);
        }
}

请注意,如果元素的数量碰巧不均匀,则会抛出。另一种方法是使用此扩展方法将源集合拆分为2:

的块
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> list, int batchSize)
{

    var batch = new List<T>(batchSize);

    foreach (var item in list)
    {
        batch.Add(item);
        if (batch.Count == batchSize)
        {
            yield return batch;
            batch = new List<T>(batchSize);
        }
    }

    if (batch.Count > 0)
        yield return batch;
}

然后你可以这样做:

var tuples = items.Chunk(2)
    .Select(x => new Tuple<string, string>(x.First(), x.Skip(1).First()))
    .ToArray();

最后,仅使用现有的扩展方法:

var tuples = items.Where((x, i) => i % 2 == 0)
    .Zip(items.Where((x, i) => i % 2 == 1), 
                     (a, b) => new Tuple<string, string>(a, b))
    .ToArray();

答案 1 :(得分:5)

morelinq包含Batch扩展方法,可以执行您想要的操作:

var str = new string[] { "First", "1", "Second", "2", "Third", "3" };
var tuples = str.Batch(2, r => new Tuple<string, string>(r.FirstOrDefault(), r.LastOrDefault()));

答案 2 :(得分:4)

您可以使用LINQ .Zip()扩展方法来完成此工作:

IEnumerable<string> source = new List<string> { "First", "1", "Second", "2" };
var tupleList = source.Zip(source.Skip(1), 
                           (a, b) => new Tuple<string, string>(a, b))
                      .Where((x, i) => i % 2 == 0)
                      .ToList();

基本上这种方法是将源Enumerable自身拉上来,跳过第一个元素,这样第二个枚举就是一个 - 这会给你一对(“First”,“1”),(“1”,“Second” ),(“第二”,“2”)。

然后我们过滤奇数元组,因为我们不想要那些并最终得到正确的元组对(“First”,“1”),(“Second”,“2”)等等。

修改

我实际上同意评论的观点 - 这就是我认为“聪明”的代码 - 看起来很聪明,但有明显(并不那么明显)的缺点:

  1. 效果:Enumerable必须 被遍历两次 - 同样的 它无法使用的原因 消耗他们的Enumerables 来源,即来自网络的数据 流。

  2. 维护:什么都不明显 代码确实 - 如果其他人是 负责维护那里的代码 可能会遇到麻烦,特别是 给出第1点。

  3. 话虽如此,我可能会在给出选择时使用一个好的旧foreach循环,或者使用一个列表作为源集合for循环,所以我可以直接使用索引。

答案 3 :(得分:4)

可以做类似的事情:

var pairs = source.Select((value, index) => new {Index = index, Value = value})
                  .GroupBy(x => x.Index / 2)
                  .Select(g => new Tuple<string, string>(g.ElementAt(0).Value, 
                                                         g.ElementAt(1).Value));

这会给你一个IEnumerable<Tuple<string, string>>。它的工作原理是将元素按奇/偶位置分组,然后将每个组扩展为​​Tuple。与BrokenGlass建议的Zip方法相比,这种方法的好处是它只枚举原始可枚举的

乍一看,有些人很难理解,所以我要么采取另一种方式(即不使用linq),或者将其意图记录在使用它的旁边。

答案 4 :(得分:1)

IEnumerable<T> items = ...;
using (var enumerator = items.GetEnumerator())
{
    while (enumerator.MoveNext())
    {
        T first = enumerator.Current;
        bool hasSecond = enumerator.MoveNext();
        Trace.Assert(hasSecond, "Collection must have even number of elements.");
        T second = enumerator.Current;

        var tuple = new Tuple<T, T>(first, second);
        //Now you have the tuple
    }
}

答案 5 :(得分:0)

如果您使用的是.NET 4.0,则可以使用元组对象(请参阅http://mutelight.org/articles/finally-tuples-in-c-sharp.html)。与LINQ一起,它应该为您提供所需的一切。如果没有,那么您可能需要定义自己的元组来执行此操作或编码这些字符串,例如"First:1""Second:2"然后对其进行解码(也使用LINQ)。