Изящный способ создать вложенный Словарь в C#

Я понял, что не дал достаточно информации для большинства людей, чтобы прочитать мои мысли и понять все мои потребности, таким образом, я изменил это несколько из оригинала.

Скажите, что у меня есть список объектов класса как это:

public class Thing
{
    int Foo;
    int Bar;
    string Baz;
}

И я хочу категоризировать строку Baz на основе значений Foo, затем Панель. Будет самое большее одна Вещь для каждой возможной комбинации Foo и значений Панели, но у меня, как гарантируют, не будет значения для каждого. Это может помочь осмыслять его как информацию о соте для таблицы: Foo является номером строки, Панель является номером столбца, и 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.
        }
    }
}

Что такое легкий, изящный способ генерировать вложенный словарь? Я использовал C# достаточно долго, что я привыкаю к нахождению простых, коротких решений для всего общего материала как это, но этому озадачили меня.

17
задан StriplingWarrior 17 December 2009 в 16:33
поделиться

7 ответов

Вот решение с использованием 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));
30
ответ дан 30 November 2019 в 10:18
поделиться

Изящным способом было бы не самостоятельно создавать словари, а использовать 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.");

Я предполагаю, что путем категоризации Baz с использованием Foo и Bar ] вы имеете в виду, что если у двух вещей оба Foo и Bar равны, то их значение Baz также будет таким же. Пожалуйста, поправьте меня, если я ошибаюсь.

Вы в основном группа по свойству Foo сначала ...
затем для каждой полученной группы вы группируете по свойству Bar ...
затем для каждой результирующей группы вы берете первое значение 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 нет эквивалента понимания запросов, поэтому он не такой элегантный, как лямбда-выражения.

...

Надеюсь, это поможет.

20
ответ дан 30 November 2019 в 10:18
поделиться

Define your own custom generic NestedDictionary class

public class NestedDictionary<K1, K2, V>: 
     Dictionary<K1, Dictionary<K2, V>> {}

then in your code you write

NestedDictionary<int, int, string> dict = 
       new NestedDictionary<int, int, string> ();

if you use the int, int, string one a lot, define a custom class for that too..

   public class NestedIntStringDictionary: 
        NestedDictionary<int, int, string> {}

and then write:

  NestedIntStringDictionary dict = 
          new NestedIntStringDictionary();

EDIT: To add capability to construct specific instance from provided List of items:

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

and then write:

  NestedIntStringDictionary dict = 
       new NestedIntStringDictionary(GetThings());
4
ответ дан 30 November 2019 в 10:18
поделиться

Другой подход быть ключевым для вашего словаря, используя анонимный тип, основанный как на значениях 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}];

Это эффективно сводит вашу иерархию в единый словарь. Обратите внимание, что этот словарь не может быть представлен извне, поскольку он основан на анонимном типе.

Если комбинация значений Foo и Bar не уникальна в вашей исходной коллекции, вам нужно сначала сгруппировать их.

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)
{
    //...
}
3
ответ дан 30 November 2019 в 10:18
поделиться

Вы можете использовать KeyedCollection , где вы определяете:

class ThingCollection
    : KeyedCollection<Dictionary<int,int>,Employee>
{
    ...
}
2
ответ дан 30 November 2019 в 10:18
поделиться
Dictionary<int, Dictionary<string, int>> nestedDictionary = 
            new Dictionary<int, Dictionary<string, int>>();
-2
ответ дан 30 November 2019 в 10:18
поделиться

Я думаю, что самым простым подходом было бы использование методов расширения 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
1
ответ дан 30 November 2019 в 10:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: