Как взять каждые два элемента из IEnumerable в паре? - PullRequest
4 голосов
/ 09 марта 2011

У меня есть IEnumerable<string>, который выглядит как {"First", "1", "Second", "2", ... }.

Мне нужно перебрать список и создать IEnumerable<Tuple<string, string>>, где кортежи будут выглядеть так:

"First", "1"

"Second", "2"

Итак, мне нужно создать пары из списка, чтобы получить пары, как указано выше.

Ответы [ 6 ]

12 голосов
/ 09 марта 2011

Ленивый метод расширения для достижения этой цели:

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();
5 голосов
/ 09 марта 2011

morelinq содержит Пакет метод расширения, который может делать то, что вы хотите:

var str = new string[] { "First", "1", "Second", "2", "Third", "3" };
var tuples = str.Batch(2, r => new Tuple<string, string>(r.FirstOrDefault(), r.LastOrDefault()));
4 голосов
/ 09 марта 2011

Вы могли бы сделать что-то вроде:

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. Преимущество этого подхода по сравнению с подходом Zip, предложенным BrokenGlass, заключается в том, что перечисляет исходное перечисляемое только один раз .

Однако, на первый взгляд, это трудно понять, поэтому я бы сделал это иначе (т.е. не использовал linq) или задокументировал свое намерение рядом с тем местом, где он используется.

4 голосов
/ 09 марта 2011

Вы можете выполнить эту работу, используя метод расширения 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»).

Затем мы фильтруем нечетные кортежи, поскольку они нам не нужны, и получаем правильные пары кортежей («Первый», «1»), («Второй», «2») и т. Д.

Edit:

Я действительно согласен с мнением комментариев - это то, что я бы назвал «умным» кодом - выглядит умным, но имеет очевидные (и не столь очевидные) недостатки:

  1. Производительность : перечисляемый должен пройти дважды - для одного и того же причина, по которой его нельзя использовать на Enumerables которые потребляют их источник, то есть данные из сети потоки.

  2. Техническое обслуживание : Не очевидно, что код делает - если кто-то еще поручено поддерживать там код могут быть проблемы впереди, особенно данный пункт 1.

Сказав это, я, вероятно, сам использовал бы старый добрый цикл foreach, если бы у меня был выбор, или со списком в качестве исходного набора цикла for, чтобы я мог напрямую использовать индекс.

1 голос
/ 09 марта 2011
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
    }
}
0 голосов
/ 09 марта 2011

Если вы используете .NET 4.0, то вы можете использовать объект кортежа (см. http://mutelight.org/articles/finally-tuples-in-c-sharp.html).. Вместе с LINQ он должен дать вам то, что вам нужно. Если нет, то вам, вероятно, нужно определить свои собственные кортежи, чтобы сделать или закодируйте эти строки, например, "First:1", "Second:2", а затем декодируйте их (также с помощью LINQ).

...