Reputation: 83326
I have two lists that are of the same length, is it possible to loop through these two lists at once?
I am looking for the correct syntax to do the below
foreach itemA, itemB in ListA, ListB
{
Console.WriteLine(itemA.ToString()+","+itemB.ToString());
}
do you think this is possible in C#? And if it is, what is the lambda expression equivalent of this?
Upvotes: 25
Views: 21760
Reputation: 20911
LINQ now has a built-in Zip method, so you don't need to create your own. The resulting sequence is as long as the shortest input. Zip currently (as of .NET Core 3.0) has 2 overloads. The simpler one returns a sequence of tuples. It lets us produce some very terse code that's close to the original request:
int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
foreach (var (number, word) in numbers.Zip(words))
Console.WriteLine($"{number}, {word}");
// 1, one
// 2, two
// 3, three
Or, the same thing but without tuple unpacking:
foreach (var item in numbers.Zip(words))
Console.WriteLine($"{item.First}, {item.Second}");
The other overload (which appeared earlier than Core 3.0) takes a mapping function, giving you more control over the result. This example returns a sequence of strings, but you could return a sequence of whatever (e.g. some custom class).
var numbersAndWords = numbers.Zip(words, (number, word) => $"{number}, {word}");
foreach (string item in numbersAndWords)
Console.WriteLine(item);
If you are using LINQ on regular objects (as opposed to using it to generate SQL), you can also check out MoreLINQ, which provides several zipping methods. Each of those methods takes up to 4 input sequences, not just 2:
EquiZip
- An exception is thrown if the input sequences are of different lengths.
ZipLongest
- The resulting sequence will always be as long as the longest of input sequences where the default value of each of the shorter sequence element types is used for padding.
ZipShortest
- The resulting sequence is as short as the shortest input sequence.
See their examples and/or tests for usage. It seems MoreLINQ's zipping came before regular LINQ's, so if you're stuck with an old version of .NET, it might be a good option.
Upvotes: 7
Reputation: 303
I have this small function which helps me to iterate through this two list objects. schema is of type SqlData, which is a class that hold three properties. And data is a list that holds values of dynamic type. First I'm iterating through the schema collection and than using the index of item to iterate through the data object.
public List<SqlData> SqlDataBinding(List<SqlData> schema, List<dynamic> data)
{
foreach (SqlData item in schema)
{
item.Values = data[schema.IndexOf(item)];
}
return schema
}
Upvotes: 0
Reputation: 5090
I had this same problem but using lists of objects with lists inside of them.. for what its worth, this might help someone with the same issue.
The running time of this isn't very good since IndexOf is O(n), but at the time, I'm dealing with a lot more inner-foreach loops than in this example, so I didn't want to deal with handling iterator variables.
At times like this I very much miss PHPs foreach($arrayList as $key => $value) notation... maybe I'm missing something in C#, there's got to be a way to get the index in O(c) time! (sadly this post says no: Getting the array key in a 'foreach' loop)
class Stock {
string symbol;
List<decimal> hourlyPrice; // provides a list of 24 decimals
}
// get hourly prices from yesterday and today
List<Stock> stockMondays = Stocks.GetStock("GOOGL,IBM,AAPL", DateTime.Now.AddDay(-1));
List<Stock> stockTuesdays = Stocks.GetStock("GOOGL,IBM,AAPL", DateTime.Now);
try {
foreach(Stock sMonday in stockMondays) {
Stock sTuesday = stockTuesday[stockMondays.IndexOf(sMonday)];
foreach(decimal mondayPrice in sMonday.prices) {
decimal tuesdayPrice = sTuesday.prices[sMonday.prices.IndexOf(mondayPrice)];
// do something now
}
}
} catch (Exception ex) { // some reason why list counts aren't matching? }
Upvotes: 1
Reputation: 1064274
[edit]: to clarify; this is useful in the generic LINQ / IEnumerable<T>
context, where you can't use an indexer, because a: it doesn't exist on an enumerable, and b: you can't guarantee that you can read the data more than once. Since the OP mentions lambdas, it occurs that LINQ might not be too far away (and yes, I do realise that LINQ and lambdas are not quite the same thing).
It sounds like you need the missing Zip
operator; you can spoof it:
static void Main()
{
int[] left = { 1, 2, 3, 4, 5 };
string[] right = { "abc", "def", "ghi", "jkl", "mno" };
// using KeyValuePair<,> approach
foreach (var item in left.Zip(right))
{
Console.WriteLine("{0}/{1}", item.Key, item.Value);
}
// using projection approach
foreach (string item in left.Zip(right,
(x,y) => string.Format("{0}/{1}", x, y)))
{
Console.WriteLine(item);
}
}
// library code; written once and stuffed away in a util assembly...
// returns each pais as a KeyValuePair<,>
static IEnumerable<KeyValuePair<TLeft,TRight>> Zip<TLeft, TRight>(
this IEnumerable<TLeft> left, IEnumerable<TRight> right)
{
return Zip(left, right, (x, y) => new KeyValuePair<TLeft, TRight>(x, y));
}
// accepts a projection from the caller for each pair
static IEnumerable<TResult> Zip<TLeft, TRight, TResult>(
this IEnumerable<TLeft> left, IEnumerable<TRight> right,
Func<TLeft, TRight, TResult> selector)
{
using(IEnumerator<TLeft> leftE = left.GetEnumerator())
using (IEnumerator<TRight> rightE = right.GetEnumerator())
{
while (leftE.MoveNext() && rightE.MoveNext())
{
yield return selector(leftE.Current, rightE.Current);
}
}
}
Upvotes: 27
Reputation: 3575
I recommend using plain old for loop, but you should consider different array lengths. So
for(int i=0; i<ListA.Length; i++)
{
Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString());
}
can turn into
for(int i = 0; i < Math.Min(ListA.Length, ListB.Lenght); i++)
{
Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString());
}
or even into
for(int i = 0; i < Math.Max(ListA.Length, ListB.Lenght); i++)
{
string valueA = i < ListA.Length ? listA[i].ToString() : "";
string valueB = i < ListB.Length ? listB[i].ToString() : "";
Console.WriteLine(valueA+ ", " + valueB);
}
Upvotes: 2
Reputation: 86502
Senthil Kumar's tech blog, has a series covering implementations of (Python) Itertools for C#, including itertools.izip
.
From Itertools for C# - Cycle and Zip, you have a solution for any number of iterables (not only List<T>). Note that Zip
yields an Array
on each iteration:
public static IEnumerable<T[]> Zip<T>(params IEnumerable<T>[] iterables)
{
IEnumerator<T>[] enumerators = Array.ConvertAll(iterables, (iterable) => iterable.GetEnumerator());
while (true)
{
int index = 0;
T[] values = new T[enumerators.Length];
foreach (IEnumerator<T> enumerator in enumerators)
{
if (!enumerator.MoveNext())
yield break;
values[index++] = enumerator.Current;
}
yield return values;
}
}
The code gets enumerators for all the iterables, moves all enumerators forward, accumulates their current values into an array and yields the array. It does this until any one of the enumerators runs out of elements.
Upvotes: -1
Reputation:
You can do it explicit.
IEnumerator ListAEnum = ListA.GetEnumerator();
IEnumerator ListBEnum = ListB.GetEnumerator();
ListBEnum.MoveNext();
while(ListAEnum.MoveNext()==true)
{
itemA=ListAEnum.getCurrent();
itemB=ListBEnum.getCurrent();
Console.WriteLine(itemA.ToString()+","+itemB.ToString());
}
At least this (or something like this) is what the compiler does for a foreach-loop. I haven't tested it though and I guess some template parameters are missing for the enumerators.
Just look up GetEnumerator() from List and the IEnumerator-Interface.
Upvotes: 4
Reputation: 1174
It'll be much simpler to just do it in a plain old for loop instead...
for(int i=0; i<ListA.Length; i++)
{
Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString());
}
Upvotes: 13