Function reading from Console.In returns previous values

若如初见. 提交于 2020-01-24 09:56:34

问题


In order to learn F# I'm trying to solve the Codingame puzzle The Descent

  • 8 numbers are read from Console.In.ReadLine()
  • the index of the highest number should be printed
  • repeat 8 times

Below code works for the first run, but appears to give the exact same result after the first loop for different inputs.
Running the idxWithHighestValue function in Interactive with an array instead of reading from console gives the desired results.

Expected behavior:
[9,8,7,6,5,4,3,2] => 0
[0,8,7,6,5,4,3,2] => 1

Actual behavior:
[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. *)
    ()

Is there something wrong with my readEightInputs function?


回答1:


The problem is that highestMountain is just an int value, but you want it to be a function that reads console inputs and returns an int i.e. its type signature should be unit -> int instead of just int. Simply adding () (unit) to the declaration makes it a function that can be called—not just a value to be calculated once. Then you can call that function in your loop:

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. *)
    ()

In your example, the code in highestMountain is only evaluated once to get its final int value and there's no need to evaluate it again; as far as F# is concerned its value could never change. Making it a function means you can call it as many times as you need to get different inputs.

Here's one way to refactor this code by extracting generally useful functions:

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
  ()

Instead of nesting all the functions in an omni-function, this extracts them as generally useful, top-level functions and pipes them together; and the omni-function highestMountain is gone. There's also a separation of concerns between reading console lines and converting them to ints. If you still wanted that high-level function, it's now possible to construct it as a composition of your other functions:

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

This is now a function that takes a number (of mountains to read in) and returns the index of the largest one, called like printfn "%i" (highestMountain 8). Note that while the highestMountain definition has no explicit args, it is a function because it's a composition of other functions via >> operator. Its arguments are the same as its first function readLines, and its return type is that of its final function maxValueIndex. This works because the middle (curried) function Seq.map int is compatible with the output of readLines and the input of maxValueIndex.




回答2:


Taylor Wood has already pointed out that highestMountain is just an integer value, not a function. I'll just mention that you ask "Is there something wrong with my readEightInputs function?", but in fact in the code that you've shown us, readEightInputs is also a value, not a function. In F#, this is a value:

let name = (series of expressions)

This is a function:

let name parameter = (series of expressions)

If there's at least one parameter after the name, even if that parameter is the "unit" value (), then it's a function, and the series of expressions after the = character will be evaluated each time you call the function. If there are no parameters, then it's a value that's only evaluated once. (BTW, the "unit" value () is very useful to know about. Think of this value as an empty tuple: there's only one possible value for an empty tuple, so the type is called unit because there can be only one of it).

One more thing I'll mention: your readEightInputs value, even if you turned it into a function, seems a bit silly. Why not just use your readlines function directly? That readlines function is a good, useful function. Instead of idxWithHighestValue readEightInputs, you could instead write that line as idxWithHighestValue (readlines 8). Voila: now if it turns out that you have to read ten lines for a later exercise, you can just call readlines 10. Code reuse is a good thing.



来源:https://stackoverflow.com/questions/48755597/function-reading-from-console-in-returns-previous-values

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!