问题
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 int
s. 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