Как объединить ключи и значения словаря в один список с помощью LINQ?
у меня есть словарь, где ключ-это строка, а значение-это список строк, соответствующих этому ключу. Я хотел бы отобразить все ключи в словаре со значениями, связанными с этим ключом, вложенным под этим ключом. Что-то вроде этого:--6-->
Key 1
Value 1
Value 2
Value 3
Key 2
Value 1
Value 2
в C# 2.0 я бы сделал это так (values
- это Dictionary
):
StringBuilder sb = new StringBuilder();
foreach(KeyValuePair<string, List<string>> pair in values)
{
sb.AppendLine(pair.Key);
foreach(string item in pair.Value)
{
sb.AppendLine('t' + item);
}
}
как бы я сделал эквивалент с помощью LINQ? Кажется, это должно быть возможно, однако я не могу понять как это сделать.
если я использую values.SelectMany(p => p.Values)
, тогда в конечном результате будут только значения, а не ключи.
любое другое решение, о котором я думал, имеет аналогичное ограничение.
5 ответов
решение в C#
вот решение, использующее метод расширения aggregate:
string result = values.Aggregate("",
(keyString, pair) =>
keyString + "\n" + pair.Key + ":"
+ pair.Value.Aggregate("",
(str, val) => str + "\n\t" + val)
);
нет синтаксиса LINQ для предложения aggregate в C# (но, по-видимому, есть в Visual Basic для некоторых предопределенных функций).
это решение может выглядеть несколько сложным, но метод aggregate довольно полезен.
как работает Aggregate
он работает следующим образом: если у вас есть List<int>
вы можете объединить все значения в один общей стоимостью.
в отличие от Select
метод, который не изменяет длину списка. Или Where
метод, который сжимает список, но он по-прежнему остается списком (вместо одного значения).
например, если у вас есть список {1, 2, 3, 4}
вы можете объединить их в одно значение, например:
int[] xs = {1, 2, 3, 4};
int sum = xs.Aggregate(0, (sumSoFar, x) => sumSoFar + x);
таким образом, вы даете два значения Aggregate
способ; семя значение и функция "объединитель":
- вы начинаете агрегировать с одним значением seed (
0
в данном случае). - ваш combiner функция вызывается для каждого значения в списке.
Первый аргумент-это вычисленный результат до сих пор (ноль в первый раз, позже это будет иметь другие значения), и он сочетает его со значениемx
.
вот вкратце, как Aggregate
метод работает на List<int>
. Он работает так же на KeyValuePair<string, List<string>>
, но только с разными типами.
Я не уверен, как вы это сделаете в LINQ, но с помощью lambdas вы можете сделать что-то вроде:
string foo = string.Join(Environment.NewLine,
values.Select(k => k.Key + Environment.NewLine +
string.Join(Environment.NewLine,
k.Value.Select(v => "\t" + v).ToArray())).ToArray());
это не очень читабельно, хотя.
в LINQ нет ничего, что поможет вам здесь, потому что у вас есть отдельный набор требований к значениям, чем вы делаете ключи (вы вкладываете между значениями).
даже если это не так, в лучшем случае LINQ поможет вам просто получить один источник перечисления для цикла, который вам нужно будет иметь какую-то логику разбиения в любом случае, чтобы указать, когда вы обрабатываете набор значений по сравнению с ключом.
Что это сводится к тому, что словарь, который у вас уже есть, дает вам естественную группировку, с которой LINQ больше не сможет помочь, если вы не имеете дело с операцией, отличной от группировки (сортировка, проекция, фильтрация).
Если бы у вас был способ такой:
public IEnumerable<string> GetStrings
(KeyValuePair<string, List<string>> kvp)
{
List<string> result = new List<string>();
result.Add(kvp.Key);
result.AddRange(kvp.Value.Select(s => "\t" + s));
return result;
}
тогда вы могли бы сделать это:
List<string> result = theDictionary
.SelectMany(kvp => GetStrings(kvp)).ToList();
или в общем виде:
public static IEnumerable<T> GetFlattened<T, U>
( this KeyValuePair<T, List<U>> kvp,
Func<U, T> ValueTransform
)
{
List<T> result = new List<T>();
result.Add(kvp.Key);
result.AddRange(kvp.Value.Select(v => ValueTransform(v)));
return result;
}
List<string> result = theDictionary
.SelectMany(kvp => kvp.GetFlattened(v => "\t" + v))
.ToList();
Я бы сделал выбор значений, чтобы поместить вещи в начальный список по элементам (используя строку.Присоединяйтесь к значениям), а затем вставьте это в строку (снова используя string.Присоединяться.)
IEnumerable<string> items = values.Select(v =>
string.Format("{0}{1}{2}", v.Key, Environment.NewLine + "\t",
string.Join(Environment.NewLine + "\t", v.Value.ToArray()));
string itemList = string.Join(Environment.NewLine, items.ToArray());