score:20

Accepted answer

a solution in c#

here is a solution using the aggregate extension method:

string result = values.aggregate("",
                  (keystring, pair) =>
                    keystring + "\n" + pair.key + ":"
                      + pair.value.aggregate("",
                          (str, val) => str + "\n\t" + val)
                  );

there is no linq syntax for the aggregate clause in c# (but apparently there is in visual basic for some predefined functions).

this solution might look somewhat complex, but the aggregate method is quite useful.

how aggregate works

it works like this: if you have a list<int> you can combine all values into a single aggregate value.

that is in contrast to the select method, which doesn't modify the length of the list. or the where method, which does shrink the list, but it still remains a list (instead of a single value).

for example, if you have a list {1, 2, 3, 4} you can combine them into a single value like this:

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

so you give two values to the aggregate method; a seed value and a 'combiner' function:

  • you start aggregating with a single seed value (0 in this case).
  • your combiner function gets called for each value in the list.
    the first argument is the computed result so far (zero the first time, later this will have other values), and it combines it with the value x.

that's in short how the aggregate method works on list<int>. it works the same on keyvaluepair<string, list<string>>, but just with different types.

score:0

i'd do a select on the values to put things in an initial list by item (using string.join for the values) and then pop that into a string (again using 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());

score:1

there isn't anything in linq that is going to help you here, because you have a separate set of requirements for the values than you do the keys (you tab between the values).

even if that wasn't the case, at best, linq is going to help you with just getting a single enumeration source to cycle through, which you would have to have some sort of partitioning logic in anyways to indicate when you are processing a set of values versus a key.

what it comes down to is that the dictionary you have already gives you a natural grouping which linq won't be able to help any more with unless you are dealing with an operation other than grouping (sorting, projection, filtering).

score:1

if you had a method like this:

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

then you could do this:

list<string> result = thedictionary
  .selectmany(kvp => getstrings(kvp)).tolist();

or generically:

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

score:2

i'm not sure how you'd do it in linq, but using lambdas you could do something like:

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

that's not terribly readable, though.


Related Query

More Query from same tag