Iterate two Lists or Arrays with one ForEach statement in C#
This just for general knowledge:
If I have two, let's say, List, and I want to iterate both with the same fore开发者_开发技巧ach loop, can we do that?
Edit
Just to clarify, I wanted to do this:
List<String> listA = new List<string> { "string", "string" };
List<String> listB = new List<string> { "string", "string" };
for(int i = 0; i < listA.Count; i++)
listB[i] = listA[i];
But with a foreach =)
This is known as a Zip operation and will be supported in .NET 4.
With that, you would be able to write something like:
var numbers = new [] { 1, 2, 3, 4 };
var words = new [] { "one", "two", "three", "four" };
var numbersAndWords = numbers.Zip(words, (n, w) => new { Number = n, Word = w });
foreach(var nw in numbersAndWords)
{
Console.WriteLine(nw.Number + nw.Word);
}
As an alternative to the anonymous type with the named fields, you can also save on braces by using a Tuple and its static Tuple.Create helper:
foreach (var nw in numbers.Zip(words, Tuple.Create))
{
Console.WriteLine(nw.Item1 + nw.Item2);
}
If you don't want to wait for .NET 4.0, you could implement your own Zip
method. The following works with .NET 2.0. You can adjust the implementation depending on how you want to handle the case where the two enumerations (or lists) have different lengths; this one continues to the end of the longer enumeration, returning the default values for missing items from the shorter enumeration.
static IEnumerable<KeyValuePair<T, U>> Zip<T, U>(IEnumerable<T> first, IEnumerable<U> second)
{
IEnumerator<T> firstEnumerator = first.GetEnumerator();
IEnumerator<U> secondEnumerator = second.GetEnumerator();
while (firstEnumerator.MoveNext())
{
if (secondEnumerator.MoveNext())
{
yield return new KeyValuePair<T, U>(firstEnumerator.Current, secondEnumerator.Current);
}
else
{
yield return new KeyValuePair<T, U>(firstEnumerator.Current, default(U));
}
}
while (secondEnumerator.MoveNext())
{
yield return new KeyValuePair<T, U>(default(T), secondEnumerator.Current);
}
}
static void Test()
{
IList<string> names = new string[] { "one", "two", "three" };
IList<int> ids = new int[] { 1, 2, 3, 4 };
foreach (KeyValuePair<string, int> keyValuePair in ParallelEnumerate(names, ids))
{
Console.WriteLine(keyValuePair.Key ?? "<null>" + " - " + keyValuePair.Value.ToString());
}
}
You can use Union or Concat, the former removes duplicates, the later doesn't
foreach (var item in List1.Union(List1))
{
//TODO: Real code goes here
}
foreach (var item in List1.Concat(List1))
{
//TODO: Real code goes here
}
Since C# 7, you can use Tuples...
int[] nums = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three", "four" };
foreach (var tuple in nums.Zip(words, (x, y) => (x, y)))
{
Console.WriteLine($"{tuple.Item1}: {tuple.Item2}");
}
// or...
foreach (var tuple in nums.Zip(words, (x, y) => (Num: x, Word: y)))
{
Console.WriteLine($"{tuple.Num}: {tuple.Word}");
}
EDIT 2022-04-14
The Zip extension method just got better since the original answer (and .NET Core 3.0), so you can now just write
int[] nums = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three", "four" };
foreach (var (x, y) in nums.Zip(words))
{
Console.WriteLine($"{x}: {y}");
}
and variant for three arrays is also supported since .NET 6
int[] nums = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three", "four" };
string[] roman = { "I", "II", "III", "IV" };
foreach (var (x, y, z) in nums.Zip(words, roman))
{
Console.WriteLine($"{x}: {y} ({z})");
}
Here's a custom IEnumerable<> extension method that can be used to loop through two lists simultaneously.
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1
{
public static class LinqCombinedSort
{
public static void Test()
{
var a = new[] {'a', 'b', 'c', 'd', 'e', 'f'};
var b = new[] {3, 2, 1, 6, 5, 4};
var sorted = from ab in a.Combine(b)
orderby ab.Second
select ab.First;
foreach(char c in sorted)
{
Console.WriteLine(c);
}
}
public static IEnumerable<Pair<TFirst, TSecond>> Combine<TFirst, TSecond>(this IEnumerable<TFirst> s1, IEnumerable<TSecond> s2)
{
using (var e1 = s1.GetEnumerator())
using (var e2 = s2.GetEnumerator())
{
while (e1.MoveNext() && e2.MoveNext())
{
yield return new Pair<TFirst, TSecond>(e1.Current, e2.Current);
}
}
}
}
public class Pair<TFirst, TSecond>
{
private readonly TFirst _first;
private readonly TSecond _second;
private int _hashCode;
public Pair(TFirst first, TSecond second)
{
_first = first;
_second = second;
}
public TFirst First
{
get
{
return _first;
}
}
public TSecond Second
{
get
{
return _second;
}
}
public override int GetHashCode()
{
if (_hashCode == 0)
{
_hashCode = (ReferenceEquals(_first, null) ? 213 : _first.GetHashCode())*37 +
(ReferenceEquals(_second, null) ? 213 : _second.GetHashCode());
}
return _hashCode;
}
public override bool Equals(object obj)
{
var other = obj as Pair<TFirst, TSecond>;
if (other == null)
{
return false;
}
return Equals(_first, other._first) && Equals(_second, other._second);
}
}
}
No, you would have to use a for-loop for that.
for (int i = 0; i < lst1.Count; i++)
{
//lst1[i]...
//lst2[i]...
}
You can't do something like
foreach (var objCurrent1 int lst1, var objCurrent2 in lst2)
{
//...
}
I often need to execute an action on each pair in two collections. The Zip method is not useful in this case.
This extension method ForPair can be used:
public static void ForPair<TFirst, TSecond>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second,
Action<TFirst, TSecond> action)
{
using (var enumFirst = first.GetEnumerator())
using (var enumSecond = second.GetEnumerator())
{
while (enumFirst.MoveNext() && enumSecond.MoveNext())
{
action(enumFirst.Current, enumSecond.Current);
}
}
}
So for example, you could write:
var people = new List<Person> { person1, person2 };
var wages = new List<decimal> { 10, 20 };
people.ForPair(wages, (p, w) => p.Wage = w);
Note however that this method cannot be used to modify the collection itself. This for example will not work:
List<String> listA = new List<string> { "string", "string" };
List<String> listB = new List<string> { "string", "string" };
listA.ForPair(listA, (c1, c2) => c1 = c2); // Nothing will happen!
So in this case, the example in your own question is probably the best way.
If you want one element with the corresponding one you could do
Enumerable.Range(0, List1.Count).All(x => List1[x] == List2[x]);
That will return true if every item is equal to the corresponding one on the second list
If that's almost but not quite what you want it would help if you elaborated more.
This method would work for a list implementation and could be implemented as an extension method.
public void TestMethod()
{
var first = new List<int> {1, 2, 3, 4, 5};
var second = new List<string> {"One", "Two", "Three", "Four", "Five"};
foreach(var value in this.Zip(first, second, (x, y) => new {Number = x, Text = y}))
{
Console.WriteLine("{0} - {1}",value.Number, value.Text);
}
}
public IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(List<TFirst> first, List<TSecond> second, Func<TFirst, TSecond, TResult> selector)
{
if (first.Count != second.Count)
throw new Exception();
for(var i = 0; i < first.Count; i++)
{
yield return selector.Invoke(first[i], second[i]);
}
}
You could also simply use a local integer variable if the lists have the same length:
List<classA> listA = fillListA();
List<classB> listB = fillListB();
var i = 0;
foreach(var itemA in listA)
{
Console.WriteLine(itemA + listB[i++]);
}
You can also do the following:
var i = 0;
foreach (var itemA in listA)
{
Console.WriteLine(itemA + listB[i++]);
}
Note: the length of
listA
must be the same withlistB
.
I understand/hope that the lists have the same length: No, your only bet is going with a plain old standard for loop.
精彩评论