可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm just starting to learn F# using VS2010 and below is my first attempt at generating the Fibonacci series. What I'm trying to do is to build a list of all numbers less than 400.
let fabList =
let l = [1;2;]
let mutable a = 1
let mutable b = 2
while l.Tail < 400 do
let c = a + b
l.Add(c)
let a = b
let b = c
My first problem is that on the last statement, I'm getting an error message "Incomplete structured construct at or before this point in expression" on the last line. I don't understand what I'm doing wrong here.
While this seems to be an obvious way to build the list in a fairly efficient way (from a c++/C# programmer), from what little I know of f#, this doesn't seem to feel to be the right way to do the program. Am I correct in this feeling?
回答1:
First of all, you're using let
as if it was a statement to mutate a variable, but that's not the case. In F#, let
is used to declare a new value (which may hide any previous values of the same name). If you want to write code using mutation, then you need to use something like:
let c = a + b // declare new local value
l.Add(c)
a <- b // mutate value marked as 'mutable'
b <- c // .. mutate the second value
The second issue with your code is that you're trying to mutate F# list by adding elements to it - F# lists are immutable, so once you create them, you cannot modify them (in particular, there is no Add
member!). If you wanted to write this using mutation, you could write:
let fabList =
// Create a mutable list, so that we can add elements
// (this corresponds to standard .NET 'List<T>' type)
let l = new ResizeArray<_>([1;2])
let mutable a = 1
let mutable b = 2
while l.[l.Count - 1] < 400 do
let c = a + b
l.Add(c) // Add element to the mutable list
a <- b
b <- c
l |> List.ofSeq // Convert any collection type to standard F# list
But, as others already noted, writing the code in this way isn't the idiomatic F# solution. In F#, you would use immutable lists and recursion instead of loops (such as while
). For example like this:
// Recursive function that implements the looping
// (it takes previous two elements, a and b)
let rec fibsRec a b =
if a + b < 400 then
// The current element
let current = a + b
// Calculate all remaining elements recursively
// using 'b' as 'a' and 'current' as 'b' (in the next iteration)
let rest = fibsRec b current
// Return the remaining elements with 'current' appended to the
// front of the resulting list (this constructs new list,
// so there is no mutation here!)
current :: rest
else
[] // generated all elements - return empty list once we're done
// generate list with 1, 2 and all other larger fibonaccis
let fibs = 1::2::(fibsRec 1 2)
回答2:
Other posts tell you how to write the while loop using recursive functions. This is another way by using the Seq library in F#:
// generate an infinite Fibonacci sequence
let fibSeq = Seq.unfold (fun (a,b) -> Some( a+b, (b, a+b) ) ) (0,1)
// take the first few numbers in the sequence and convert the sequence to a list
let fibList = fibSeq |> Seq.takeWhile (fun x -> x<=400 ) |> Seq.toList
for explanation, please ref solution 2 in F# for Project Euler Problems, where the first 50 Euler problems are solved. I think you will be interested in these solutions.
回答3:
let rec fibSeq p0 p1 = seq {
yield p0
yield! fibSeq p1 (p0+p1)
}
回答4:
Here's an infinite tail-recursive solution using sequence expressions. It's quite efficient, producing the 100,000th term in just a few seconds. The "yield" operator is just like C#'s "yield return", and the "yield!" operator may be read as "yield all", where in C# you would have to do "foreach item ... yield return item".
https://stackoverflow.com/questions/2296664/code-chess-fibonacci-sequence/2892670#2892670
let fibseq =
let rec fibseq n1 n2 =
seq { let n0 = n1 + n2
yield n0
yield! fibseq n0 n1 }
seq { yield 1I ; yield 1I ; yield! (fibseq 1I 1I) }
let fibTake n = fibseq |> Seq.take n //the first n Fibonacci numbers
let fib n = fibseq |> Seq.nth (n-1) //the nth Fibonacci number
This approach is similar to the following in C# (which uses a while(true) loop instead of recursion):
Finding Fibonacci sequence in C#. [Project Euler Exercise]
回答5:
Yes, mutable variables and while loops are usually a good sign that your code is not very functional. Also the fibonacci series, doesn't start with 1,2 - it starts with 0,1 or 1,1 depending on who you ask.
Here's how I'd do it:
let rec fabListHelper (a:int,b:int,n:int) =
if a+b < n then
a+b :: fabListHelper (b, a+b, n)
else
[];;
let fabList (n:int) = 0 :: 1 :: fabListHelper (0,1, n);;
(*> fabList 400;;
val it : int list = [0; 1; 1; 2; 3; 5; 8; 13; 21; 34; 55; 89; 144; 233; 377]*)
回答6:
This function "fib" will return a list of Fibonacci numbers that are not greater than 500
let rec fib a b =
let current = a + b
match current with
| _ when current >= 500 -> []
| _ -> current :: fib b current
let testFib = fib 1 2;;
回答7:
One more codata'ish way:
let rec fib = seq {
yield! seq {0..1}
yield! Seq.map (fun(a,b)->a+b) <| Seq.zip fib (Seq.skip 1 fib)
}
let a = fib |> Seq.take 10 |> Seq.toList
回答8:
One with an array:
let fibonacci n = [|1..n|] |> Array.fold (fun (a,b) _ -> b, a + b) (0,1) |> fst
回答9:
One using aggregation (fold):
let fib n =
[1..n] |> List.fold (fun ac _ -> (ac |> List.take 2 |> List.sum) :: ac) [1;1] |> List.rev
回答10:
Here's a good article by .Net guru Scott Hanselman on generating fibonacci series in F#
let rec fib n = if n < 2 then 1 else fib (n-2) + fib(n-1)
http://www.hanselman.com/blog/TheWeeklySourceCode13FibonacciEdition.aspx
It also compares with other languages as a reference
回答11:
The great solution of Scott Hanselman does not report the 0 the fibonacci sequence starts with.
So here is a minor change to his solution to also report the 0.
I used a small list from 0 to 10 to display the first 11 items of the sequence.
let nums=[0..10]
let rec fib n = if n < 1 then 0 else if n < 2 then 1 else fib (n-2) + fib(n-1)
let finres = List.map fib nums
printfn "%A" finres
I'm new and incompetent in relation to f# and still not totally fully understanding the need of it. But found this an interesting test.
Just for fun : If found a formula of Binet that calculates the n-th Fibonacci number.
Unfortunately some floating point functions are needed to get the integer result back in the end :
[Fibonacci formula of Binet][1]
http://i.stack.imgur.com/nMkxf.png
let fib2 n = (1.0 / sqrt(5.0)) * ( (((1.0 + sqrt(5.0)) /2.0)**n) - (((1.0 - sqrt(5.0)) /2.0)**n) )
let fib2res = fib2 10.0
System.Console.WriteLine(fib2res)
let strLine = System.Console.ReadLine()
A quick and dirty translation to f# would be as shown above. I'm sure others can improve on this in matter of style and efficiency. The example calculates the 10th number. The result will be 55.