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

31

我知道这个问题之前已经被问过很多次,但我尝试了回答,它们似乎不起作用。

我有两个长度相同但类型不同的列表,并且我想同时遍历它们,使list1[i]list2[i]相对应。

例如:

假设我有列表1(作为List<string>)和列表2(作为List<int>

我想做类似这样的事情

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

这个是否可能?


第一个foreach循环里再加一个循环来遍历第二个列表怎么样? - Bali C
Dupe:http://stackoverflow.com/questions/5630758/how-to-loop-through-two-lists-simultaneously - Jonathan
4
@Jonathan - VB.NET不是C#,不是重复内容。 - Oded
@Oded:我的错。我现在同时使用两者,有时候我的头脑甚至看不出区别。 - Jonathan
6个回答

53
这可以通过使用.NET 4的LINQ Zip() 操作符或使用开源的MoreLINQ库实现,它也提供了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

有用的链接:


http://community.bartdesmet.net/blogs/bart/archive/2008/11/03/c-4-0-feature-focus-part-3-intermezzo-linq-s-new-zip-operator.aspx - tbddeveloper
如果OP询问的是特定问题,比如连接字符串,那么这样做很好。但并不是所有的用例都会从中受益。 - Paul Childs

35

编辑 - 在两个集合中定位到同一索引时进行迭代

如果要以“同步”的方式移动两个集合,即使用第一个集合的第一个元素与第二个集合的第一个元素,然后是第二个与第二个,以此类推,而不需要执行任何副作用代码,则可以参见@sll's answer 并使用.Zip()将在同一索引处投射出一对元素,直到其中一个集合没有元素为止。

更一般地说

您可以从每个集合的IEnumerable中访问IEnumerator,使用GetEnumerator()方法,然后在需要移动到该集合的下一个元素时调用MoveNext()。 当处理两个或多个有序流而不需要将流实体化时,这种技术很常见。

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
}

正如其他人所指出的,如果集合被实例化并支持索引,例如 ICollection 接口,你也可以使用下标 [] 操作符,尽管这在现今感觉相当笨拙:

如其他人指出,如果集合被实例化且支持索引,例如 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]
    });

不要忘记 Dispose()(或包装在 using 中)枚举器。 - Bip901
不错,虽然大多数内存集合迭代器的Dispose实现将是无操作的。 - StuartLC

16

简短的回答是不行。

更详细的解释是因为foreach只是语法糖 - 它从集合中获取一个迭代器并调用Next方法。这在同时使用两个集合时是不可能的。

如果你只想要一个循环,你可以使用for循环并使用相同的索引值来遍历两个集合。

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

另一个选择是使用 LINQ Zip 运算符(.NET 4.0 中的新功能)将两个集合合并成一个,并迭代处理结果。


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

如果你可以在 lambda 表达式中创建元组,那么“做事情”的大部分工作都可以在其中完成,这会更好。

如果集合可以迭代,那么使用 for() 循环可能仍然更简单。

更新:现在 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 */
}

3

您可以将两个 IEnumerable<> 封装在辅助类中:

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

2

为什么不使用for()而不是foreach?例如...

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接