C#: Combine Two Values Together From a List Into Pairs

Posted by OmegaMan at December 6, 2012

Category: C#, Code Utilities, Extension Methods

Tags:

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.

Share

10 Comments

  1. Omer Mor says

    It can be done also in Linq:

    var xs = new int[] {1,2,3,4,5,6};
    
    var odds = xs.Where((v,i) => i%2 == 0);
    var evens = xs.Where((v,i) => i%2 == 1);
    
    var tuples = odds.Zip(evens, (o,e) => Tuple.Create(o,e));
    
    Reply
    • OmegaMan says

      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. 🙂

      Reply
  2. Oded says

    With LINQ you can achieve the same using the Zip extension method:

    http://msdn.microsoft.com/en-us/library/dd267698.aspx

    Reply
  3. Larry Smith says

    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

    Reply
  4. B. Clay Shannon says

    Awesome; this will come in very handy.

    Reply
  5. Bill Sorensen says

    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.

    Reply
    • OmegaMan says

      Excellent suggestion on the Count! Thanks!

      Reply
  6. James Curran says

    This is a bit simpler, and doesn’t require the input to be a list:

    public static IEnumerable<Tuple> AsPairs(this IEnumerable list, bool safe=false)
    {
    	bool haveFirst = false;
    	T firstT = default(T);
    	
    	foreach(var t in list)
    	{
    		if (haveFirst)
    		{
    			yield return new Tuple(firstT, t);
    			haveFirst = false;
    		}
    		else
    		{
    		    firstT = t;
    		    haveFirst = true;
    		}
    	}
    	
    	if (haveFirst && !safe)
    			yield return new Tuple(firstT, default(T));
    }
    
    public static IEnumerable<Tuple> AsPairsSafe(this IEnumerable list)
    { return AsPairs(list, true); }
    
    Reply
  7. James Curran says

    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…..

    Reply
    • OmegaMan says

      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.

      Reply

Leave a comment

(required)
(required) (will not be published)

This site uses Akismet to reduce spam. Learn how your comment data is processed.