2016-12-14 11 views
4

に変化の挙動を提出するために、私は(CNTKTextFormatリーダーが期待する)特定のTXTスキーマにCSVファイルを変換し、次の機能があります。F#ライティング戻り値の型

open System.IO 
open FSharp.Data; 
open Deedle; 

let convert (inFileName : string) = 
    let data = Frame.ReadCsv(inFileName) 
    let outFileName = inFileName.Substring(0, (inFileName.Length - 4)) + ".txt" 
    use outFile = new StreamWriter(outFileName, false) 
    data.Rows.Observations 
    |> Seq.map(fun kvp -> 
     let row = kvp.Value |> Series.observations |> Seq.map(fun (k,v) -> v) |> Seq.toList 
     match row with 
     | label::data -> 
      let body = data |> List.map string |> String.concat " " 
      outFile.WriteLine(sprintf "|labels %A |features %s" label body) 
      printf "%A" label 
     | _ -> 
      failwith "Bad data." 
    ) 
    |> ignore 

不思議なことに、出力ファイルはで実行した後、空でありますF#インタラクティブパネルとそのprintfは全く印刷を行いません。

私が代わりに私が手に空のファイルで、(ヌルの配列を返すことによって証明される)処理されている実際の行があることを確認するignoreを削除する場合:私はだった、前

val it : seq<unit> = Error: Cannot write to a closed TextWriter.

StreamWriterletとし、それを手動で破棄していると宣言していますが、空のファイルやほんの数行(数千から5個)も生成しました。

ここで何が起こっているかまた、どのようにファイルの書き込みを修正するには?

+0

「Seq.map」はレイジーです。あなたが望むのは 'Seq.iter'です。これは副作用関数(' unit'を返す関数)を受け取り、非遅延的にseqの各項目にそれを適用します。 – rmunn

答えて

7

Seq.mapは、反復処理されるまで評価されないレイジーシーケンスを返します。現時点では、convertの範囲内で反復処理を行っていないため、行は処理されません。 Seq<unit>を返してconvertの外側に反復すると、outFileは既に閉じられているため、例外が表示されます。

代わりSeq.iterを使用する必要があります。

data.Rows.Observations 
    |> Seq.iter (fun kvp -> ...) 
2

リーはすでに述べたように、Seq.mapは怠け者です。そして、それはあなたが「閉じたTextWriterに書き込めません」となっている理由です:useキーワードは範囲外になったときにそのIDisposableを破棄します。この場合、それはあなたの関数の最後です。 Seq.mapが遅延しているため、useステートメントのStreamWriterを閉じていたシーケンスオブジェクトはの評価されていませんでしたが、そのシーケンスが評価される頃には(コードのどの部分がヌルのSeqであるか、またはF#インタラクティブウィンドウで)、StreamWriterはすでに範囲外になって処分されていました。

Seq.mapからSeq.iterに変更すると、両方の問題が解決されます。

3

既に解説したソリューションとは別に、StreamWriterも避けて、標準の.Net関数の1つ、File.WriteAllLinesを使用することもできます。あなたは、変換された行のシーケンスを作成し、そのファイルにそれを記述します。コメント欄での議論に基づいて更新

let convert (inFileName : string) = 
    let lines = 
     Frame.ReadCsv(inFileName).Rows.Observations 
     |> Seq.map(fun kvp -> 
      let row = kvp.Value |> Series.observations |> Seq.map snd |> Seq.toList 
      match row with 
      | label::data -> 
       let body = data |> List.map string |> String.concat " " 
       printf "%A" label 
       sprintf "|labels %A |features %s" label body 
      | _ -> 
       failwith "Bad data." 
     ) 
    let outFileName = inFileName.Substring(0, (inFileName.Length - 4)) + ".txt" 
    File.WriteAllLines(outFileName, lines) 

:ここでは完全にDeedleを回避ソリューションです。私は今日あなたが投稿した別の質問に基づいて、あなたの入力ファイル形式についていくつかの仮定をしています。ラベルは列1にあり、機能が続きます。

let lines = 
    File.ReadLines inFileName 
    |> Seq.map (fun line -> 
     match Seq.toList(line.Split ',') with 
     | label::data -> 
      let body = data |> List.map string |> String.concat " " 
      printf "%A" label 
      sprintf "|labels %A |features %s" label body 
     | _ -> 
      failwith "Bad data." 
    ) 
+1

クールなことです:あなたはファイルサイズについて心配する必要はありません。シーケンスは怠惰です(少なくとも、ReadCsvがすべての正しいことをしているとは限りません。) –

+0

ああ、私はいません何かを学んだ。そのような遅延ロードを入力ファイル行にも行う方法はありますか? – VillasV

+2

'File.ReadLines'はあなたの友人です –