Elegant way to create a nested Dictionary in C#

前端 未结 8 1809
深忆病人
深忆病人 2021-01-31 04:57

I realized that I didn\'t give enough information for most people to read my mind and understand all my needs, so I changed this somewhat from the original.

相关标签:
8条回答
  • 2021-01-31 05:19

    Another approach would be to key your dictionary using an anonymous type based on both the Foo and Bar values.

    var things = new List<Thing>
                     {
                         new Thing {Foo = 3, Bar = 4, Baz = "quick"},
                         new Thing {Foo = 3, Bar = 8, Baz = "brown"},
                         new Thing {Foo = 6, Bar = 4, Baz = "fox"},
                         new Thing {Foo = 6, Bar = 8, Baz = "jumps"}
                     };
    var dict = things.ToDictionary(thing => new {thing.Foo, thing.Bar},
                                   thing => thing.Baz);
    var baz = dict[new {Foo = 3, Bar = 4}];
    

    This effectively flattens your hierarchy into a single dictionary. Note that this dictionary cannot be exposed externally since it is based on an anonymous type.

    If the Foo and Bar value combination isn't unique in your original collection, then you would need to group them first.

    var dict = things
        .GroupBy(thing => new {thing.Foo, thing.Bar})
        .ToDictionary(group => group.Key,
                      group => group.Select(thing => thing.Baz));
    var bazes = dict[new {Foo = 3, Bar = 4}];
    foreach (var baz in bazes)
    {
        //...
    }
    
    0 讨论(0)
  • 2021-01-31 05:20

    Here's a solution using Linq:

    Dictionary<int, Dictionary<int, string>> dict = things
        .GroupBy(thing => thing.Foo)
        .ToDictionary(fooGroup => fooGroup.Key,
                      fooGroup => fooGroup.ToDictionary(thing => thing.Bar,
                                                        thing => thing.Baz));
    
    0 讨论(0)
  • An elegant way would be to not create the dictionaries yourself but use LINQ GroupBy and ToDictionary to generate it for you.

    var things = new[] {
        new Thing { Foo = 1, Bar = 2, Baz = "ONETWO!" },
        new Thing { Foo = 1, Bar = 3, Baz = "ONETHREE!" },
        new Thing { Foo = 1, Bar = 2, Baz = "ONETWO!" }
    }.ToList();
    
    var bazGroups = things
        .GroupBy(t => t.Foo)
        .ToDictionary(gFoo => gFoo.Key, gFoo => gFoo
            .GroupBy(t => t.Bar)
            .ToDictionary(gBar => gBar.Key, gBar => gBar.First().Baz));
    
    Debug.Fail("Inspect the bazGroups variable.");
    

    I assume that by categorizing Baz using Foo and Bar you mean that if two things have both Foo and Bar equals then their Baz value also be the same as well. Please correct me if I'm wrong.

    You're basically group by the Foo property first...
    then for each resulting group, you group on the Bar property...
    then for each resulting group you take the first Baz value as the dictionary value.

    If you noticed, the method names matched exactly what you are trying to do. :-)


    EDIT: Here's another way using query comprehensions, they are longer but are quiet easier to read and grok:

    var bazGroups =
        (from t1 in things
         group t1 by t1.Foo into gFoo
         select new
         {
             Key = gFoo.Key,
             Value = (from t2 in gFoo
                      group t2 by t2.Bar into gBar
                      select gBar)
                      .ToDictionary(g => g.Key, g => g.First().Baz)
         })
         .ToDictionary(g => g.Key, g => g.Value);
    

    Unfortunately, there are no query comprehension counterpart for ToDictionary so it's not as elegant as the lambda expressions.

    ...

    Hope this helps.

    0 讨论(0)
  • 2021-01-31 05:33
    Dictionary<int, Dictionary<string, int>> nestedDictionary = 
                new Dictionary<int, Dictionary<string, int>>();
    
    0 讨论(0)
  • 2021-01-31 05:36

    Use BeanMap's two key Map class. There is also a 3 key map, and it is quite extensible in case you need n keys.

    http://beanmap.codeplex.com/

    Your solution would then look like:

    class Thing
    {
      public int Foo { get; set; }
      public int Bar { get; set; }
      public string Baz { get; set; }
    }
    
    [TestMethod]
    public void ListToMapTest()
    {
      var things = new List<Thing>
                 {
                     new Thing {Foo = 3, Bar = 3, Baz = "quick"},
                     new Thing {Foo = 3, Bar = 4, Baz = "brown"},
                     new Thing {Foo = 6, Bar = 3, Baz = "fox"},
                     new Thing {Foo = 6, Bar = 4, Baz = "jumps"}
                 };
    
      var thingMap = Map<int, int, string>.From(things, t => t.Foo, t => t.Bar, t => t.Baz);
    
      Assert.IsTrue(thingMap.ContainsKey(3, 4));
      Assert.AreEqual("brown", thingMap[3, 4]);
    
      thingMap.DefaultValue = string.Empty;
      Assert.AreEqual("brown", thingMap[3, 4]);
      Assert.AreEqual(string.Empty, thingMap[3, 6]);
    
      thingMap.DefaultGeneration = (k1, k2) => (k1.ToString() + k2.ToString());
    
      Assert.IsFalse(thingMap.ContainsKey(3, 6));
      Assert.AreEqual("36", thingMap[3, 6]);
      Assert.IsTrue(thingMap.ContainsKey(3, 6));
    }
    
    0 讨论(0)
  • 2021-01-31 05:37

    I think the simplest approach would be to use the LINQ extension methods. Obviously I haven't tested this code for performace.

    var items = new[] {
      new Thing { Foo = 1, Bar = 3, Baz = "a" },
      new Thing { Foo = 1, Bar = 3, Baz = "b" },
      new Thing { Foo = 1, Bar = 4, Baz = "c" },
      new Thing { Foo = 2, Bar = 4, Baz = "d" },
      new Thing { Foo = 2, Bar = 5, Baz = "e" },
      new Thing { Foo = 2, Bar = 5, Baz = "f" }
    };
    
    var q = items
      .ToLookup(i => i.Foo) // first key
      .ToDictionary(
        i => i.Key, 
        i => i.ToLookup(
          j => j.Bar,       // second key
          j => j.Baz));     // value
    
    foreach (var foo in q) {
      Console.WriteLine("{0}: ", foo.Key);
      foreach (var bar in foo.Value) {
        Console.WriteLine("  {0}: ", bar.Key);
        foreach (var baz in bar) {
          Console.WriteLine("    {0}", baz.ToUpper());
        }
      }
    }
    
    Console.ReadLine();
    

    Output:

    1:
      3:
        A
        B
      4:
        C
    2:
      4:
        D
      5:
        E
        F
    
    0 讨论(0)
提交回复
热议问题