0

I need to write a list of tuples to a csv file. The tuples could have a variable number of fields and types! My current effort is as follows:

module SOQN = 

    open System
    open System.IO
    open FSharp.Data

    let lstTuples = [(2, false, 83.23, "Alpha", 29); (3, true, 79.11, "Beta", 47); (5, false, 61.13, "Gamma", 71)]

    let main() =
        do 
            use writer = new StreamWriter(@"C:\tmp\ListTuples.csv")
            let lstTuplesIter = lstTuples |> List.iter writer.WriteLine
            lstTuplesIter
        0

    [<EntryPoint>]
    main() |> ignore

// Actual Output: 
// (2, False, 83.23, Alpha, 29)
// (3, True, 79.11, Beta, 47)
// (5, False, 61.13, Gamma, 71)
// 
// Expected Output: 
// 2, False, 83.23, Alpha, 29
// 3, True, 79.11, Beta, 47
// 5, False, 61.13, Gamma, 71
//

What am I missing?

matekus
  • 778
  • 3
  • 14

3 Answers3

2

Although I agree with @Jackson that this is probably not the right data structure, for arbitrary length you will probably need reflection.

You see how they access components of a tuple ("ItemN" where N is a number) here.

You could iterate over the properties and get the values for your dynamic case.

Keep in mind that using reflection is pretty inefficient (see here)

MrWombat
  • 649
  • 9
  • 19
1

What you are doing is writing out the F# text interpretation of the tuple which includes the surrounding brackets, if you deconstruct the tuple and use sprintf to format the output you can get the result that you want:

lstTuples |> List.iter (fun (a,b,c,d,e) -> writer.WriteLine (sprintf "%d,%A,%.2f,%s,%d" a b c d e ))
Jackson
  • 5,627
  • 2
  • 29
  • 48
  • Unfortunately, I do not know in advance what fields are available in the tuple or its length as the incoming data is variable? – matekus Dec 14 '18 at 10:18
  • If you don't know the format of the data how are you going to create a list of tuples that represent it? I think you need to go back a step and show what your source data is and how you plan to turn it into a list of tuples. It may well be that tuples are not the right data type to be using. (NB: I'd suggest a new question rather than editing this one). – Jackson Dec 14 '18 at 10:28
  • 1
    Depending on the data source you might be able to use the F# Data library, see http://fsharp.github.io/FSharp.Data/ – Jackson Dec 14 '18 at 10:30
0

Thanks to you both for the benefit of your expertise. The following snippet works, as required (adapting Tomas Petricek's code):

module SOANS = 

open System
open System.IO
open FSharp.Reflection
open FSharp.Data

let lstTuples = [(2, false, 83.23, "Alpha", 29); (3, true, 79.11, "Beta", 47); (5, false, 61.13, "Gamma", 71)]

// https://stackoverflow.com/questions/13071986/write-a-sequence-of-tuples-to-a-csv-file-f    
let tupleToString t = 
  if FSharpType.IsTuple(t.GetType()) then
    FSharpValue.GetTupleFields(t)
    |> Array.map string
    |> String.concat ", "
  else failwith "not a tuple!"

let allIsStrings t = 
  t 
  |> Seq.map tupleToString
  |> Array.ofSeq

let main() =
    let lstTuples = [(2, false, 83.23, "Alpha", 29); (3, true, 79.11, "Beta", 47); (5, false, 61.13, "Gamma", 71)]
    let outTest = allIsStrings(lstTuples)
    File.WriteAllLines(@"C:\tmp\ListTuples.csv", outTest)
    0

[<EntryPoint>]
main() |> ignore
matekus
  • 778
  • 3
  • 14
  • Although this works, it is quite inefficient, you are using reflection to get the tuple fields as objects that therefore box ValueTypes like int, bool, and float onto heap, and you are concatenating all the strings into bigger strings... in memory. As already, I would recommend trying to use a record type instead, but if it needs to be tupled, you can overload a method with 2-20 tuple slots in a few minutes. If not going the overload route, I would still recommend opening up a stream writer like before, and write your tuple results and commas to the stream directly. – Gerard Dec 15 '18 at 02:32