如何在函数式编程 (F#) 中将行号添加到文本文件?

How to add line numbers to a text file in functional programming (F#)?

它适用于 for 循环和可变变量:

let addLnNum filename =    
    use outFile = new StreamWriter(@"out.txt")    
    let mutable count = 1
    for line in File.ReadLines(filename) do
        let newLine = addPre (count.ToString()) line
        outFile.WriteLine newLine
        count <- count + 1

但这非常 "non-functional" 所以我很好奇执行此操作的正确方法是什么? 我想出了如何将索引号附加到字符串列表中:

let rec addIndex (startInd:int) l=
    match l with
    |x::xs ->  startInd.ToString()+x :: (addIndex (startInd+1) xs)
    |[] -> []

但不适用于File.ReadLines:

let addLnNum2 filename =    
    use outFile = new StreamWriter(@"out.txt")    
    File.ReadLines(filename)
    |> addIndex 1
    |> ignore
    //Error 1   Type mismatch. Expecting a Collections.Generic.IEnumerable<string> -> 'a    
    //but given a string list -> string list    

将整个文件作为列表读入内存是执行此操作的唯一方法吗?是否有类似 seq.count 的东西,所以它可以像下面这样完成?

let addLnNum3 filename =    
    use outFile = new StreamWriter(@"out.txt")    
    File.ReadLines(filename)
    |> Seq.map (fun s -> Seq.count + s) //no such thing as Seq.count
    |> Seq.iter outFile.WriteLine 
    |> ignore

对于 Seq 模块中的某些功能(与 List 相同,...)您会发现带有附加 i 的版本 - 例如 Seq.map 你会发现 Seq.mapi 这就是你要找的东西 - 除了你的 collection 的值之外,你还得到(作为第一个参数)索引:

let addLnNums filename =    
    use outFile = new System.IO.StreamWriter (@"out.txt")
    System.IO.File.ReadLines filename
    |> Seq.mapi (sprintf "%d: %s")
    |> Seq.iter outFile.WriteLine

另请注意,您不需要 ignore,因为 Seq.iter 已经 returns () : unit

如果我们没有这个,那么实用的方法就是像这样使用 Zip

let addLnNum filename =    
    use outFile = new System.IO.StreamWriter (@"out.txt")
    Seq.zip (Seq.initInfinite id) (System.IO.File.ReadLines filename)
    |> Seq.map (fun (index, line) -> sprintf "%d: %s" index line)
    |> Seq.iter outFile.WriteLine

这(除了将函数取消柯里化为 map)基本相同


注:

对于列表,您显然没有 List.initInfinte,所以只需使用 Seq - Seq.zipList.zip 在 [=42= 方面也有不同的行为]s 具有不同的 item-count - Seq.zip 在一个 collection 运行时停止尝试但是 List.zip 希望两个列表具有相同的大小并且如果不是 [=28 将抛出异常=]

您的 addIndex 函数实际上是正确的 - 但它适用于 F# 列表。 ReadLine 函数 returns IEnumerable<T> 而不是 F# 列表(这是有道理的,因为它是一个 .NET 库)。您可以通过添加 List.ofSeq 来修复 addLnNum2 函数(将 IEnumerable<T> 转换为列表):

let addLnNum2 filename =    
    let added = 
      File.ReadLines(filename)
      |> List.ofSeq
      |> addIndex 1
    File.WriteAllLines("out.txt", added)

使用 Carsten 的回答中提到的 Seq.mapiSeq.zip 当然比实现您自己的递归函数更简单,但您确实正确地实现了递归和模式匹配:-)。