ILookup

There are many times where I need to convert an array of data into a Dictionary or Hash table.  If the key to be used in the new Dictionary is not unique, it is necessary to manage duplicates.  I previously went through a lot of gyrations, but eventually setteled on the ILookup.  ILookup is not perfect, but it beats some alternatives.

Note this example (given the following structure):

public static List<string[]> lst_arr_strData = new List<string[]>()
{
   new string[] {"fred", "1"},
   new string[] {"joe", "1"},
   new string[] {"bill", "1"},
   new string[] {"sam", "1"},
   new string[] {"dave", "1"},
   new string[] {"fred", "2"},
   new string[] {"joe", "2"}
};



 

Technique 0: Throw an Exception : Argument Exception : Message="An item with the same key has already been added."

Dictionary Picture Causing Exception
Argument Exception Picture


Technique 1: Ignore/discard duplicates
Picture showing how to eliminate duplicates
 


Technique 2: Add the duplicate data as an Enumerable type
   Example: Dictionary<TKey, List<TValue>>

Picture of code showing conversion of duplicates to a List in the Dictionary


ToLookup converts an IEnumerable<T> to a Lookup<Key, Element> type.  Lookup is like a dictionary, but where a Dictionary uses a single key value, Lookup maps the keys to a collection of values.  Lookups have no public constructor and are immutable.  You cannot add or remove elements or keys after they are created.

Technique 3: Convert the data using the Enumerable.ToLookup

ILookup<TKey, TElement>
http://msdn.microsoft.com/en-us/library/bb549073.aspx

In this example, I show the calling code and the ILookup solution in C++, C# and VB

////////////////////////////////////////////////////////////////////////////////
// CSharp Main program
using System;
using System.Collections.Generic;
using System.Linq;
using LinqTestCPP;
using LinqTestCS;
using LinqTestVB;
//
namespace LinqTestShell
{
   class LinqTest
   {
      //////////////////////////////////////////////////////////////////////////
      // here is some static data to convert to the ILookup
      public static List<string[]> lst_arr_strData = new List<string[]>()
      {
         new string[] {"fred", "1"},
         new string[] {"joe", "1"},
         new string[] {"bill", "1"},
         new string[] {"sam", "1"},
         new string[] {"dave", "1"},
         new string[] {"fred", "2"},
         new string[] {"joe", "2"}
      };

      static void Main(string[] args)
      {
         // Interfaces will/should look exactly the same.
         Console.WriteLine("---  C++   ---");
         CLinqTestCPP.LinqTest(lst_arr_strData);

         Console.WriteLine("--- CSharp ---");
         CLinqTestCS.LinqTest(lst_arr_strData);

         Console.WriteLine("---   VB   ---");
         CLinqTestVB.LinqTest(lst_arr_strData);
      }
   }
}


C++ Example:

// CPP implementation
#pragma once
using
namespace System;
using namespace System::Collections::Generic;
using namespace System::Linq;

namespace LinqTestCPP {

       public ref class CLinqTestCPP
       {
       protected:
              //
              static String^ GetFirst(array<String^>^ arr){
                     return Enumerable::First<String^>(arr);
              }
      
       public:
              //
              static void LinqTest(List<array<String^>^>^ lst_arr_strData)
              {
                     ILookup<String^, array<String^>^>^ lkup =
                           Enumerable::ToLookup(lst_arr_strData,
                                  gcnew Func<array<String^>^, String^>(GetFirst)
                                  );

                     for each (IGrouping<String^, array<String^>^>^ arr in lkup)
                     {
                           Console::Write(arr->Key);
                          
                           for each (array<String^>^ s in arr)
                           {
                                  Console::Write("\t" + s[1]);
                           }

                           Console::WriteLine();
                     }
              }
       };
}


C# (CSharp) Example:

////////////////////////////////////////////////////////////////////////////////
// CSharp implementation
using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqTestCS
{
   public class CLinqTestCS
   {
      public static void LinqTest(List<string[]> lst_arr_strData)
      {
         // (using Extension)
         /* ILookup<string, string[]> lkup =
               lst_arr_strData.ToLookup(key => key.First()); */

         ILookup<string, string[]> lkup =
            Enumerable.ToLookup(lst_arr_strData,
               new Func<string[], String>(Enumerable.First)
               );
        
         foreach (IGrouping<string, string[]> arr in lkup)
         {
            Console.Write(arr.Key);
            foreach (var s in arr)
            {
               Console.Write("\t" + s[1]);
            }

            Console.WriteLine();
         }
      }
   }
}


 VB Example:

' //////////////////////////////////////////////////////////////////////////////
' // VB Implementation
Imports System.Linq

Public Class CLinqTestVB

   Public Shared Sub LinqTest(ByVal lst_arr_strData As List(Of String()))
      ' (using Extension)
      ' Dim lkup As ILookup(Of String, String()) = _
      '  lst_arr_strData.ToLookup(Function(key) key.First())

      Dim lkup As ILookup(Of String, String()) = _
         Enumerable.ToLookup(lst_arr_strData, _
            New Func(Of String(), String)(AddressOf Enumerable.First))

      For Each arr As IGrouping(Of String, String()) In lkup
         Console.Write(arr.Key)

         For Each s As String() In arr
            Console.Write(Chr(9) + s(1))
         Next

         Console.WriteLine()
      Next

   End Sub
End
Class



 :and the output...:

Picture of output

posted @ Friday, February 12, 2010 5:37 PM
Print

Comments on this entry:

No comments posted yet.

Your comment:



(not displayed)

 
 
 
 

Live Comment Preview: