从Console.In读取的函数返回先前的值

时间:2018-02-12 21:12:49

标签: f#

为了学习F#我正试图解决Codingame之谜The Descent

  • 从Console.In.ReadLine()
  • 中读取8个数字
  • 应打印最高编号的索引
  • 重复8次

下面的代码适用于第一次运行,但似乎在第一次循环后为不同的输入提供完全相同的结果 使用数组在Interactive中运行idxWithHighestValue函数而不是从控制台读取,可以得到所需的结果。

预期行为:
[9,8,7,6,5,4,3,2] => 0
[0,8,7,6,5,4,3,2] => 1

实际行为:
[9,8,7,6,5,4,3,2] => 0
[0,8,7,6,5,4,3,2] => 0

open System

let highestMountain =
    let readEightInputs =
        let readlines n = Array.init n (fun _ -> Console.In.ReadLine() |> int)
        readlines 8
    let idxWithHighestValue list = 
        list
        |> Seq.mapi (fun i v -> i, v)
        |> Seq.maxBy snd
        |> fst
    idxWithHighestValue readEightInputs

(* game loop *)
while true do   
    (* Write an action using printfn *)
    (* To debug: Console.Error.WriteLine("Debug message") *)

    printfn "%i" highestMountain(* The index of the mountain to fire on. *)
    ()

我的readEightInputs函数有问题吗?

2 个答案:

答案 0 :(得分:6)

问题是highestMountain只是int值,但您希望它是函数,它读取控制台输入并且返回 int即其类型签名应为unit -> int,而不仅仅是int。简单地将()(单位)添加到声明中使其成为可以被调用的函数 - 而不仅仅是要计算一次的值。然后你可以在循环中调用该函数:

let highestMountain () =
    let readEightInputs =
        let readlines n = Array.init n (fun _ -> Console.In.ReadLine() |> int)
        readlines 8
    let idxWithHighestValue list = 
        list
        |> Seq.mapi (fun i v -> i, v)
        |> Seq.maxBy snd
        |> fst
    idxWithHighestValue readEightInputs

while true do
    let result = highestMountain()
    printfn "%i" result (* The index of the mountain to fire on. *)
    ()

在您的示例中,highestMountain中的代码仅一次进行评估,以获得其最终int值,并且无需再次对其进行评估;就F#而言,它的价值永远不会改变。使其成为功能意味着您可以根据需要多次调用它来获得不同的输入。

这是通过提取通常有用的函数来重构此代码的一种方法:

let readLines count = // int -> string[]
  [| for _ in 1 .. count -> Console.ReadLine() |]
let maxValueIndex source = // seq<'a> -> int
  source
  |> Seq.mapi (fun i v -> i, v)
  |> Seq.maxBy snd
  |> fst
while true do
  let highMountainIdx = readLines 8 |> Seq.map int |> maxValueIndex
  printfn "%i" highMountainIdx
  ()

不是将所有函数嵌套在泛函数中,而是将它们提取为通常有用的顶级函数并将它们组合在一起;并且全向函数highestMountain消失了。在阅读控制台线路并将它们转换为int之间也存在分离关注点。如果您仍然想要这个高级功能,现在可以将其构建为其他功能的组合

// int -> int
let highestMountain = readLines >> Seq.map int >> maxValueIndex

现在这是一个函数,它接受一些(山脉读入)并返回最大的索引,称为printfn "%i" (highestMountain 8)。请注意,虽然highestMountain定义没有明确的args,但 是一个函数,因为它是>>运算符组成的其他函数。它的参数与第一个函数readLines相同,其返回类型是其最终函数maxValueIndex。这是有效的,因为中间(curried)函数Seq.map intreadLines的输出和maxValueIndex的输入兼容。

答案 1 :(得分:3)

Taylor Wood已经指出highestMountain只是一个整数值,而不是一个函数。我只想提一下你问“我的readEightInputs函数有什么问题吗?”,但实际上在你向我们展示的代码中,readEightInputs 也是一个值,不是一个功能。在F#中,这是一个值:

let name = (series of expressions)

这是一个功能:

let name parameter = (series of expressions)

如果名称后面至少有一个参数,即使该参数是“单位”值(),那么它也是一个函数,每次调用时都会评估=字符后的一系列表达式功能。如果没有参数,那么它只是一次评估的值。 (顺便说一下,“单位”值()对于了解非常有用。可以将此值视为空元组:空元组只有一个可能的值,因此类型称为unit,因为只能有一个)。

我还要提到的另一件事:你的readEightInputs值,即使你把它变成一个函数,看起来有点傻。为什么不直接使用您的readlines功能? readlines函数是一个很好的,有用的函数。您可以将该行写为idxWithHighestValue readEightInputs,而不是idxWithHighestValue (readlines 8)。 Voila:现在如果事实证明你必须阅读10行以便以后练习,你可以拨打readlines 10。代码重用是一件好事。