C# 在C中创建嵌套字典的优雅方法#

C# 在C中创建嵌套字典的优雅方法#,c#,linq,C#,Linq,我意识到我没有为大多数人提供足够的信息来了解我的想法和理解我的所有需求,所以我对原来的想法做了一些改变。 假设我有一个类的项目列表,如下所示: public class Thing { int Foo; int Bar; string Baz; } 我想根据Foo的值对Baz字符串进行分类,然后是Bar。Foo和Bar值的每个可能组合至多有一个值,但我不能保证每个值都有一个值。它可能有助于将其概念化为表的单元格信息:Foo是行号,Bar是列号,Baz是要在其中找到的值,

我意识到我没有为大多数人提供足够的信息来了解我的想法和理解我的所有需求,所以我对原来的想法做了一些改变。

假设我有一个类的项目列表,如下所示:

public class Thing
{
    int Foo;
    int Bar;
    string Baz;
}
我想根据Foo的值对Baz字符串进行分类,然后是Bar。Foo和Bar值的每个可能组合至多有一个值,但我不能保证每个值都有一个值。它可能有助于将其概念化为表的单元格信息:Foo是行号,Bar是列号,Baz是要在其中找到的值,但不一定每个单元格都有一个值

IEnumerable<Thing> things = GetThings();
List<int> foos = GetAllFoos();
List<int> bars = GetAllBars();
Dictionary<int, Dictionary<int, string>> dict = // what do I put here?
foreach(int foo in foos)
{
    // I may have code here to do something for each foo...
    foreach(int bar in bars)
    {
        // I may have code here to do something for each bar...
        if (dict.ContainsKey(foo) && dict[foo].ContainsKey(bar))
        {
            // I want to have O(1) lookups
            string baz = dict[foo][bar];
            // I may have code here to do something with the baz.
        }
    }
}
IEnumerable things=GetThings();
List foos=GetAllFoos();
列表栏=GetAllBars();
Dictionary dict=//我在这里放什么?
foreach(在foos中输入foo)
{
//我可能有代码在这里做的每一个食物。。。
foreach(以条为单位的整型条)
{
//我可能有代码在这里做一些事情,为每个酒吧。。。
if(dict.ContainsKey(foo)&&dict[foo].ContainsKey(bar))
{
//我想进行O(1)次查找
字符串baz=dict[foo][bar];
//我这里可能有代码来处理baz。
}
}
}
生成嵌套字典的简单而优雅的方法是什么?我使用C语言的时间已经够长了,我已经习惯于为所有像这样的普通东西找到简单的、一行的解决方案,但这一行让我感到困惑。

Dictionary nestedDictionary=
Dictionary<int, Dictionary<string, int>> nestedDictionary = 
            new Dictionary<int, Dictionary<string, int>>();
新字典();
定义您自己的自定义泛型
NestedDictionary

public class NestedDictionary<K1, K2, V>: 
     Dictionary<K1, Dictionary<K2, V>> {}
编辑:添加从提供的项目列表构造特定实例的功能:

   public class NestedIntStringDictionary: 
        NestedDictionary<int, int, string> 
   {
        public NestedIntStringDictionary(IEnumerable<> items)
        {
            foreach(Thing t in items)
            {
                Dictionary<int, string> innrDict = 
                       ContainsKey(t.Foo)? this[t.Foo]: 
                           new Dictionary<int, string> (); 
                if (innrDict.ContainsKey(t.Bar))
                   throw new ArgumentException(
                        string.Format(
                          "key value: {0} is already in dictionary", t.Bar));
                else innrDict.Add(t.Bar, t.Baz);
            }
        }
   }
您可以在定义以下内容时使用:

class ThingCollection
    : KeyedCollection<Dictionary<int,int>,Employee>
{
    ...
}
class ThingCollection
:KeyedCollection
{
...
}

下面是一个使用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));
Dictionary dict=事物
.GroupBy(thing=>thing.Foo)
.ToDictionary(fooGroup=>fooGroup.Key,
fooGroup=>fooGroup.ToDictionary(thing=>thing.Bar,
thing=>thing.Baz);

一种优雅的方法是不要自己创建字典,而是使用LINQ
GroupBy
ToDictionary
为您生成字典

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.");
我假设,通过使用
Foo
Bar
Baz
进行分类,你的意思是,如果两个事物都
Foo
Bar
相等,那么它们的
Baz
值也相同。如果我错了,请纠正我

您基本上是先按
Foo
属性分组…
然后,对于每个结果组,您可以在
属性上进行分组…
然后,对于每个结果组,将第一个
Baz
值作为字典值

如果您注意到,方法名称与您尝试执行的操作完全匹配。:-)


编辑:这里有另一种使用查询理解的方法,它们更长,但更容易阅读和理解:

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);
不幸的是,ToDictionary没有对应的查询理解,因此它没有lambda表达式那么优雅


希望这能有所帮助。

另一种方法是使用基于Foo和Bar值的匿名类型为词典设置关键字

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}];

我认为最简单的方法是使用LINQ扩展方法。显然,我还没有测试这段代码的性能

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();
输出:

1:
  3:
    A
    B
  4:
    C
2:
  4:
    D
  5:
    E
    F

使用BeanMap的双键映射类。还有一个3键映射,如果您需要n个键,它是非常可扩展的

然后,您的解决方案将如下所示:

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));
}
类的东西
{
公共int Foo{get;set;}
公共整型条{get;set;}
公共字符串Baz{get;set;}
}
[测试方法]
公共无效列表目录()
{
var things=新列表
{
新事物{Foo=3,Bar=3,Baz=“quick”},
新事物{Foo=3,Bar=4,Baz=“brown”},
新事物{Foo=6,Bar=3,Baz=“fox”},
新事物{Foo=6,Bar=4,Baz=“jumps”}
};
var thingMap=Map.From(things,t=>t.Foo,t=>t.Bar,t=>t.Baz);
Assert.IsTrue(thingMap.ContainsKey(3,4));
AreEqual(“brown”,thingMap[3,4]);
thingMap.DefaultValue=string.Empty;
AreEqual(“brown”,thingMap[3,4]);
AreEqual(string.Empty,thingMap[3,6]);
thingMap.DefaultGeneration=(k1,k2)=>(k1.ToString()+k2.ToString());
Assert.IsFalse(thingMap.ContainsKey(3,6));
AreEqual(“36”,thingMap[3,6]);
Assert.IsTrue(thingMap.ContainsKey(3,6));
}

起点是什么?物品清单?谢谢你的回答。我更新了这个问题,让它更清楚。看起来你们中的一些人已经有了正确的想法。一旦我测试了你的答案,我将开始投票并分配一个获胜者。访问者是什么样子的?这如何帮助我从提供给我的数据优雅地构建嵌套字典?对不起,我回答了原始问题,但没有明确这一点。。。编辑了我的答案,明确地向您展示了如何做到这一点……使用“var dict=”,您可以使用LINQ折叠多个foreach语句:var bazz=dict.SelectMany(topPair=>topPair.Value.Values);foreach(stringbaz-in-baz){/…}这似乎是我一直在寻找的简洁、优雅的解决方案。GroupBy/ToDictionary组合正是我自己难以想出的。谢谢。你不太明白我问题的要点。我正在寻找一个linq语句或一些允许我从现有列表中填充字典的东西,而不是简单地实例化它。我现在还不清楚这将如何解决我的问题。请详细说明。+1以确保答案的完整性。我很想把这个标记为答案,但是马克的答案因为不需要第二个
GroupByvar 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}];
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)
{
    //...
}
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();
1:
  3:
    A
    B
  4:
    C
2:
  4:
    D
  5:
    E
    F
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));
}