Iterating Multiple Lists In Order: Part 2
Last week I presented a problem: how do you iterate over multiple lists of multiple types in the order of some common field? For example, how would you iterate over a list of Player
and a list of Enemy
by both of their Health
fields? In that article I showed two solutions to iterate over two lists in this way. What I didn’t show were any solutions to handle more than two lists. What if you needed to also iterate over a list of NPC
? Today’s article discusses how to tackle this problem and ends up with a handy utility class that you can use for your own types no matter how many lists you have. Read on to see how!
In the previous article I showed two solutions for iterating over two lists at once in order. The first used LINQ extension methods to do the job concisely, but slowly. Here it is:
public static void IterateInOrderLinq<A, B, TKey>( IEnumerable<A> aList, IEnumerable<B> bList, Func<A, TKey> aKeyGetter, Func<B, TKey> bKeyGetter, Action<A> aHandler, Action<B> bHandler ) { foreach (var cur in aList .Select(a => new { Key = aKeyGetter(a), HasA = true, A = a, B = default(B) }) .Concat( bList.Select(b => new { Key = bKeyGetter(b), HasA = false, A = default(A), B = b }) ) .OrderBy(x => x.Key)) { if (cur.HasA) { aHandler(cur.A); } else { bHandler(cur.B); } } }
This version is quite inefficient due to the need to sort all the elements of all the lists and the need to allocate an anonymous object for each element in all the lists. That’s a lot of garbage to throw at the garbage collector!
The brevity of this version does, however, allow us to create more versions of this function to handle three lists, four lists, and more. For example, here’s a version that iterates over three lists:
public static void IterateInOrderLinq<A, B, C, TKey>( IEnumerable<A> aList, IEnumerable<B> bList, IEnumerable<C> cList, Func<A, TKey> aKeyGetter, Func<B, TKey> bKeyGetter, Func<C, TKey> cKeyGetter, Action<A> aHandler, Action<B> bHandler, Action<C> bHandler ) { foreach (var cur in aList .Select(a => new { Key = aKeyGetter(a), Type = 0, A = a, B = default(B), C = default(C) }) .Concat( bList.Select(b => new { Key = bKeyGetter(b), Type = 1, A = default(A), B = b, C = default(C) }) ) .Concat( cList.Select(c => new { Key = cKeyGetter(c), Type = 2, A = default(A), B = default(B), C = c }) ) .OrderBy(x => x.Key)) { switch (cur.Type) { case 0: aHandler(cur.A); break; case 1: bHandler(cur.B); break; case 2: cHandler(cur.C); break; } } }
All we had to do was add some extra parameters for the C
type of list, switch the anonymous type’s HasA
bool
to a Type
int
, and use a switch
to find out which type the anonymous type is holding. It’s cookie-cutter at this point, so you can easily imagine adding support for four or more lists.
The real trick comes when you want to get away from LINQ. It’d be really nice to avoid the two main problems here: allocating an object for all the elements of all the lists and sorting all those objects. We also want to keep the nice interface of this LINQ version, so let’s make that a requirement of our alternative solution.
To do this, we’ll be manually using a lot of IEnumerator<T>
so let’s first start with a little helper struct to make using it easier. The main problem with IEnumerator<T>
is that you don’t know if it’s a valid enumerator or not unless you save the result of a call to MoveNext
. We can remedy that by automatically saving the result, which gives us this:
struct EnumeratorWrapper<T> { private readonly IEnumerator<T> enumerator; private bool isValid; public bool IsValid { get { return isValid; } } public T Current { get { return enumerator.Current; } } public EnumeratorWrapper(IEnumerable<T> enumerable) { enumerator = enumerable.GetEnumerator(); isValid = false; MoveNext(); } public T CurrentMoveNext() { var current = Current; MoveNext(); return current; } private void MoveNext() { isValid = enumerator.MoveNext(); } }
This isn’t meant as a general-purpose improvement on IEnumerator<T>
, but feel free to take it and improve it for your own needs if you’d like.
Next, let’s talk about the algorithm to iterate over these multiple lists. In pseudo-code, it goes like this:
while (any list still has elements) { smallest = find the smallest remaining element pass smallest to the appropriate type handler }
To do the “find the smallest remaining element” we’re going to need a function that can loop over those elements. Since the lists are of multiple types, we can’t just put them in a list. Instead, we need to get the field (e.g. Health
) they have in common out of them and put those in a list. If there’s no remaining element, we need to flag this so it’s not taken into account. So let’s make another helper struct for this purpose:
struct CompareVal<T> where T : IComparable<T> { public bool Compare; public T Val; }
This struct simply holds a value to compare and whether it should be compared. The where
clause requires that the value be comparable using IComparable<T>
to other values of its same type.
Finally, we can make the function that finds the smallest element out of a list of CompareVal
:
int FindMinIndex<T>(IList<CompareVal<T>> compareVals) where T : IComparable<T> { var minIndex = -1; var minVal = default(T); var count = compareVals.Count; for (var i = 0; i < count; ++i) { var compareVal = compareVals[i]; if (compareVal.Compare) { minVal = compareVal.Val; minIndex = i; break; } } for (var i = minIndex + 1; i < count; ++i) { var compareVal = compareVals[i]; if (compareVal.Compare && compareVal.Val.CompareTo(minVal) < 0) { minVal = compareVal.Val; minIndex = i; } } return minIndex; }
This function’s first loop finds the first CompareVal
that can be compared. The second loop compares the rest of the values that can be compared against this one. In the end, the index of the smallest value that can be compared is returned.
To put this all together for two lists only, let’s write a function using CompareVal
and FindMinIndex
. I’ve annotated it with a bunch of comments to explain how it works:
// TA and TB are the types of elements in the list (e.g. Player and Enemy) // TCompareVal is the type of the value being compared (e.g. Health is an int) void Iterate<TA, TB, TCompareVal>( // aList and bList are the lists to iterate IEnumerable<TA> aList, IEnumerable<TB> bList, // aGetCompareVal and bGetCompareVal take a TA or TB and return a TCompareVal (e.g. p => p.Health) Func<TA, TCompareVal> aGetCompareVal, Func<TB, TCompareVal> bGetCompareVal, // aHandler and bHandler take a TA or TB and are called in order during iteration Action<TA> aHandler, Action<TB> bHandler ) // The value to compare (e.g. Health) must be comparable where TCompareVal : IComparable<TCompareVal> { // Make an array of the values to compare, one per list var compareVals = new CompareVal<TCompareVal>[2]; // Make a wrapper for each list's enumerator var aE = new EnumeratorWrapper<TA>(aList); var bE = new EnumeratorWrapper<TB>(bList); // Loop while any list's enumerator is still valid while (aE.IsValid || bE.IsValid) { // Set each list's CompareVal.Compare to the EnumeratorWrapper.IsValid // and if the enumerator is still valid set its current value to CompareVal.Val // See below for the full function SetCompareVal(ref compareVals[0], aE, aGetCompareVal); SetCompareVal(ref compareVals[1], bE, bGetCompareVal); // Find the index of the minimum CompareVal switch (FindMinIndex(compareVals)) { // Call the correct handler with the current enumerator value and move next case 0: aHandler(aE.CurrentMoveNext()); break; case 1: bHandler(bE.CurrentMoveNext()); break; } } } // Helper function to set a CompareVal's fields based on an EnumeratorWrapper // TElem is the type of the list's elements (e.g. Player) // TCompareVal is the type of the value to compare (e.g. Health is an int) private void SetCompareVal<TElem, TCompareVal>( // CompareVal whose fields should be set // Note: "ref" to avoid only modifying a copy ref CompareVal<TCompareVal> compareVal, // EnumeratorWrapper to set from EnumeratorWrapper<TElem> enumerator, // Delegate that gets the value to compare from the element Func<TElem, TCompareVal> getVal ) where TCompareVal : IComparable<TCompareVal> { // Only compare if the enumerator is still valid compareVal.Compare = enumerator.IsValid; // Only set the value if the enumerator is still valid if (compareVal.Compare) { // Use the compare value getter delegate to set the value compareVal.Val = getVal(enumerator.Current); } }
The above comments explain how the Iterate
function and its helper work. The SetCompareVal
helper can be reused by all versions of Iterate
and simply provides a concise way to set up the CompareVal
for usage in FindMinIndex
.
Now we’ve achieved a cookie-cutter pattern into which we can add more and more lists. Here’s the full file with the EnumerablesUtil
class and overloaded Iterate
functions for 2-10 lists:
using System; using System.Collections.Generic; /// <summary> /// Holds overloaded Iterate functions for iterating over multiple enumerables of multiple element /// types in the order of a common type of field. For example, these can iterate over a list of /// Player and a list of Enemy in the order of a Health field that they both have. /// </summary> /// <author>Jackson Dunstan, http://JacksonDunstan.com/articles/3423</author> /// <license>MIT</license> public static class EnumerablesUtil { public static void Iterate<TA, TB, TCompareVal>( IEnumerable<TA> aList, IEnumerable<TB> bList, Func<TA, TCompareVal> aGetCompareVal, Func<TB, TCompareVal> bGetCompareVal, Action<TA> aHandler, Action<TB> bHandler ) where TCompareVal : IComparable<TCompareVal> { var compareVals = new CompareVal<TCompareVal>[2]; var aE = new EnumeratorWrapper<TA>(aList); var bE = new EnumeratorWrapper<TB>(bList); while (aE.IsValid || bE.IsValid) { SetCompareVal(ref compareVals[0], aE, aGetCompareVal); SetCompareVal(ref compareVals[1], bE, bGetCompareVal); switch (FindMinIndex(compareVals)) { case 0: aHandler(aE.CurrentMoveNext()); break; case 1: bHandler(bE.CurrentMoveNext()); break; } } } public static void Iterate<TA, TB, TC, TCompareVal>( IEnumerable<TA> aList, IEnumerable<TB> bList, IEnumerable<TC> cList, Func<TA, TCompareVal> aGetCompareVal, Func<TB, TCompareVal> bGetCompareVal, Func<TC, TCompareVal> cGetCompareVal, Action<TA> aHandler, Action<TB> bHandler, Action<TC> cHandler ) where TCompareVal : IComparable<TCompareVal> { var compareVals = new CompareVal<TCompareVal>[3]; var aE = new EnumeratorWrapper<TA>(aList); var bE = new EnumeratorWrapper<TB>(bList); var cE = new EnumeratorWrapper<TC>(cList); while (aE.IsValid || bE.IsValid || cE.IsValid) { SetCompareVal(ref compareVals[0], aE, aGetCompareVal); SetCompareVal(ref compareVals[1], bE, bGetCompareVal); SetCompareVal(ref compareVals[2], cE, cGetCompareVal); switch (FindMinIndex(compareVals)) { case 0: aHandler(aE.CurrentMoveNext()); break; case 1: bHandler(bE.CurrentMoveNext()); break; case 2: cHandler(cE.CurrentMoveNext()); break; } } } public static void Iterate<TA, TB, TC, TD, TCompareVal>( IEnumerable<TA> aList, IEnumerable<TB> bList, IEnumerable<TC> cList, IEnumerable<TD> dList, Func<TA, TCompareVal> aGetCompareVal, Func<TB, TCompareVal> bGetCompareVal, Func<TC, TCompareVal> cGetCompareVal, Func<TD, TCompareVal> dGetCompareVal, Action<TA> aHandler, Action<TB> bHandler, Action<TC> cHandler, Action<TD> dHandler ) where TCompareVal : IComparable<TCompareVal> { var compareVals = new CompareVal<TCompareVal>[4]; var aE = new EnumeratorWrapper<TA>(aList); var bE = new EnumeratorWrapper<TB>(bList); var cE = new EnumeratorWrapper<TC>(cList); var dE = new EnumeratorWrapper<TD>(dList); while (aE.IsValid || bE.IsValid || cE.IsValid || dE.IsValid) { SetCompareVal(ref compareVals[0], aE, aGetCompareVal); SetCompareVal(ref compareVals[1], bE, bGetCompareVal); SetCompareVal(ref compareVals[2], cE, cGetCompareVal); SetCompareVal(ref compareVals[3], dE, dGetCompareVal); switch (FindMinIndex(compareVals)) { case 0: aHandler(aE.CurrentMoveNext()); break; case 1: bHandler(bE.CurrentMoveNext()); break; case 2: cHandler(cE.CurrentMoveNext()); break; case 3: dHandler(dE.CurrentMoveNext()); break; } } } public static void Iterate<TA, TB, TC, TD, TE, TCompareVal>( IEnumerable<TA> aList, IEnumerable<TB> bList, IEnumerable<TC> cList, IEnumerable<TD> dList, IEnumerable<TE> eList, Func<TA, TCompareVal> aGetCompareVal, Func<TB, TCompareVal> bGetCompareVal, Func<TC, TCompareVal> cGetCompareVal, Func<TD, TCompareVal> dGetCompareVal, Func<TE, TCompareVal> eGetCompareVal, Action<TA> aHandler, Action<TB> bHandler, Action<TC> cHandler, Action<TD> dHandler, Action<TE> eHandler ) where TCompareVal : IComparable<TCompareVal> { var compareVals = new CompareVal<TCompareVal>[5]; var aE = new EnumeratorWrapper<TA>(aList); var bE = new EnumeratorWrapper<TB>(bList); var cE = new EnumeratorWrapper<TC>(cList); var dE = new EnumeratorWrapper<TD>(dList); var eE = new EnumeratorWrapper<TE>(eList); while (aE.IsValid || bE.IsValid || cE.IsValid || dE.IsValid || eE.IsValid) { SetCompareVal(ref compareVals[0], aE, aGetCompareVal); SetCompareVal(ref compareVals[1], bE, bGetCompareVal); SetCompareVal(ref compareVals[2], cE, cGetCompareVal); SetCompareVal(ref compareVals[3], dE, dGetCompareVal); SetCompareVal(ref compareVals[4], eE, eGetCompareVal); switch (FindMinIndex(compareVals)) { case 0: aHandler(aE.CurrentMoveNext()); break; case 1: bHandler(bE.CurrentMoveNext()); break; case 2: cHandler(cE.CurrentMoveNext()); break; case 3: dHandler(dE.CurrentMoveNext()); break; case 4: eHandler(eE.CurrentMoveNext()); break; } } } public static void Iterate<TA, TB, TC, TD, TE, TF, TCompareVal>( IEnumerable<TA> aList, IEnumerable<TB> bList, IEnumerable<TC> cList, IEnumerable<TD> dList, IEnumerable<TE> eList, IEnumerable<TF> fList, Func<TA, TCompareVal> aGetCompareVal, Func<TB, TCompareVal> bGetCompareVal, Func<TC, TCompareVal> cGetCompareVal, Func<TD, TCompareVal> dGetCompareVal, Func<TE, TCompareVal> eGetCompareVal, Func<TF, TCompareVal> fGetCompareVal, Action<TA> aHandler, Action<TB> bHandler, Action<TC> cHandler, Action<TD> dHandler, Action<TE> eHandler, Action<TF> fHandler ) where TCompareVal : IComparable<TCompareVal> { var compareVals = new CompareVal<TCompareVal>[6]; var aE = new EnumeratorWrapper<TA>(aList); var bE = new EnumeratorWrapper<TB>(bList); var cE = new EnumeratorWrapper<TC>(cList); var dE = new EnumeratorWrapper<TD>(dList); var eE = new EnumeratorWrapper<TE>(eList); var fE = new EnumeratorWrapper<TF>(fList); while (aE.IsValid || bE.IsValid || cE.IsValid || dE.IsValid || eE.IsValid || fE.IsValid) { SetCompareVal(ref compareVals[0], aE, aGetCompareVal); SetCompareVal(ref compareVals[1], bE, bGetCompareVal); SetCompareVal(ref compareVals[2], cE, cGetCompareVal); SetCompareVal(ref compareVals[3], dE, dGetCompareVal); SetCompareVal(ref compareVals[4], eE, eGetCompareVal); SetCompareVal(ref compareVals[5], fE, fGetCompareVal); switch (FindMinIndex(compareVals)) { case 0: aHandler(aE.CurrentMoveNext()); break; case 1: bHandler(bE.CurrentMoveNext()); break; case 2: cHandler(cE.CurrentMoveNext()); break; case 3: dHandler(dE.CurrentMoveNext()); break; case 4: eHandler(eE.CurrentMoveNext()); break; case 5: fHandler(fE.CurrentMoveNext()); break; } } } public static void Iterate<TA, TB, TC, TD, TE, TF, TG, TCompareVal>( IEnumerable<TA> aList, IEnumerable<TB> bList, IEnumerable<TC> cList, IEnumerable<TD> dList, IEnumerable<TE> eList, IEnumerable<TF> fList, IEnumerable<TG> gList, Func<TA, TCompareVal> aGetCompareVal, Func<TB, TCompareVal> bGetCompareVal, Func<TC, TCompareVal> cGetCompareVal, Func<TD, TCompareVal> dGetCompareVal, Func<TE, TCompareVal> eGetCompareVal, Func<TF, TCompareVal> fGetCompareVal, Func<TG, TCompareVal> gGetCompareVal, Action<TA> aHandler, Action<TB> bHandler, Action<TC> cHandler, Action<TD> dHandler, Action<TE> eHandler, Action<TF> fHandler, Action<TG> gHandler ) where TCompareVal : IComparable<TCompareVal> { var compareVals = new CompareVal<TCompareVal>[7]; var aE = new EnumeratorWrapper<TA>(aList); var bE = new EnumeratorWrapper<TB>(bList); var cE = new EnumeratorWrapper<TC>(cList); var dE = new EnumeratorWrapper<TD>(dList); var eE = new EnumeratorWrapper<TE>(eList); var fE = new EnumeratorWrapper<TF>(fList); var gE = new EnumeratorWrapper<TG>(gList); while ( aE.IsValid || bE.IsValid || cE.IsValid || dE.IsValid || eE.IsValid || fE.IsValid || gE.IsValid ) { SetCompareVal(ref compareVals[0], aE, aGetCompareVal); SetCompareVal(ref compareVals[1], bE, bGetCompareVal); SetCompareVal(ref compareVals[2], cE, cGetCompareVal); SetCompareVal(ref compareVals[3], dE, dGetCompareVal); SetCompareVal(ref compareVals[4], eE, eGetCompareVal); SetCompareVal(ref compareVals[5], fE, fGetCompareVal); SetCompareVal(ref compareVals[6], gE, gGetCompareVal); switch (FindMinIndex(compareVals)) { case 0: aHandler(aE.CurrentMoveNext()); break; case 1: bHandler(bE.CurrentMoveNext()); break; case 2: cHandler(cE.CurrentMoveNext()); break; case 3: dHandler(dE.CurrentMoveNext()); break; case 4: eHandler(eE.CurrentMoveNext()); break; case 5: fHandler(fE.CurrentMoveNext()); break; case 6: gHandler(gE.CurrentMoveNext()); break; } } } public static void Iterate<TA, TB, TC, TD, TE, TF, TG, TH, TCompareVal>( IEnumerable<TA> aList, IEnumerable<TB> bList, IEnumerable<TC> cList, IEnumerable<TD> dList, IEnumerable<TE> eList, IEnumerable<TF> fList, IEnumerable<TG> gList, IEnumerable<TH> hList, Func<TA, TCompareVal> aGetCompareVal, Func<TB, TCompareVal> bGetCompareVal, Func<TC, TCompareVal> cGetCompareVal, Func<TD, TCompareVal> dGetCompareVal, Func<TE, TCompareVal> eGetCompareVal, Func<TF, TCompareVal> fGetCompareVal, Func<TG, TCompareVal> gGetCompareVal, Func<TH, TCompareVal> hGetCompareVal, Action<TA> aHandler, Action<TB> bHandler, Action<TC> cHandler, Action<TD> dHandler, Action<TE> eHandler, Action<TF> fHandler, Action<TG> gHandler, Action<TH> hHandler ) where TCompareVal : IComparable<TCompareVal> { var compareVals = new CompareVal<TCompareVal>[8]; var aE = new EnumeratorWrapper<TA>(aList); var bE = new EnumeratorWrapper<TB>(bList); var cE = new EnumeratorWrapper<TC>(cList); var dE = new EnumeratorWrapper<TD>(dList); var eE = new EnumeratorWrapper<TE>(eList); var fE = new EnumeratorWrapper<TF>(fList); var gE = new EnumeratorWrapper<TG>(gList); var hE = new EnumeratorWrapper<TH>(hList); while ( aE.IsValid || bE.IsValid || cE.IsValid || dE.IsValid || eE.IsValid || fE.IsValid || gE.IsValid || hE.IsValid ) { SetCompareVal(ref compareVals[0], aE, aGetCompareVal); SetCompareVal(ref compareVals[1], bE, bGetCompareVal); SetCompareVal(ref compareVals[2], cE, cGetCompareVal); SetCompareVal(ref compareVals[3], dE, dGetCompareVal); SetCompareVal(ref compareVals[4], eE, eGetCompareVal); SetCompareVal(ref compareVals[5], fE, fGetCompareVal); SetCompareVal(ref compareVals[6], gE, gGetCompareVal); SetCompareVal(ref compareVals[7], hE, hGetCompareVal); switch (FindMinIndex(compareVals)) { case 0: aHandler(aE.CurrentMoveNext()); break; case 1: bHandler(bE.CurrentMoveNext()); break; case 2: cHandler(cE.CurrentMoveNext()); break; case 3: dHandler(dE.CurrentMoveNext()); break; case 4: eHandler(eE.CurrentMoveNext()); break; case 5: fHandler(fE.CurrentMoveNext()); break; case 6: gHandler(gE.CurrentMoveNext()); break; case 7: hHandler(hE.CurrentMoveNext()); break; } } } public static void Iterate<TA, TB, TC, TD, TE, TF, TG, TH, TI, TCompareVal>( IEnumerable<TA> aList, IEnumerable<TB> bList, IEnumerable<TC> cList, IEnumerable<TD> dList, IEnumerable<TE> eList, IEnumerable<TF> fList, IEnumerable<TG> gList, IEnumerable<TH> hList, IEnumerable<TI> iList, Func<TA, TCompareVal> aGetCompareVal, Func<TB, TCompareVal> bGetCompareVal, Func<TC, TCompareVal> cGetCompareVal, Func<TD, TCompareVal> dGetCompareVal, Func<TE, TCompareVal> eGetCompareVal, Func<TF, TCompareVal> fGetCompareVal, Func<TG, TCompareVal> gGetCompareVal, Func<TH, TCompareVal> hGetCompareVal, Func<TI, TCompareVal> iGetCompareVal, Action<TA> aHandler, Action<TB> bHandler, Action<TC> cHandler, Action<TD> dHandler, Action<TE> eHandler, Action<TF> fHandler, Action<TG> gHandler, Action<TH> hHandler, Action<TI> iHandler ) where TCompareVal : IComparable<TCompareVal> { var compareVals = new CompareVal<TCompareVal>[9]; var aE = new EnumeratorWrapper<TA>(aList); var bE = new EnumeratorWrapper<TB>(bList); var cE = new EnumeratorWrapper<TC>(cList); var dE = new EnumeratorWrapper<TD>(dList); var eE = new EnumeratorWrapper<TE>(eList); var fE = new EnumeratorWrapper<TF>(fList); var gE = new EnumeratorWrapper<TG>(gList); var hE = new EnumeratorWrapper<TH>(hList); var iE = new EnumeratorWrapper<TI>(iList); while ( aE.IsValid || bE.IsValid || cE.IsValid || dE.IsValid || eE.IsValid || fE.IsValid || gE.IsValid || hE.IsValid || iE.IsValid ) { SetCompareVal(ref compareVals[0], aE, aGetCompareVal); SetCompareVal(ref compareVals[1], bE, bGetCompareVal); SetCompareVal(ref compareVals[2], cE, cGetCompareVal); SetCompareVal(ref compareVals[3], dE, dGetCompareVal); SetCompareVal(ref compareVals[4], eE, eGetCompareVal); SetCompareVal(ref compareVals[5], fE, fGetCompareVal); SetCompareVal(ref compareVals[6], gE, gGetCompareVal); SetCompareVal(ref compareVals[7], hE, hGetCompareVal); SetCompareVal(ref compareVals[8], iE, iGetCompareVal); switch (FindMinIndex(compareVals)) { case 0: aHandler(aE.CurrentMoveNext()); break; case 1: bHandler(bE.CurrentMoveNext()); break; case 2: cHandler(cE.CurrentMoveNext()); break; case 3: dHandler(dE.CurrentMoveNext()); break; case 4: eHandler(eE.CurrentMoveNext()); break; case 5: fHandler(fE.CurrentMoveNext()); break; case 6: gHandler(gE.CurrentMoveNext()); break; case 7: hHandler(hE.CurrentMoveNext()); break; case 8: iHandler(iE.CurrentMoveNext()); break; } } } public static void Iterate<TA, TB, TC, TD, TE, TF, TG, TH, TI, TJ, TCompareVal>( IEnumerable<TA> aList, IEnumerable<TB> bList, IEnumerable<TC> cList, IEnumerable<TD> dList, IEnumerable<TE> eList, IEnumerable<TF> fList, IEnumerable<TG> gList, IEnumerable<TH> hList, IEnumerable<TI> iList, IEnumerable<TJ> jList, Func<TA, TCompareVal> aGetCompareVal, Func<TB, TCompareVal> bGetCompareVal, Func<TC, TCompareVal> cGetCompareVal, Func<TD, TCompareVal> dGetCompareVal, Func<TE, TCompareVal> eGetCompareVal, Func<TF, TCompareVal> fGetCompareVal, Func<TG, TCompareVal> gGetCompareVal, Func<TH, TCompareVal> hGetCompareVal, Func<TI, TCompareVal> iGetCompareVal, Func<TJ, TCompareVal> jGetCompareVal, Action<TA> aHandler, Action<TB> bHandler, Action<TC> cHandler, Action<TD> dHandler, Action<TE> eHandler, Action<TF> fHandler, Action<TG> gHandler, Action<TH> hHandler, Action<TI> iHandler, Action<TJ> jHandler ) where TCompareVal : IComparable<TCompareVal> { var compareVals = new CompareVal<TCompareVal>[10]; var aE = new EnumeratorWrapper<TA>(aList); var bE = new EnumeratorWrapper<TB>(bList); var cE = new EnumeratorWrapper<TC>(cList); var dE = new EnumeratorWrapper<TD>(dList); var eE = new EnumeratorWrapper<TE>(eList); var fE = new EnumeratorWrapper<TF>(fList); var gE = new EnumeratorWrapper<TG>(gList); var hE = new EnumeratorWrapper<TH>(hList); var iE = new EnumeratorWrapper<TI>(iList); var jE = new EnumeratorWrapper<TJ>(jList); while ( aE.IsValid || bE.IsValid || cE.IsValid || dE.IsValid || eE.IsValid || fE.IsValid || gE.IsValid || hE.IsValid || iE.IsValid || jE.IsValid ) { SetCompareVal(ref compareVals[0], aE, aGetCompareVal); SetCompareVal(ref compareVals[1], bE, bGetCompareVal); SetCompareVal(ref compareVals[2], cE, cGetCompareVal); SetCompareVal(ref compareVals[3], dE, dGetCompareVal); SetCompareVal(ref compareVals[4], eE, eGetCompareVal); SetCompareVal(ref compareVals[5], fE, fGetCompareVal); SetCompareVal(ref compareVals[6], gE, gGetCompareVal); SetCompareVal(ref compareVals[7], hE, hGetCompareVal); SetCompareVal(ref compareVals[8], iE, iGetCompareVal); SetCompareVal(ref compareVals[9], jE, jGetCompareVal); switch (FindMinIndex(compareVals)) { case 0: aHandler(aE.CurrentMoveNext()); break; case 1: bHandler(bE.CurrentMoveNext()); break; case 2: cHandler(cE.CurrentMoveNext()); break; case 3: dHandler(dE.CurrentMoveNext()); break; case 4: eHandler(eE.CurrentMoveNext()); break; case 5: fHandler(fE.CurrentMoveNext()); break; case 6: gHandler(gE.CurrentMoveNext()); break; case 7: hHandler(hE.CurrentMoveNext()); break; case 8: iHandler(iE.CurrentMoveNext()); break; case 9: jHandler(jE.CurrentMoveNext()); break; } } } private static void SetCompareVal<TElem, TCompareVal>( ref CompareVal<TCompareVal> compareVal, EnumeratorWrapper<TElem> enumerator, Func<TElem, TCompareVal> getVal ) where TCompareVal : IComparable<TCompareVal> { compareVal.Compare = enumerator.IsValid; if (compareVal.Compare) { compareVal.Val = getVal(enumerator.Current); } } private struct EnumeratorWrapper<T> { private readonly IEnumerator<T> enumerator; private bool isValid; public bool IsValid { get { return isValid; } } public T Current { get { return enumerator.Current; } } public EnumeratorWrapper(IEnumerable<T> enumerable) { enumerator = enumerable.GetEnumerator(); isValid = false; MoveNext(); } public T CurrentMoveNext() { var current = Current; MoveNext(); return current; } private void MoveNext() { isValid = enumerator.MoveNext(); } } private struct CompareVal<T> where T : IComparable<T> { public bool Compare; public T Val; } private static int FindMinIndex<T>(IList<CompareVal<T>> compareVals) where T : IComparable<T> { var minIndex = -1; var minVal = default(T); var count = compareVals.Count; for (var i = 0; i < count; ++i) { var compareVal = compareVals[i]; if (compareVal.Compare) { minVal = compareVal.Val; minIndex = i; break; } } for (var i = minIndex + 1; i < count; ++i) { var compareVal = compareVals[i]; if (compareVal.Compare && compareVal.Val.CompareTo(minVal) < 0) { minVal = compareVal.Val; minIndex = i; } } return minIndex; } }
To use this, let’s look at a tiny example that iterates over two lists just like in the last article:
void Iterate(Game game) { EnumerableUtils.Iterate( game.Players, game.Enemies, p => p.Health, e => e.Health, p => Debug.Log("player has " + p.XP + " XP"), e => Debug.Log("enemy is worth " + e.WorthXP + " XP") ); }
That’s it! It’s easy to use with any IEnumerable<T>
(not just lists), with any element type in those enumerables, with any field type regardless of name, and with up to 10 lists.
Hopefully you’ll find EnumerablesUtil.Iterate
useful, particularly for the purposes of avoiding error-prone runtime type checking and casting as mentioned in the last article. If you do or you’ve tackled this problem another way, drop me a line in the comments!