2016-12-14 21 views
4

我有以下功能CSV文件轉換到一個特定的TXT模式(由CNTKTextFormat閱讀器預計):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.

之前,我聲明StreamWriter使用let並手動處理它,但我也生成空文件或只是幾行(比如說成千上萬)。

這裏發生了什麼?另外,如何解決文件寫入?

+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函數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'是你的朋友 –