Я понял, что не дал достаточно информации для большинства людей, чтобы прочитать мои мысли и понять все мои потребности, таким образом, я изменил это несколько из оригинала.
Скажите, что у меня есть список объектов класса как это:
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# достаточно долго, что я привыкаю к нахождению простых, коротких решений для всего общего материала как это, но этому озадачили меня.
Вот решение с использованием 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));
Изящным способом было бы не самостоятельно создавать словари, а использовать 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 нет эквивалента понимания запросов, поэтому он не такой элегантный, как лямбда-выражения.
...
Надеюсь, это поможет.
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());
Другой подход быть ключевым для вашего словаря, используя анонимный тип, основанный как на значениях 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)
{
//...
}
Вы можете использовать KeyedCollection , где вы определяете:
class ThingCollection
: KeyedCollection<Dictionary<int,int>,Employee>
{
...
}
Dictionary<int, Dictionary<string, int>> nestedDictionary =
new Dictionary<int, Dictionary<string, int>>();
Я думаю, что самым простым подходом было бы использование методов расширения 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