У меня есть IEnumerable<T>
и IEnumerable<U>
, которые я хочу объединить в IEnumerable<KeyValuePair<T,U>>
, где индексы элементов, объединенных в KeyValuePair, одинаковы. Примечание. Я не использую IList, поэтому у меня нет счетчика или индекса для элементов, которые я объединяю. Как лучше всего это сделать? Я бы предпочел ответ LINQ, но все, что выполняет работу элегантным образом, тоже подойдет.
Как объединить (или заархивировать) два IEnumerables вместе?
Ответы (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 из Блог Барта Де Смета
- Добавлено удаление счетчика (также было отмечено в исходном сообщении Барта)
- Добавлена проверка нулевого параметра (также обсуждается в посте Барта)
В качестве обновления для всех, кто наткнется на этот вопрос, .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);
Метод объединяет каждый элемент первой последовательности с элементом второй последовательности с таким же индексом. Если последовательности не имеют одинакового количества элементов, метод объединяет последовательности, пока не достигнет конца одной из них. Например, если в одной последовательности три элемента, а в другой — четыре, результирующая последовательность будет состоять только из трех элементов.
Подумайте о том, что вы спрашиваете, немного более внимательно здесь:
Вы хотите объединить два IEnumerables, в которых «индексы элементов, объединенных в KeyValuePair, одинаковы», но у вас «нет счетчика или < strong>index для элементов, которые я объединяю".
Нет никакой гарантии, что ваши IEnumerables даже отсортированы или не отсортированы. Между двумя вашими объектами IEnumerable нет корреляции, так как вы можете ожидать их корреляции?
Посмотрите на следующее:
Реализованные в настоящее время методы
IEnumerable
- ForEach Выполняет указанное действие над каждым элементом IEnumerable.
- Группировать товары в партии одинакового размера.
- Scan Создает список, применяя делегат к парам элементов в IEnumerable.
- По крайней мере Проверяет, что в IEnumerable есть как минимум определенное количество элементов.
- AtMost Проверяет наличие не более определенного количества элементов в IEnumerable.
- Zip Создает список путем объединения двух других списков в один.
- Цикл Создает список путем повторения другого списка.
Я бы использовал что-то вроде -
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();
}
}
Это должно работать правильно, а затем правильно очищаться.
Не проверено, но должно работать:
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);
}
}
В MSDN есть следующий пример Пользовательские операторы последовательности. И Уэлбог прав; если у вас нет индекса базовых данных, у вас нет гарантии, что операция сделает то, что вы ожидаете.
Еще одна реализация из проекта 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>
(и типом возвращаемого значения), и все готово.
JaredPar имеет библиотека с большим количеством полезных вещей, включите Zip
, которая активирует то, что вы хотите сделать.