4

我有以下函数将 csv 文件转换为特定的 txt 模式(由 CNTKTextFormat 阅读器预期):

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和处理它,但我也生成了空文件或仅几行(比如千分之五)。

这里发生了什么?另外,如何修复文件写入?

4

3 回答 3

7

Seq.map返回一个惰性序列,在迭代之前不会对其进行评估。您当前没有在其中迭代它,convert因此不会处理任何行。如果您返回 aSeq<unit>并在外部对其进行迭代convertoutFile则将已经关闭,这就是您看到异常的原因。

您应该Seq.iter改用:

data.Rows.Observations
    |> Seq.iter (fun kvp -> ...)
于 2016-12-14T13:32:18.493 回答
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."
    )
于 2016-12-14T16:42:59.277 回答
2

正如李已经提到的,Seq.map是懒惰的。这也是为什么您得到“无法写入已关闭的 TextWriter”的原因:use关键字在超出范围时将其 IDisposable 丢弃。在这种情况下,这是您的功能的结尾。由于Seq.map是惰性的,因此您的函数返回了一个StreamWriter评估的序列对象,该对象已在您的语句中关闭use- 但在您评估该序列时(在代码的任何部分检查Seq空值,或在 F# 交互窗口中),StreamWriter已经通过超出范围而被处置。

更改Seq.mapSeq.iter,您的两个问题都将得到解决。

于 2016-12-14T13:36:02.633 回答