views:

827

answers:

6

I happen to use this kind of structure quite a lot:

Dictionary<string, List<string>> Foo = new Dictionary<string, List<string>>();

Which leads to this kind of code :

foreach (DataRow dr in ds.Tables[0].Rows)
{
    List<string> bar;
    if (!Foo.TryGetValue(dr["Key"].ToString(), out desks))
    {
        bar= new List<string>();
        Foo.Add(dr["Key"].ToString(), bar);
    }
    bar.Add(dr["Value"].ToString());
}

Do you think it's worth writing a custom DictionaryOfList class which would handle this kind of things automatically?

Is there another way to lazily initialize those Lists?

+7  A: 

You can write an extension method - GetValueOrCreateDefault() or something like that:

foreach (DataRow dr in ds.Tables[0].Rows)
{
    Foo.GetValueOrCreateDefault( dr["Key"] ).Add( dr["Value"].ToString() )
}

Maybe you can even write an extension method for the whole initialisation?

tanascius
Nice solution, but I would go further and create an extension method Dictionary<TKey,TValue>.AddPair(TKey, TValue). This will be much easier to read and understand if someone else has to look over the code.
m0rb
+2  A: 

I think the following should do:

class DictionaryOfList : Dictionary<string, List<string>> {}
  • Edit I should read more properly. This does not answer the question. Tanascius has supplied a neat way to solve it.
Kevin
Thanks, that's handy!
iik
+3  A: 

A dictionary of a list... in .NET 3.5 that would be an ILookup<TKey,TValue>. The default implementation (Lookup<TKey,TValue>) is immutable, but I wrote an EditableLookup<TKey,TValue> for MiscUtil. This will be a lot simpler to use - i.e.

var data = new EditableLookup<string, int>();
data.Add("abc",123);
data.Add("def",456);
data.Add("abc",789);

foreach(int i in data["abc"]) {
    Console.WriteLine(i); // 123 & 789
}

Other than that, an extension method:

public static void Add<TKey, TList, TValue>(
    this IDictionary<TKey, TList> lookup,
    TKey key, TValue value)
    where TList : class, ICollection<TValue>, new()
{
    TList list;
    if (!lookup.TryGetValue(key, out list))
    {
        lookup.Add(key, list = new TList());
    }
    list.Add(value);
}

static void Main() {
    var data = new Dictionary<string, List<string>>();
    data.Add("abc", "def");
}
Marc Gravell
Nice! I <4 extension methods.
Matt Hamilton
Indeed nice, although I'm still not really convinced when it comes to maintainability and obscure stuff that can be done with it. For example, implementing default implementations for interfaces using extension methods.But in this case very handy!
Kevin
+1  A: 

Add a reference to System.Data.DataSetExtensions and you can use the Linq extensions:

var dictOfLst = ds.Tables[0].Rows.
    //group by the key field
    GroupBy( dr => dr.Field<string>("key") ).
    ToDictionary(
        grp => grp.Key,
        //convert the collection of rows into values
        grp => grp.Select( dr => dr.Field<string>("value") ).ToList() );

I'm not sure I'd bother with another class, but a utility or extension method could make this simpler:

public static Dictionary<TKey, List<TValue>> ToGroupedDictionary<TKey, List<TValue>>(
    this DataTable input, 
    Func<TKey, DataRow> keyConverter, 
    Func<TValue, DataRow> valueConverter )
{
    return input.Rows.
        //group by the key field
        GroupBy( keyConverter ).
        ToDictionary(
            grp => grp.Key,
            //convert the collection of rows into values
            grp => grp.Select( valueConverter ).ToList() );
}

//now you have a simpler syntax
var dictOfLst = ds.Tables[0].ToGroupedDictionary(
    dr => dr.Field<string>("key"),
    dr => dr.Field<string>("value") );
Keith
Have you see ToLookup?
Marc Gravell
A: 

Don't forget the using directive.

This isn't directly responsive, but may be helpful anyway. A "using alias" for a generic collection type may make your code easier on the eyes.

using StoreBox = System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<string>>; 
using ListOfStrings = System.Collections.Generic.List<string>; 
class Program
{
    static void Main(string[] args)
    {
      var b = new StoreBox ();
      b.Add("Red", new ListOfStrings {"Rosso", "red" });
      b.Add("Green", new ListOfStrings {"Verde", "green" });
    }
}

Credit to SO for this hint.

Cheeso
A: 

Why not just simplify a little:

foreach (DataRow dr in ds.Tables[0].Rows)
{
   string key = dr["Key"].ToString();
   if (!Foo.ContainsKey(key)) Foo.Add(key, new List<string>());
   Foo[key].Add(dr["Value"].ToString());
}
Robin
A reason not to do that is that the complexity would be O(2n) instead of O(n). Of course in most cases, that's probably not really an issue.
Brann