C#: Combine Two Values Together From a List Into Pairs
Sometimes in C# .Net (see Notes section on usage for before .Net 4) one might have a list of items and want to make them into pairs. Possibly to take those pairs and place the them into a dictionary. If the items are in a list, that list is linear by nature and using linq is not an option when using the extension ToDictionary.
I have created extension methods to create paired values from any list and those methods are named AsPairs and AsPairsSafe. If the list is odd in length, the final number will be combined with the system default for that type as handled in the method AsPairs. If that is not desired then call AsPairsSafe which will skip the last odd value.
Here is the usage of the AsPairs extension on integers and strings:
var ints = new List<int> { 1, 2, 3, 4, 5 }; IEnumerable<Tuple<int,int>> asTuplePairs = ints.AsPairs(); /* asTuplePairs looks like this(Note value 5 is paired with a default value of 0) 1,2 3,4 5,0 */ var strings = new List<string> { "Alpha", "Beta", "Gamma", "Delta", "Omega" }; IEnumerable<Tuple<string,string>> asTuplePairsStrings = strings.AsPairs(); /* asTuplePairsStrings (note Omega is paired a default value of Null) Alpha, Beta Gamma, Delta Omega, NULL Whereas the call to AsPairsSafe would return without Omega: Alpha, Beta Gamma, Delta */
Here are the extension methods:
public static class MyExtensions { // Create Pairs from a list. If the list is odd add a default value for the final pair. public static IEnumerable<Tuple<T, T>> AsPairs<T>(this List<T> list) { int index = 0; while (index < list.Count()) { if (index + 1 > list.Count()) yield break; if (index + 1 == list.Count()) yield return new Tuple<T,T>(list[index++], default(T)); else yield return new Tuple<T,T>(list[index++], list[index++]); } } // Create Pairs from a list. Note if the list is not even in count, the last value is skipped. public static IEnumerable<Tuple<T, T>> AsPairsSafe<T>(this List<T> list) { int index = 0; while (index < list.Count()) { if (index + 1 >= list.Count()) yield break; yield return new Tuple<T,T>(list[index++], list[index++]); } } }
Notes
Tuple is a .Net 4 item. If you are using a previous version of .Net use the KeyValuePair structure instead.
It can be done also in Linq:
Thanks Omer & Oded & Larry!
But zip cannot default a value if the list is uneven, it just skips. So in your (Omer’s) example if one adds a 7 to the list, the result of tuples is without the items. Regardless, I will update this post to reflect the zip method. :-)
With LINQ you can achieve the same using the Zip extension method:
http://msdn.microsoft.com/en-us/library/dd267698.aspx
LINQ already has pretty much the same ability built-in. It’s the Zip (as in zipper, not as in data compression) method.
See http://msdn.microsoft.com/en-us/library/dd267698.aspx
Awesome; this will come in very handy.
Since you’re typing the parameter as a list, it’s more efficient to use the Count property instead of the Count() LINQ extension method. See http://stackoverflow.com/a/985279/161457 for the timings. Since you’re evaluating this repeatedly in a loop, the overhead will multiply.
Excellent suggestion on the Count! Thanks!
This is a bit simpler, and doesn’t require the input to be a list:
Wow… THREE people suggest using Zip() despite the fact that Zip() does something completely different. And, apparently only one of those three seems to realize that you have to preprocess the data to be able to get Zip to do what we want. And even he doesn’t seem to realize that all that preprocessing makes it a worse solution…..
Thanks for your insights James and your example. Due to the response I have gotten, I am going to update this post to include the topics you mentioned as well as incorporating your example.