Подтвердить что ты не робот

Переплетение слияния с LINQ?

В настоящее время я немного экспериментирую с LINQ. Скажем, у меня есть две коллекции одинаковой длины:

var first = new string[] { "1", "2", "3" };
var second = new string[] { "a", "b", "c" };

Я хотел бы объединить эти две коллекции в одну, но в чередующемся порядке. Таким образом, результирующая последовательность должна быть:

"1", "a", "2", "b", "3", "c"

То, что я до сих пор использовал, представляет собой комбинацию Zip, анонимного типа и SelectMany:

var result = first.Zip( second, ( f, s ) => new { F = f, S = s } )
                  .SelectMany( fs => new string[] { fs.F, fs.S } );

Кто-нибудь знает о альтернативном/более простом способе достижения такого чередующегося слияния с LINQ?

4b9b3361

Ответ 1

Предупреждение:, это пропустит конечные элементы, если перечисления имеют разную длину. Если вы предпочтете заменить в нулях, чтобы вырезать более короткую коллекцию, используйте ответ Andrew Shepherd ниже.


Вы можете написать собственный метод расширения Interleave, например, в в этом примере.

internal static IEnumerable<T> InterleaveEnumerationsOfEqualLength<T>(
    this IEnumerable<T> first, 
    IEnumerable<T> second)
{
    using (IEnumerator<T>
        enumerator1 = first.GetEnumerator(),
        enumerator2 = second.GetEnumerator())
    {
        while (enumerator1.MoveNext() && enumerator2.MoveNext())
        {
            yield return enumerator1.Current;
            yield return enumerator2.Current;
        }
    }
}

Ответ 2

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

   var result = first.Zip(second, (f, s) => new[] { f, s })
                      .SelectMany(f => f);

Ответ 3

Данная реализация в принятом ответе имеет несогласованность:
Полученная последовательность всегда будет содержать все элементы первой последовательности (из-за внешнего цикла while), но если вторая последовательность содержит больше элементов, то эти элементы не будут добавлены.

Из метода Interleave я ожидал бы, что результирующая последовательность содержит

  • только "пары" (длина результирующей последовательности: min(length_1, length_2) * 2)) или что
  • остальные элементы более длинной последовательности всегда добавляются (длина результирующей последовательности: length_1 + length_2).

Следующая реализация следует за вторым подходом.
Обратите внимание на одиночный | в or-сравнении, который позволяет избежать оценки короткого замыкания.

public static IEnumerable<T> Interleave<T> (
    this IEnumerable<T> first, IEnumerable<T> second)
{
  using (var enumerator1 = first.GetEnumerator())
  using (var enumerator2 = second.GetEnumerator())
  {
    bool firstHasMore;
    bool secondHasMore;

    while ((firstHasMore = enumerator1.MoveNext())
         | (secondHasMore = enumerator2.MoveNext()))
    {
      if (firstHasMore)
        yield return enumerator1.Current;

      if (secondHasMore)
        yield return enumerator2.Current;
    }
  }
}

Ответ 4

Вы можете просто создать цикл и выбрать массив в зависимости от индекса:

var result =
  Enumerable.Range(0, first.Length * 2)
  .Select(i => (i % 2 == 0 ? first : second)[i / 2]);

Ответ 5

var result = first.SelectMany( ( f, i ) => new List<string> { f, second[ i ] } );