views:

485

answers:

4

I have a double[][] that I want to convert to a CSV string format (i.e. each row in a line, and row elements separated by commas). I wrote it like this:

public static string ToCSV(double[][] array)
{
    return String.Join(Environment.NewLine,
                       Array.ConvertAll(array,
                                        row => String.Join(",",
                                                           Array.ConvertAll(row, x => x.ToString())));
}

Is there a more elegant way to write this using LINQ?

(I know, one could use temporary variables to make this look better, but this code format better conveys what I am looking for.)

+5  A: 

You can, but I wouldn't personally do all the lines at once - I'd use an iterator block:

public static IEnumerable<string> ToCSV(IEnumerable<double[]> source)
{
    return source.Select(row => string.Join(",",
       Array.ConvertAll(row, x=>x.ToString())));        
}

This returns each line (the caller can then WriteLine etc efficiently, without buffering everything). It is also now callable from any source of double[] rows (including but not limited to a jagged array).

Also - with a local variable you could use StringBuilder to make each line slightly cheaper.


To return the entire string at once, I'd optimize it to use a single StringBuilder for all the string work; a bit more long-winded, but much more efficient (far fewer intermediate strings):

public static string ToCSV(IEnumerable<double[]> source) {
    StringBuilder sb = new StringBuilder();
    foreach(var row in source) {
        if (row.Length > 0) {
            sb.Append(row[0]);
            for (int i = 1; i < row.Length; i++) {
                sb.Append(',').Append(row[i]);
            }
        }
    }
    return sb.ToString();
}
Marc Gravell
Thank you. But just for the sake of learning, how would you make it into a single string? `String.Join(Environment.NewLine, ToCSV(source).ToArray())` for example?
Hosam Aly
I'd want to use a single StringBuilder - see update
Marc Gravell
Thank you. I understand that, but efficiency is not my concern here. I'm actually trying to learn LINQ. :)
Hosam Aly
I'm also not so sure a StringBuilder would have a real effect on the output. It may decrease memory requirements (due to the unneeded string allocated for each row), but the implementation of `String.Join` is very efficient IMO.
Hosam Aly
But you then have one StringBuilder per row, as opposed to one overall. It also avoids an extra array per row.
Marc Gravell
According to my measurements, `String.Join` in this case is sometimes 10% faster than `StringBuilder`, and sometimes about 1% slower (depending on the data). The `String.Join` method looks a better choice to me, as using it is easier and less error-prone.
Hosam Aly
+1  A: 

You can do it with LINQ, but I'm not sure if you like this one better than yours. I'm afraid you don't. :)

var q = String.Join(Environment.NewLine, (from a in d
                                      select String.Join(", ", (from b in a
                                                                select b.ToString()).ToArray())).ToArray());

Cheers, Matthias

Mudu
+2  A: 

You could also use Aggregate

public static string ToCSV(double[][] array)
{
  return array.Aggregate(string.Empty, (multiLineStr, arrayDouble) =>
           multiLineStr + System.Environment.NewLine + 
           arrayDouble.Aggregate(string.Empty, (str, dbl) => str + "," + dbl.ToString()));
}
Jero
+1  A: 

This is compatible with any nested sequences of double. It also defers the ToString implementation to the caller, allowing formatting while avoiding messy IFormatProvider overloads:

public static string Join(this IEnumerable<string> source, string separator)
{
    return String.Join(separator, source.ToArray());
}

public static string ToCsv<TRow>(this IEnumerable<TRow> rows, Func<double, string> valueToString)
    where TRow : IEnumerable<double>
{
    return rows
        .Select(row => row.Select(valueToString).Join(", "))
        .Join(Environment.NewLine);
}
Bryan Watts