简体   繁体   English

如何使用单个foreach迭代两个相同长度的集合

[英]How to iterate through two collections of the same length using a single foreach

I know this question has been asked many times before but I tried out the answers and they don't seem to work. 我知道这个问题之前已被问过多次,但我尝试了答案,但它们似乎没有用。

I have two lists of the same length but not the same type, and I want to iterate through both of them at the same time as list1[i] is connected to list2[i] . 我有两个长度相同但类型不同的列表,我想在list1[i]连接到list2[i]的同时迭代它们。

Eg: 例如:

Assuming that i have list1 (as List<string> ) and list2 (as List<int> ) 假设我有list1(作为List<string> )和list2(作为List<int>

I want to do something like 我想做点什么

foreach( var listitem1, listitem2 in list1, list2)
{
   // do stuff
}

Is this possible? 这可能吗?

This is possible using .NET 4 LINQ Zip() operator or using open source MoreLINQ library which provides Zip() operator as well so you can use it in more earlier .NET versions 这可以使用.NET 4 LINQ Zip()运算符或使用提供Zip()运算符的开源MoreLINQ库 ,因此您可以在更早期的.NET版本中使用它

Example from MSDN : 来自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

Useful links: 有用的链接:

Edit - Iterating whilst positioning at the same index in both collections 编辑 - 在两个集合中的同一索引处进行迭代

If the requirement is to move through both collections in a 'synchronized' fashion, ie to use the 1st element of the first collection with the 1st element of the second collection, then 2nd with 2nd, and so on, without needing to perform any side effecting code, then see @sll's answer and use .Zip() to project out pairs of elements at the same index, until one of the collections runs out of elements. 如果要求是以“同步”方式移动两个集合,即将第一个集合的第一个元素与第二个集合的第一个元素一起使用,那么将第二个元素与第二个集合一起使用,依此类推,而不需要执行任何一方影响代码,然后查看@sll的答案并使用.Zip()在同一索引处投射出元素对,直到其中一个集合用尽元素。

More Generally 更普遍

Instead of the foreach , you can access the IEnumerator from the IEnumerable of both collections using the GetEnumerator() method and then call MoveNext() on the collection when you need to move on to the next element in that collection. 您可以使用GetEnumerator()方法从两个集合的IEnumerable中访问IEnumerator ,而不是foreach ,然后在需要移动到该集合中的下一个元素时MoveNext()集合上的MoveNext() This technique is common when processing two or more ordered streams, without needing to materialize the streams. 当处理两个或更多个有序流时,这种技术很常见,而不需要实现流。

var stream1Enumerator = stream1.GetEnumerator();
var stream2Enumerator = stream2.GetEnumerator();
var currentGroupId = -1; // Initial value
// i.e. Until stream1Enumerator runs out of 
while (stream1Enumerator.MoveNext())
{
   // Now you can iterate the collections independently
   if (stream1Enumerator.Current.Id != currentGroupId)
   {
       stream2Enumerator.MoveNext();
       currentGroupId = stream2Enumerator.Current.Id;
   }
   // Do something with stream1Enumerator.Current and stream2Enumerator.Current
}

As others have pointed out, if the collections are materialized and support indexing, such as an ICollection interface, you can also use the subscript [] operator, although this feels rather clumsy nowadays: 正如其他人所指出的那样,如果集合具体化并支持索引,例如ICollection接口,你也可以使用subscript []运算符,尽管现在感觉相当笨拙:

var smallestUpperBound = Math.Min(collection1.Count, collection2.Count);
for (var index = 0; index < smallestUpperBound; index++)
{
     // Do something with collection1[index] and collection2[index]
}

Finally, there is also an overload of Linq's .Select() which provides the index ordinal of the element returned, which could also be useful. 最后,还有Linq的.Select()重载,它提供了返回元素的索引序号,这也很有用。

eg the below will pair up all elements of collection1 alternatively with the first two elements of collection2 : 例如,下面将配对的所有元素collection1可选地与前两个元素collection2

var alternatePairs = collection1.Select(
    (item1, index1) => new 
    {
        Item1 = item1,
        Item2 = collection2[index1 % 2]
    });

Short answer is no you can't. 简短的回答是不,你不能。

Longer answer is that is because foreach is syntactic sugar - it gets an iterator from the collection and calls Next on it. 更长的答案是因为foreach是语法糖 - 它从集合中获取迭代器并在其上调用Next This is not possible with two collections at the same time. 两个集合不可能同时进行。

If you just want to have a single loop, you can use a for loop and use the same index value for both collections. 如果您只想拥有一个循环,则可以使用for循环并对两个集合使用相同的索引值。

for(int i = 0; i < collectionsLength; i++)
{
   list1[i];
   list2[i];
}

An alternative is to merge both collections into one using the LINQ Zip operator (new to .NET 4.0) and iterate over the result. 另一种方法是使用LINQ Zip运算符(.NET 4.0的新增功能)将两个集合合并为一个集合并迭代结果。

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 */
}

It can though be such that much of your "do stuff" can go in the lambda that here creates a tuple, which would be even better. 虽然可以这样你的“做东西”可以放在lambda中,这里创建一个元组,这会更好。

If the collections are such that they can be iterated, then a for() loop is probably simpler still though. 如果集合是可以迭代的,那么for()循环可能仍然更简单。

Update: Now with the built-in support for ValueTuple in C#7.0 we can use: 更新:现在有了C#7.0中对ValueTuple的内置支持,我们可以使用:

foreach ((var listitem1, var listitem2) in list1.Zip(list2, (i1, i2) => (i1, i2)))
{
    /* The "do stuff" from your question goes here */
}

You can wrap the two IEnumerable<> in helper class: 您可以将两个IEnumerable <>包装在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);
    }
  }
}

Instead of a foreach, why not use a for()? 而不是foreach,为什么不使用for()? for example... 例如...

int length = list1.length;
for(int i = 0; i < length; i++)
{
    // do stuff with list1[i] and list2[i] here.
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM