Как выполнить итерацию по двум коллекциям одинаковой длины, используя один foreach
Я знаю, что этот вопрос задавался много раз, но я опробовал ответы, и они, похоже, не работают.
У меня есть два списка одинаковой длины, но не одного и того же типа, и я хочу перебирать их оба в то же самое время, когда list1 [i] подключен к списку2 [i].
Например:
Предполагая, что у меня есть list1 (как List) и list2 (as List)
Я хочу сделать что-то вроде
foreach( var listitem1, listitem2 in list1, list2)
{
//do stuff
}
Возможно ли это?
Ответы
Ответ 1
Изменить - Итерация при позиционировании с одним индексом в обеих коллекциях
Если требование состоит в том, чтобы перемещать обе коллекции по принципу "синхронно", то есть использовать 1-й элемент первой коллекции с 1-м элементом второй коллекции, затем 2-й со 2-го и так далее, без необходимости выполнить какой-либо побочный код, затем посмотреть @sll answer и использовать .Zip()
для проектирования пар элементов в одном и том же индексе, пока один из наборов не закончится из элементов.
Больше
Вместо foreach
вы можете получить доступ к IEnumerator
из IEnumerable
обеих коллекций с помощью метода GetEnumerator()
, а затем вызвать MoveNext()
в коллекции, когда вам нужно перейти к следующему элементу в этой коллекции. Этот метод является обычным явлением при обработке двух или более упорядоченных потоков без необходимости материализовать потоки.
var stream1Enumerator = stream1.GetEnumerator();
var stream2Enumerator = stream2.GetEnumerator();
// i.e. Until stream1Enumerator runs out of
while (stream1Enumerator.MoveNext())
{
// Now you can iterate the collections independently
if (moon == 'Blue')
{
stream2Enumerator.MoveNext();
}
// Do something with stream1Enumerator.Current and stream2Enumerator.Current
}
Как отмечали другие, если сборники материализованы и поддерживают индексирование, например интерфейс ICollection
, вы также можете использовать оператор подстроки []
, хотя в настоящее время это кажется довольно неуклюжим:
var smallestUpperBound = Math.Min(collection1.Count, collection2.Count);
for (var index = 0; index < smallestUpperBound; index++)
{
// Do something with collection1[index] and collection2[index]
}
Наконец, существует также перегрузка Linq .Select()
, которая предоставляет порядковый номер индекса возвращенного элемента, который также может быть полезен.
например. ниже будут объединены все элементы collection1
в качестве альтернативы с первыми двумя элементами collection2
:
var alternatePairs = collection1.Select(
(item1, index1) => new
{
Item1 = item1,
Item2 = collection2[index1 % 2]
});
Ответ 2
Это возможно с помощью .NET 4 LINQ оператора Zip() или с использованием библиотеки с расширением openLINQ с открытым исходным кодом, которая предоставляет оператор Zip()
, так что вы можете использовать его в более ранних версиях .NET
Пример из MSDN:
int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
// The following example concatenates corresponding elements of the
// two input sequences.
var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);
foreach (var item in numbersAndWords)
{
Console.WriteLine(item);
}
// OUTPUT:
// 1 one
// 2 two
// 3 three
Полезные ссылки:
Ответ 3
Короткий ответ - вы не можете.
Более длинный ответ заключается в том, что foreach
является синтаксическим сахаром - он получает итератор из коллекции и называет Next
на нем. Это невозможно с двумя коллекциями одновременно.
Если вы просто хотите иметь один цикл, вы можете использовать цикл for
и использовать одинаковое значение индекса для обеих коллекций.
for(int i = 0; i < collectionsLength; i++)
{
list1[i];
list2[i];
}
Альтернативой является объединение обеих коллекций в один, используя оператор LINQ Zip (новый для .NET 4.0) и повторение результата.
Ответ 4
foreach(var tup in list1.Zip(list2, (i1, i2) => Tuple.Create(i1, i2))
{
var listItem1 = tup.Item1;
var listItem2 = tup.Item2;
/* The "do stuff" from your question goes here */
}
Это может быть так, что большая часть вашего "делающего вещи" может идти в лямбда, что создает кортеж, что было бы еще лучше.
Если коллекции таковы, что их можно повторить, то цикл for()
, вероятно, еще проще, но.
Ответ 5
Вместо foreach, почему бы не использовать a for()? например...
int length = list1.length;
for(int i = 0; i < length; i++)
{
// do stuff with list1[i] and list2[i] here.
}
Ответ 6
Вы можете обернуть два класса IEnumerable < > in helper:
var nums = new []{1, 2, 3};
var strings = new []{"a", "b", "c"};
ForEach(nums, strings).Do((n, s) =>
{
Console.WriteLine(n + " " + s);
});
//-----------------------------
public static TwoForEach<A, B> ForEach<A, B>(IEnumerable<A> a, IEnumerable<B> b)
{
return new TwoForEach<A, B>(a, b);
}
public class TwoForEach<A, B>
{
private IEnumerator<A> a;
private IEnumerator<B> b;
public TwoForEach(IEnumerable<A> a, IEnumerable<B> b)
{
this.a = a.GetEnumerator();
this.b = b.GetEnumerator();
}
public void Do(Action<A, B> action)
{
while (a.MoveNext() && b.MoveNext())
{
action.Invoke(a.Current, b.Current);
}
}
}