Переплетение слияния с 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?
Ответы
Ответ 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 ] } );