【问题标题】:What's the idiomatic way to make replayable sequences in F#?在 F# 中制作可重播序列的惯用方法是什么?
【发布时间】:2022-01-08 01:29:48
【问题描述】:

我刚刚开始使用今年的 Advent of Code 来学习 F#,我立即通过尝试重用 File.ReadLines 中的 IEnumerable 来踩雷。

以下是我看到的解决此问题的所有方法:

// Read all lines immediately into array/list
let linesAll     = File.ReadAllLines "file.txt"
let linesArray   = File.ReadLines "file.txt" |> Array.ofSeq
let linesList    = File.ReadLines "file.txt" |> List.ofSeq

// Lazily load and cache for replays
let linesCache   = File.ReadLines "file.txt" |> Seq.cache

// Start new filesystem read for every replay
let linesDelay   = (fun () -> File.ReadLines "file.txt") |> Seq.delay
let linesSeqExpr = seq { yield! File.ReadLines "file.txt" }
  • 这些在语义上是否都相同(对于只读文件)?
  • 只有linesDelaylinesSeqExpr 不会将整个文件读入内存吗?
  • linesList 是否因必须向后组合列表而减慢速度?
  • 这些中的任何一个被认为或多或少是惯用的吗?

编辑

这是重现我的问题的代码:

let lines = System.IO.File.ReadLines("alphabet.txt")
for i = 0 to 5 do
  let arr = Seq.zip lines (Seq.skip 1 lines) |> Array.ofSeq
  printfn "%A %A" i arr

给出输出:

0 [|("A", "C"); ("D", "E"); ("F", "G"); ("H", "I"); ("J", "K"); ("L", "M");
  ("N", "O"); ("P", "Q"); ("R", "S"); ("T", "U"); ("V", "W"); ("X", "Y")|]
1 [|("A", "B"); ("B", "C"); ("C", "D"); ("D", "E"); ("E", "F"); ("F", "G");
  ("G", "H"); ("H", "I"); ("I", "J"); ("J", "K"); ("K", "L"); ("L", "M");
  ("M", "N"); ("N", "O"); ("O", "P"); ("P", "Q"); ("Q", "R"); ("R", "S");
  ("S", "T"); ("T", "U"); ("U", "V"); ("V", "W"); ("W", "X"); ("X", "Y");
  ("Y", "Z")|]
2 [|("A", "B"); ("B", "C"); ("C", "D"); ("D", "E"); ("E", "F"); ("F", "G");
  ("G", "H"); ("H", "I"); ("I", "J"); ("J", "K"); ("K", "L"); ("L", "M");
  ("M", "N"); ("N", "O"); ("O", "P"); ("P", "Q"); ("Q", "R"); ("R", "S");
  ("S", "T"); ("T", "U"); ("U", "V"); ("V", "W"); ("W", "X"); ("X", "Y");
  ("Y", "Z")|]
3 [|("A", "B"); ("B", "C"); ("C", "D"); ("D", "E"); ("E", "F"); ("F", "G");
  ("G", "H"); ("H", "I"); ("I", "J"); ("J", "K"); ("K", "L"); ("L", "M");
  ("M", "N"); ("N", "O"); ("O", "P"); ("P", "Q"); ("Q", "R"); ("R", "S");
  ("S", "T"); ("T", "U"); ("U", "V"); ("V", "W"); ("W", "X"); ("X", "Y");
  ("Y", "Z")|]
4 [|("A", "B"); ("B", "C"); ("C", "D"); ("D", "E"); ("E", "F"); ("F", "G");
  ("G", "H"); ("H", "I"); ("I", "J"); ("J", "K"); ("K", "L"); ("L", "M");
  ("M", "N"); ("N", "O"); ("O", "P"); ("P", "Q"); ("Q", "R"); ("R", "S");
  ("S", "T"); ("T", "U"); ("U", "V"); ("V", "W"); ("W", "X"); ("X", "Y");
  ("Y", "Z")|]
5 [|("A", "B"); ("B", "C"); ("C", "D"); ("D", "E"); ("E", "F"); ("F", "G");
  ("G", "H"); ("H", "I"); ("I", "J"); ("J", "K"); ("K", "L"); ("L", "M");
  ("M", "N"); ("N", "O"); ("O", "P"); ("P", "Q"); ("Q", "R"); ("R", "S");
  ("S", "T"); ("T", "U"); ("U", "V"); ("V", "W"); ("W", "X"); ("X", "Y");
  ("Y", "Z")|]

看起来Seq.zip lines (Seq.skip 1 lines) 表达式通过同时进行两个枚举来触发错误。

编辑 2

C# 中的复制。顺序略有不同,因为我没有跳过右侧的一个。

var lines = File.ReadLines("alphabet.txt");
for (int i = 0; i < 5; i++)
{
    var zipped = new List<(string, string)>();
    var enum1 = lines.GetEnumerator();
    var enum2 = lines.GetEnumerator();
    while (enum1.MoveNext() && enum2.MoveNext())
    {
        zipped.Add((enum1.Current, enum2.Current));
    }
    Console.WriteLine($"{i} [{string.Join(',', zipped)}]");
}
0 [(A, B),(C, D),(E, F),(G, H),(I, J),(K, L),(M, N),(O, P),(Q, R),(S, T),(U, V),(W, X),(Y, Z)]
1 [(A, A),(B, B),(C, C),(D, D),(E, E),(F, F),(G, G),(H, H),(I, I),(J, J),(K, K),(L, L),(M, M),(N, N),(O, O),(P, P),(Q, Q),(R, R),(S, S),(T, T),(U, U),(V, V),(W, W),(X, X),(Y, Y),(Z, Z)]
2 [(A, A),(B, B),(C, C),(D, D),(E, E),(F, F),(G, G),(H, H),(I, I),(J, J),(K, K),(L, L),(M, M),(N, N),(O, O),(P, P),(Q, Q),(R, R),(S, S),(T, T),(U, U),(V, V),(W, W),(X, X),(Y, Y),(Z, Z)]
3 [(A, A),(B, B),(C, C),(D, D),(E, E),(F, F),(G, G),(H, H),(I, I),(J, J),(K, K),(L, L),(M, M),(N, N),(O, O),(P, P),(Q, Q),(R, R),(S, S),(T, T),(U, U),(V, V),(W, W),(X, X),(Y, Y),(Z, Z)]
4 [(A, A),(B, B),(C, C),(D, D),(E, E),(F, F),(G, G),(H, H),(I, I),(J, J),(K, K),(L, L),(M, M),(N, N),(O, O),(P, P),(Q, Q),(R, R),(S, S),(T, T),(U, U),(V, V),(W, W),(X, X),(Y, Y),(Z, Z)]

编辑 3

This is a known issue and will not be fixed to keep compatibility.

    //  - IEnumerator<T> instances from the same IEnumerable<T> party on the same underlying
    //    reader.

【问题讨论】:

    标签: f# lazy-evaluation seq lazy-sequences


    【解决方案1】:

    重用来自File.ReadLines 的序列有什么问题?以下代码对我来说很好用:

    let lines = File.ReadLines "file.txt"
    for line in lines do printfn "%s" line
    for line in lines do printfn "%s" line
    

    无论如何,以下是我对您的问题的回答:

    • 这些在语义上是否都相同(对于只读文件)?

    它们相似,但不相同,因为它们有不同的类型。例如。数组和列表没有完全相同的语义。 (另外,请记住,即使是只读文件也可以删除,这会影响惰性版本。)

    • 只有linesDelaylinesSeqExpr 不会将整个文件读入内存吗?

    不,linesCache 也应该只读取所需的行数。

    • linesList 是否因必须向后组装列表而减慢速度?

    我不这么认为。查看List.ofSeq 原语here 的来源。

    • 这些中的任何一个被认为或多或少是惯用的吗?

    我认为它们都很好,具体取决于具体情况。就个人而言,我经常只使用File.ReadAllLines,除非我有理由相信该文件很大。

    【讨论】:

    • 感谢您提供的信息!不一致的是,它(不抛出)只会给我文件的一部分。我看到this 这表明我不是第一个遇到这个问题的人。我看看能不能得到一致的复制。
    • 添加了重现我看到的问题的代码。
    • 显然这是一个已知问题,不会修复。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-16
    相关资源
    最近更新 更多