Как объединить (или сжать) два IEnumerables вместе? - PullRequest
6 голосов
/ 25 марта 2009

У меня есть IEnumerable<T> и IEnumerable<U>, которые я хочу объединить в IEnumerable<KeyValuePair<T,U>>, где индексы элементов, соединенных вместе в KeyValuePair, одинаковы. Обратите внимание, что я не использую IList, поэтому у меня нет количества или индекса для элементов, которые я объединяю. Как лучше всего это сделать? Я бы предпочел ответ LINQ, но все, что выполнено в элегантной манере, тоже подойдет.

Ответы [ 10 ]

17 голосов
/ 25 марта 2009

Примечание: Начиная с .NET 4.0, фреймворк включает в себя метод расширения .Zip для IEnumerable, документированный здесь . Следующее поддерживается для потомков и для использования в .NET Framework версии ранее 4.0.

Я использую следующие методы расширения:

// From http://community.bartdesmet.net/blogs/bart/archive/2008/11/03/c-4-0-feature-focus-part-3-intermezzo-linq-s-new-zip-operator.aspx
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> func) {
    if (first == null) 
        throw new ArgumentNullException("first");
    if (second == null) 
        throw new ArgumentNullException("second");
    if (func == null)
        throw new ArgumentNullException("func");
    using (var ie1 = first.GetEnumerator())
    using (var ie2 = second.GetEnumerator())
        while (ie1.MoveNext() && ie2.MoveNext())
            yield return func(ie1.Current, ie2.Current);
}

public static IEnumerable<KeyValuePair<T, R>> Zip<T, R>(this IEnumerable<T> first, IEnumerable<R> second) {
    return first.Zip(second, (f, s) => new KeyValuePair<T, R>(f, s));
}

РЕДАКТИРОВАТЬ : после комментариев я обязан уточнить и исправить некоторые вещи:

  • Первоначально я взял первый дословный вариант реализации Zip из блога Барта де Смета
  • Добавлена ​​утилита перечисления (которая была также отмечена в исходном сообщении Барта)
  • Добавлена ​​проверка нулевых параметров (также обсуждается в посте Барта)
15 голосов
/ 23 сентября 2010

В качестве обновления для всех, кто сталкивается с этим вопросом, .Net 4.0 поддерживает это изначально, например, от MS:

int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };

var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);
2 голосов
/ 25 марта 2009

Подумайте о том, что вы спрашиваете, более подробно здесь:

Вы хотите объединить два IEnumerables, в которых «индексы элементов, соединенных вместе в KeyValuePair, одинаковы», но у вас нет числа или index для элементов, которые я объединяю ".

Нет никакой гарантии, что ваши IEnumerables даже отсортированы или не отсортированы. Нет никакой корреляции между вашими двумя объектами IEnumerable, так как вы можете ожидать их корреляции?

1 голос
/ 25 марта 2009

Я бы использовал что-то вроде -

IEnumerable<KeyValuePair<T,U>> Merge<T,U>(IEnumerable<T> keyCollection, IEnumerable<U> valueCollection)
{
    var keys = keyCollection.GetEnumerator();
    var values = valueCollection.GetEnumerator();
    try
    { 
        keys.Reset();
        values.Reset();

        while (keys.MoveNext() && values.MoveNext())
        {
            yield return new KeyValuePair<T,U>(keys.Current,values.Current);
        }
    }
    finally
    {
        keys.Dispose();
        values.Dispose();
    }
}

Это должно работать правильно, а после этого правильно очищаться.

1 голос
/ 25 марта 2009

Посмотрите на Следующее :

Методы, применяемые в настоящее время

IEnumerable

  • ForEach Выполняет указанное действие для каждого элемента IEnumerable.
  • Скопление Группирует предметы в партии одинакового размера.
  • Сканирование Создает список, применяя делегат к парам элементов в IEnumerable.
  • AtLeast Проверяет, что в IEnumerable есть хотя бы определенное количество элементов.
  • AtMost Проверяет, что в IEnumerable содержится не более определенного количества элементов.
  • Zip Создает список путем объединения двух других списков в один.
  • Цикл Создает список, повторяя другой список.
0 голосов
/ 25 марта 2009

Еще одна реализация из проекта функционально-точечная от Алексея Романова:

/// <summary>
/// Takes two sequences and returns a sequence of corresponding pairs. 
/// If one sequence is short, excess elements of the longer sequence are discarded.
/// </summary>
/// <typeparam name="T1">The type of the 1.</typeparam>
/// <typeparam name="T2">The type of the 2.</typeparam>
/// <param name="sequence1">The first sequence.</param>
/// <param name="sequence2">The second sequence.</param>
/// <returns></returns>
public static IEnumerable<Tuple<T1, T2>> Zip<T1, T2>(
    this IEnumerable<T1> sequence1, IEnumerable<T2> sequence2) {
    using (
        IEnumerator<T1> enumerator1 = sequence1.GetEnumerator())
    using (
        IEnumerator<T2> enumerator2 = sequence2.GetEnumerator()) {
        while (enumerator1.MoveNext() && enumerator2.MoveNext()) {
            yield return
                Pair.New(enumerator1.Current, enumerator2.Current);
        }
    }
    //
    //zip :: [a] -> [b] -> [(a,b)]
    //zip (a:as) (b:bs) = (a,b) : zip as bs
    //zip _      _      = []
}

Замените Pair.New новым KeyValuePair<T1, T2> (и типом возврата), и все готово.

0 голосов
/ 25 марта 2009

JaredPar имеет библиотеку со множеством полезных вещей, включая Zip, которая включит то, что вы хотите сделать.

0 голосов
/ 25 марта 2009

MSDN имеет следующий пример Пользовательские последовательности . И Велбог прав; если у вас нет индекса для базовых данных, у вас нет гарантии, что операция сделает то, что вы ожидаете.

0 голосов
/ 25 марта 2009

Вы можете использовать методы Zip в MoreLINQ .

0 голосов
/ 25 марта 2009

Не проверено, но должно работать:

IEnumerable<KeyValuePair<T, U>> Zip<T, U>(IEnumerable<T> t, IEnumerable<U> u) {
    IEnumerator<T> et = t.GetEnumerator();
    IEnumerator<U> eu = u.GetEnumerator();

    for (;;) {
        bool bt = et.MoveNext();
        bool bu = eu.MoveNext();
        if (bt != bu)
            throw new ArgumentException("Different number of elements in t and u");
        if (!bt)
            break;
        yield return new KeyValuePair<T, U>(et.Current, eu.Current);
    }
}
...