tags:

views:

323

answers:

3

Say that i have a generic dictionary with data like this (I hope the notation is clear here):

{ "param1" => "value1", "param2" => "value2", "param3" => "value3" }

I'm trying to use the Enumerable.Aggregate function to fold over each entry in the dictionary and output something like this:

"/param1= value1; /param2=value2; /param3=value3"

If I were aggregating a list, this would be easy. With the dictionary, I'm getting tripped up by the key/value pairs.

+5  A: 

You don't need Aggregate:

String.Join("; ", 
    dic.Select(x => String.Format("/{0}={1}", x.Key, x.Value)).ToArray())

If you really want to use it:

dic.Aggregate("", (acc, item) => (acc == "" ? "" : acc + "; ") 
                        + String.Format("/{0}={1}", item.Key, item.Value))

Or:

dic.Aggregate("", 
     (acc, item) => String.Format("{0}; /{1}={2}", acc, item.Key, item.Value), 
     result => result == "" ? "" : result.Substring(2));
Mehrdad Afshari
Thanks! looking at the dic.aggregate code, I see why the String.Join solution is better suited for this.
brad
But isn't the `String.Join` method walking over the dictionary twice (once for setting up the array and a second time to join array elements)? You are also creating a throw-away array when you can just as well join your string as you are walking over the elements.
paracycle
paracycle: If performance is at the forefront, you should probably use something like `StringBuilder` in a loop but even if it's not, using `Join` will allocate a single string buffer and copies all the strings in the array to it which is probably more efficient than concatenating each part of string and throwing it away. The concat operation in aggregate is O(n^2) where n is the length of output string.
Mehrdad Afshari
+1  A: 

I believe this suits your needs:

        var dictionary = new Dictionary<string, string> {{"a", "alpha"}, {"b", "bravo"}, {"c", "charlie"}};
        var actual = dictionary.Aggregate("", (s, kv) => string.Format("{0}/{1}={2}; ", s, kv.Key, kv.Value));
        Assert.AreEqual("/a=alpha; /b=bravo; /c=charlie; ", actual);
neontapir
That would leave you with an extra ";" at the end though. Most of the time (and also for the original question, it seems) that is not desirable.
paracycle
Will include an additional "; " you'll have to strip off. Works anyway.
Mehrdad Afshari
You can use the result selector to take care of that trailing entry:var actual = dictionary.Aggregate("", (s, kv) => string.Format("{0}/{1}={2}; ", s, kv.Key, kv.Value), s => s.TrimEnd(' ').TrimEnd(';'));
neontapir
neontapir: It's pretty easy to get rid of the it but I wouldn't use `TrimEnd(';')`. It'll screw things up if the last value ends with `;` character.
Mehrdad Afshari
neontapir, If you are looking for a moral victory, yours is the solution that I ended up using. The ';' character was actually a mistake in my original question: I didn't actually need the ';' character to delimit the key-value pairs, I just needed whitespace. Still, whether I actually needed it or not, the ';' character was part of my question, and in this situation, I've gotta give the score to Mehrdad. Thanks!
brad
@brad: Thanks! @Mehrdad, I concur. If the data might have semicolons, something more stringent is required. My point simply was that there is an overload to Aggregate() that allows for some post-processing.
neontapir
A: 

I think what you are looking for is something like this:

var str = dic.Aggregate("", (acc, item) => {
    if (acc != "")
        acc += "; ";

    acc += "/" + item.Key + "=" + item.Value;

    return acc;
});
paracycle
Won't work. You need to return `acc`.
Mehrdad Afshari
That's what you get for changing from simple lambda syntax to a function body at the last minute. :)
paracycle