Как объединить (или заархивировать) два IEnumerables вместе?

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


person Erik Forbes    schedule 25.03.2009    source источник
comment
Еще еще одна запись в блоге Эрика Липперт   -  person n8wrl    schedule 08.05.2009
comment
Забавно - я только вчера прочитал это. знак равно   -  person Erik Forbes    schedule 08.05.2009
comment
Начиная с .NET 4.0, платформа поставляется с метод расширения IEnumerable Zip.   -  person Joey Adams    schedule 24.04.2014


Ответы (10)


Примечание. Начиная с .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));
}

EDIT: после комментариев я обязан уточнить и исправить некоторые вещи:

  • Первоначально я дословно взял первую реализацию Zip из Блог Барта Де Смета
  • Добавлено удаление счетчика (также было отмечено в исходном сообщении Барта)
  • Добавлена ​​проверка нулевого параметра (также обсуждается в посте Барта)
person Mauricio Scheffer    schedule 25.03.2009
comment
Своего рода: побуждает вызывающего сделать предположение. Это делает единственное, что он может сделать, и иногда предположение вполне обосновано. - person Joel Coehoorn; 25.03.2009
comment
Вы также должны избавиться от счетчиков. - person Reed Copsey; 25.03.2009
comment
Справедливо. Тем не менее, если вы не будете осторожны с тем, какие типы вы передаете ему, вы выстрелите себе в ногу. - person Welbog; 25.03.2009
comment
@reed: ознакомьтесь с оригинальной статьей Барта (community.bartdesmet.net/blogs/bart/archive/2008/11/03/) охватывает утилизацию и другие вопросы . - person Mauricio Scheffer; 25.03.2009
comment
Спасибо - это именно то, что я ищу. - person Erik Forbes; 25.03.2009

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

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

var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);

Документация:

Метод объединяет каждый элемент первой последовательности с элементом второй последовательности с таким же индексом. Если последовательности не имеют одинакового количества элементов, метод объединяет последовательности, пока не достигнет конца одной из них. Например, если в одной последовательности три элемента, а в другой — четыре, результирующая последовательность будет состоять только из трех элементов.

person Lasse Espeholt    schedule 23.09.2010

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

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

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

person Welbog    schedule 25.03.2009
comment
@welbog: Похоже, что есть недопонимание вопроса. Я думаю, что под индексом Эрик имел в виду позицию элемента в IEnumerable (1-й, 2-й и т.д.) - person Mauricio Scheffer; 25.03.2009
comment
@mausch: позиция, которая не гарантирована. В зависимости от реализации порядок двух IEnumerables может отличаться от ожидаемого. - person Welbog; 25.03.2009
comment
@welbog: имеет ли смысл вызывать Zip с таким перечисляемым? Либо это не имеет смысла, либо звонящий должен знать об этом... Я не вижу другого выхода. - person Mauricio Scheffer; 25.03.2009
comment
@mausch: я хочу сказать, что сама проблема не может быть решена, как указано. Для установления связи между объектами в двух IEnumerables просто не требуется никакой информации. Ваше решение делает предположение, предположение, которое добавляет дополнительную информацию, но дает сбой и сгорает, когда оно ложно. - person Welbog; 25.03.2009
comment
как вы можете ожидать их корреляции - верная точка зрения, но я предполагаю, что «индекс» - это просто порядок, в котором они получены из IEnumerable. Это все, с чем мне нужно сопоставить в моем случае. - person Erik Forbes; 25.03.2009
comment
Кстати, голосую, потому что ваши опасения невероятно актуальны (если в моем случае они полностью ожидаемы и поддаются контролю). - person Erik Forbes; 25.03.2009

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

Реализованные в настоящее время методы

IEnumerable

  • ForEach Выполняет указанное действие над каждым элементом IEnumerable.
  • Группировать товары в партии одинакового размера.
  • Scan Создает список, применяя делегат к парам элементов в IEnumerable.
  • По крайней мере Проверяет, что в IEnumerable есть как минимум определенное количество элементов.
  • AtMost Проверяет наличие не более определенного количества элементов в IEnumerable.
  • Zip Создает список путем объединения двух других списков в один.
  • Цикл Создает список путем повторения другого списка.
person gimel    schedule 25.03.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();
    }
}

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

person Reed Copsey    schedule 25.03.2009
comment
Я думаю, что правильно называть это zip, так как это известная операция в функциональном мире. - person Daniel; 16.04.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);
    }
}
person erikkallen    schedule 25.03.2009

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

person Jeff Yates    schedule 25.03.2009

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

person Daniel Brückner    schedule 25.03.2009

Еще одна реализация из проекта functional-dotnet Алексея Романова:

/// <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> (и типом возвращаемого значения), и все готово.

person Mauricio Scheffer    schedule 25.03.2009

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

person Daniel Earwicker    schedule 25.03.2009