3

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.)

Hosam Aly
  • 38,883
  • 35
  • 132
  • 179
  • Re "I'm actually trying to learn LINQ"; LINQ is simply a tool; perhaps one of the most important lessons to learn with LINQ is when not to use it? – Marc Gravell Feb 25 '09 at 09:32
  • Maybe. I wouldn't use LINQ for this method in a real application, but it's easier to learn when one has a real-life requirement to think about (rather than some contrived examples). – Hosam Aly Feb 25 '09 at 09:37
  • Re your measurements post: then I suspect your measurements are suspect... care to post? – Marc Gravell Feb 25 '09 at 10:17
  • 1
    @Marc, sure. I will create a separate question for it. – Hosam Aly Feb 25 '09 at 11:56
  • The answer you accepted can be made more intention-revealing, meaning more "what", less "how" (one of the primary benefits of LINQ). See my answer for details. – Bryan Watts Feb 26 '09 at 08:02
  • @Bryan, yes, but your answer uses extension methods more than LINQ. I probably need to think which one is more "elegant", as this was my question initially... – Hosam Aly Feb 26 '09 at 08:58
  • This problem entails 2 joins and a transformation from double to string. The most LINQ is applicable would be a Select, since you are only transforming and aren't doing any ordering or grouping. Learning what makes LINQ powerful (extension methods, lambdas, etc.) is also a good idea. – Bryan Watts Feb 26 '09 at 15:31
  • Alright; I'm convinced. When it comes to "elegancy", your answer is probably more "elegant", but I wish I could give @Mudu another vote. :) – Hosam Aly Feb 26 '09 at 17:55
  • Haha I understand :-) You can always find another problem which lends itself a little more to LINQ usage. – Bryan Watts Feb 26 '09 at 19:22

4 Answers4

6

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
  • 927,783
  • 236
  • 2,422
  • 2,784
  • 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 Feb 25 '09 at 09:06
  • Thank you. I understand that, but efficiency is not my concern here. I'm actually trying to learn LINQ. :) – Hosam Aly Feb 25 '09 at 09:15
  • 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 Feb 25 '09 at 09:20
  • But you then have one StringBuilder per row, as opposed to one overall. It also avoids an extra array per row. – Marc Gravell Feb 25 '09 at 09:26
  • 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 Feb 25 '09 at 09:33
2

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()));
}
Hosam Aly
  • 38,883
  • 35
  • 132
  • 179
Jero
  • 159
  • 3
1

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

Matthias Meid
  • 12,080
  • 6
  • 41
  • 73
1

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
  • 42,403
  • 15
  • 78
  • 85