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

时间:2011-12-15 10:41:43

标签: c#

我知道这个问题之前已被问过多次,但我尝试了答案,但它们似乎没有用。

我有两个长度相同但类型不同的列表,我希望在list1[i]连接到list2[i]的同时迭代这两个列表。

例如:

假设我有list1(作为List<string>)和list2(作为List<int>

我想做点什么

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

这可能吗?

6 个答案:

答案 0 :(得分:44)

这可以使用.NET 4 LINQ Zip() operator或使用提供Zip()运算符的开源MoreLINQ library,因此您可以在更早期的.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

有用的链接:

答案 1 :(得分:31)

编辑 - 在两个集合中的同一索引处进行迭代

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

更一般

您可以使用foreach方法从两个集合的IEnumerator访问IEnumerable,而不是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接口),您也可以使用下标[]运算符,尽管现在感觉相当笨拙:

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

最后,还有一个overload of Linq's .Select(),它提供了返回元素的索引序号,这也很有用。

e.g。以下将collection1的所有元素与collection2的前两个元素配对:

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

答案 2 :(得分:16)

简短的回答是不,你不能。

更长的答案是因为foreach是语法糖 - 它从集合中获取迭代器并在其上调用Next两个集合同时无法实现这一点。

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

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

另一种方法是使用LINQ Zip运算符(.NET 4.0的新增功能)将两个集合合并为一个集合并迭代结果。

答案 3 :(得分:7)

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

答案 4 :(得分:2)

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

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

答案 5 :(得分:2)

你可以包装两个IEnumerable&lt;&gt;在辅助类中:

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