如何使用LINQ将Dictionary的键和值组合到一个List中?

时间:2009-04-15 19:28:55

标签: c# linq

我有一个字典,其中键是一个字符串,值是与该键对应的字符串列表。我想显示字典中的所有键,其中与该键相关联的值标签在该键下方。像这样:

Key 1
    Value 1
    Value 2
    Value 3
Key 2
    Value 1
    Value 2

在C#2.0中,我会这样做(valuesDictionary):

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 个答案:

答案 0 :(得分:20)

C#中的解决方案

以下是使用aggregate extension method

的解决方案
string result = values.Aggregate("",
                  (keyString, pair) =>
                    keyString + "\n" + pair.Key + ":"
                      + pair.Value.Aggregate("",
                          (str, val) => str + "\n\t" + val)
                  );

C#中的聚合子句没有LINQ语法(但显然在Visual Basic中有一些预定义的函数)。

这个解决方案可能看起来有些复杂,但聚合方法非常有用。

聚合的工作原理

它的工作方式如下:如果您有List<int>,则可以将所有值合并为单个汇总值

这与Select方法形成对比,后者不会修改列表的长度。或Where方法,它会缩小列表,但它仍然是一个列表(而不是单个值)。

例如,如果您有一个列表{1, 2, 3, 4},您可以将它们组合成一个值,如下所示:

int[] xs = {1, 2, 3, 4};
int sum = xs.Aggregate(0, (sumSoFar, x) => sumSoFar + x);

所以你给Aggregate方法赋予两个值;种子值和'组合器'功能:

  • 您开始使用单个种子值进行聚合(在这种情况下为0)。
  • 为列表中的每个值调用合并器函数 第一个参数是到目前为止的计算结果(第一次为零,稍后将具有其他值),并将其与值x组合。

这就是Aggregate方法在List<int>上的工作原理。它在KeyValuePair<string, List<string>>上的工作方式相同,但只是使用不同的类型。

答案 1 :(得分:2)

我不确定你是怎么在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());

但这并不是非常易读。

答案 2 :(得分:1)

LINQ中没有任何内容可以帮助你,因为你对这些值有一套单独的要求,而不是你在键之间的选项(你在值之间选项卡)。

即使情况并非如此,充其量只是LINQ会帮助你只需要一个枚举源来循环,你必须要有一些分区逻辑来指示你什么时候处理一组值与一个键。

它归结为你已经为你提供了一个自然分组,除非你正在处理除分组(排序,投影,过滤)以外的操作,否则LINQ将无法再提供帮助。

答案 3 :(得分:1)

如果你有这样的方法:

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

答案 4 :(得分:0)

我会对值进行选择,将项目放在初始列表中(使用string.Join作为值),然后将其弹出到字符串中(再次使用string.Join)。

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