tags:

views:

414

answers:

6

Is there a way to take a List and convert it into a comma separated string?

I know I can just loop and build it, but somehow I think some of you guys a more cool way of doing it?

I really want to learn these types of 'tricks', so please explain or link to the docs on the method you use.

+10  A: 
List<int> list = ...;
string.Join(",", list.Select(n => n.ToString()).ToArray())
Pavel Minaev
Clever but slow and bloated, as it allocates one string per element. Using a StringBuilder would be much more efficient.
Steven Sudit
From what I saw online(quick search) String.Join is faster than using a StringBuilder.
Yuriy Faktorovich
http://stackoverflow.com/questions/585860/string-join-vs-stringbuilder-which-is-faster, you are incorrect Steven
Yuriy Faktorovich
I think Steven is referring to the n.ToString() part rather than the String.Join.
Larsenal
Larsenal: but StringBuilder.Append(Int32) internally calls ToString on the integer anyway. StringBuilder doesn't magically avoid the cost of allocating a string for each element; it just tucks it nicely out of sight.
itowlson
@itowlson: Good point, although it still has to keep expanding the array, which means allocating a new one and copying it over.
Steven Sudit
@Adam: Even taking itowlson's point, it remains the case that a loop with StringBuilder is going to be faster.
Steven Sudit
@Steven: Read the link, StringBuilder isn't going to be faster.
Yuriy Faktorovich
@Yuriy, I read the link, including the part where Skeet says "if you already have an array of strings to concatenate together (with a delimiter), String.Join is the fastest way of doing it." That "if" is the issue. While Join is well-optimized and even bypasses some of the overhead of StringBuilder, creating the array of strings in the first place has its own costs, including the need to copy over with each doubling. Which one is faster in practice is going to depend heavily on whether it's just a couple of numbers or many. From what I see, the latter should favor StringBuilder.
Steven Sudit
@Steven: I'm getting strange benchmarking results. I used cdiggins method for the StringBuilder, unless you want to write your own. It turned out for larger arrays the StringBuilder wins, but for shorter ones it depends on how many times it is run(LINQ wins for shorter arrays run many times). But altogether it looks like you are correct in this case the StringBuilder worked faster.
Yuriy Faktorovich
@Yuriy: That's interesting. One possibility is what I said above regarding array doubling, but then again, cdiggins suffers from the same problem on the StringBuilder side. Maybe String.Join's optimization of precalculating total length runs afoul of cache limitations. Either way, I was clearly wrong in the first place about StringBuilder being "much" faster, since I didn't take into account the unnecessary temporary string inside StringBuilder.Append(int). The two methods are both acceptable, although a properly-tuned StringBuilder.Append solution has a slight advantage on large arrays.
Steven Sudit
+4  A: 
List<int> list = new List<int> { 1, 2, 3 };
Console.WriteLine(String.Join(",", list.Select(i => i.ToString()).ToArray()));
Yuriy Faktorovich
+1  A: 

My "clever" entry:

        List<int> list = new List<int> { 1, 2, 3 };
        StringBuilder sb = new StringBuilder();
        var y = list.Skip(1).Aggregate(sb.Append(x.ToString()),
                    (sb1, x) =>  sb1.AppendFormat(",{0}",x));

        // A lot of mess to remove initial comma
        Console.WriteLine(y.ToString().Substring(1,y.Length - 1));

Just haven't figured how to conditionally add the comma.

Larsenal
Please don't write `Select` with side effects in the lambda. In this case you aren't even using `y`, so your `Select` is essentially just a `foreach` - so write it as such.
Pavel Minaev
I wasn't suggesting this as a good solution. OP wanted something more interesting than foreach.
Larsenal
Yeah, but abusing `Select` as `foreach` goes past "interesting" and into, well, "abuse". A more interesting approach here would be to use `Enumerable.Aggregate` with `StringBuilder` as a seed value - try that.
Pavel Minaev
Good idea. I have to step out, but I may give that a whirl.
Larsenal
+1  A: 

For extra coolness I would make this an extension method on IEnumerable<T> so that it works on any IEnumerable:

public static class IEnumerableExtensions {
  public static string BuildString<T>(this IEnumerable<T> self, string delim) {
    return string.Join(",", list.Select(x => x.ToString()).ToArray())        
  }
}

Use it as follows:

List<int> list = new List<int> { 1, 2, 3 };
Console.WriteLine(list.BuildString(", "));
cdiggins
Two possible optimizations: 1) Append the delimeter after each item regardless, then remove the extra one after the loop ends. 2) Specify a capacity for the StringBuilder.
Steven Sudit
If you dig out Reflector, it turns out that Join sums up the lengths to precalculate the buffer size, and also "primes the pump" by appending the first string outside the loop, and then, inside the loop, unconditionally appending the delimiter before the next string. Combined with some unsafe/internal tricks, it should be very fast.
Steven Sudit
@Steven: followed your advice.
cdiggins
Hope it worked out well for you.
Steven Sudit
+4  A: 

For approximately one gazillion solutions to a slightly more complicated version of this problem -- many of which are slow, buggy, or don't even compile -- see the comments to my article on this subject:

http://blogs.msdn.com/ericlippert/archive/2009/04/15/comma-quibbling.aspx

and the StackOverflow commentary:

http://stackoverflow.com/questions/788535/eric-lipperts-challenge-comma-quibbling-best-answer

Eric Lippert
Thanks for the link. This string concatenation problem has turned out to be more complex, and more educational, than I had expected!
Steven Sudit
A: 

Seems reasonablly fast.

IList<int> listItem = Enumerable.Range(0, 100000).ToList();
var result = listItem.Aggregate<int, StringBuilder, string>(new StringBuilder(), (strBuild, intVal) => { strBuild.Append(intVal); strBuild.Append(","); return strBuild; }, (strBuild) => strBuild.ToString(0, strBuild.Length - 1));
Gregory