问:F#:遍历高分文件并选择前 3 名

Q: F#: Iterate through highscore file and pick top 3

为了掌握 F#,我正在制作一款袖珍游戏。 我正处于想要实现某种高分列表的地步。

到目前为止,我正在将 NameScoreTime 写入一个文件然后读入显示所有先前分数的应用程序。 是的,这并不理想,因为列表增长得很快。

我有点想挑前3个分数,不关心姓名时间

问题: 我应该将文件读入 array/list 并从那里选出最高分还是有更好的方法直接选出最高分从文件?

热烈欢迎指点、代码、提示和技巧。

let scoreFile = sprintf ("Name: %s\nTime: %i sec\nScore: %d\n\n") name stopWatch.Elapsed.Seconds finalScore
let readExistingFile = File.ReadAllText ("hiscore.txt")
File.WriteAllText ("hiscore.txt", scoreFile + readExistingFile)
let msg = File.ReadAllText ("hiscore.txt")
printfn "\nHighscores:\n\n%s\n\n\n\nPress ANY key to quit." msg

Should I read the file into an array/list and from there pick out the top scores or is there a nicer way to pick out the top scores directly from the file?

除非分数已经在文件中排序,否则您必须通读所有分数才能找出前 3 名。您的文件现在的写入方式,解析回数据可能有点困难 - 分数存储在多行中,因此您必须处理它。

假设文件不必是人性化的,我会改为使用逗号分隔值列表。打开文件对人类来说更难阅读,但它使它 lot 在您的程序中更容易解析。例如,如果这些行看起来像 Name,Time,Score,则可以这样解析它们:

type ScoreData = {
    Name  : string
    Time  : string // could be a DateTime, using string for simplicity
    Score : int
}

let readHighScores file =
    File.ReadAllLines file
    |> Array.choose (fun line ->
        match line.Split ',' with
        | [| name; time; score |] ->
            {
                Name  = name
                Time  = time
                Score = (int)score // This will crash if the score isn't an integer - see paragraph below.
            }
            |> Some
        | _ ->
            // Line doesn't match the expected format, we'll just drop it
            None
    )
    |> Array.sortBy (fun scoreData -> -scoreData.Score) // Negative score, so that the highest score comes first
    |> Seq.take 3

这将通读您的文件并输出三个最大的分数。使用 Array.choose 允许您只保留符合您期望的格式的行。这还允许您根据需要添加额外的验证,例如确保分数是一个整数并且可能将时间解析为 System.DateTime 而不是将其存储为 int.

然后您可以通过执行以下操作来打印您的高分:

let highScores = readHighScores "hiscore.txt"
printfn "High scores:"
highScores
|> Seq.iteri (fun index data ->
    printfn "#%i:" (index + 1)
    printfn "  Name:  %s" data.Name
    printfn "  Time:  %s" data.Time
    printfn "  Score: %i" data.Score
)

这会调用之前定义的函数并打印返回的每个分数 - 在本例中为前 3 名。使用 Seq.iteri,除了分数数据本身之外,您还可以在输出中包含索引。使用我编造的一些数据,它最终看起来像这样:

High scores:
#1:
  Name:  Awots
  Time:  2015-06-15
  Score: 2300
#2:
  Name:  Roujo
  Time:  2016-03-01
  Score: 2200
#3:
  Name:  Awots
  Time:  2016-03-02
  Score: 2100

现在,可能有一种方法可以在不将整个文件一次加载到内存中的情况下执行此操作,但我认为这不值得,除非你有一个非常大的文件 - 在这种情况下你可能想要保持排序或使用更适合的存储方法,如数据库。