问题
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