2009-03-25 4 views
6

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

+2

С .NET 4.0, каркас поставляется с [IEnumerable Zip] (HTTP://msdn.microsoft.com/en-us/library/dd267698%28v=vs.110%29.aspx) метод расширения. –

+0

Еще [еще одно сообщение в блоге] (http://blogs.msdn.com/ericlippert/archive/2009/05/07/zip-me-up.aspx) от Eric Lippert – n8wrl

+0

Забавный - я только что прочитал эту прошлую ночь. =) –

ответ

17

Примечание: С .NET 4.0, структура включает в себя метод .Zip расширения на IEnumerable, документально here. Следующее поддерживается для потомков и для использования в версии .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 дословный от Bart De Smet's blog
  • Добавленный переучет, который был также noted на исходном посте Bart)
  • Добавлена ​​проверка нулевой параметр (также обсуждается в пост Барта)
+0

Больше, чем у меня. – erikkallen

+0

Это неправильно, так как предполагает, что IEnumerables сохраняет порядок. – Welbog

+0

Сортировка: это побуждает _caller_ сделать предположение. Это единственное, что он может сделать, и иногда предположение является обоснованным. –

2

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

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

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

+0

@welbog: Похоже, что возникает непонимание вопроса. Я думаю, что по «индексу» Эрик означал положение элемента в IEnumerable (1-й, 2-й и т. Д.) –

+0

@mausch: позиция, которая не гарантируется. В зависимости от реализации, порядок двух IEnumerables может быть не таким, как ожидается. – Welbog

+0

@welbog: было бы разумно называть Zip таким перечислимым? Либо это не имеет смысла, либо вызывающий должен знать об этом ... Я не вижу другого выбора. –

0

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

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); 
    } 
} 
1

Посмотрите на nextension:

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

IEnumerable

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

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

1

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

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

Я думаю, что это хорошая форма, чтобы называть его «zip», так как это известная операция в функциональном мире. – Daniel

0

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

0

Другая реализация от functional-dotnet project Алексей Романов:

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

15

Как обновление для тех, кто наткнуться на этот вопрос, .Net 4.0 поддерживает это изначально, как бывший от MS:

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

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

 Смежные вопросы

  • Нет связанных вопросов^_^