As already suggested above, you'd better use seq instead of list. I just fired up fsi and got the code below, maybe not as elegant as you expect but should be just fine. Btw, what's the correct answer? I'm too lazy to register to play with euler.

1
2
3
4
5
6
7
> let rec fibs a b =
  { if a > 1000000 then yield! []
    else
      if a%2 = 0 then yield a
      yield! fibs b (a+b) } in
  fibs 1 1 |> Seq.fold1 (+);;
val it : int = 1089154
By on 12/18/2007 6:27 AM ()

Thank you all for the answers, they helped a lot. I do realise that lists are not the ideal type for that kind of calculation but with some (extremely minor) modifications to d2bg's code I got myself a list.

1
2
3
4
5
6
7
let rec fiblist a b =
   [if a > 1000000 then yield! []
    else
        if a%2 = 0 then yield a
        yield! fiblist b (a+b)]

print_any (fiblist 1 1)

[2; 8; 34; 144; 610; 2584; 10946; 46368; 196418; 832040]

I feel I'm starting to get a hang of lists and the different way you can create and modify them. Atleast the basics. I do have some follow up questions though:

1. yield and yield!, I have no experience with those from before and I found little resources when I searched around. Anyone know a good basic description on how they work and their general purpose, for example do they exists in a similar way in oCaml so I can find a book on that subject where they are described?

2. When are lists better to use than arrays? I know arrays are better when it comes to lookup but is there a similar way these singly-linked lists are clearly better so I should choose one instead of an array to solve a particular type of problem?

3. There exists three different implementations of fold_left (fold1_left, fold_left, fold_left2), is there any practical difference between them?

Thank you again for all the help.

(Yes, 1089154 is the correct answer to the problem)

By on 12/18/2007 10:54 AM ()

<i><b>3. There exists three different implementations of fold_left (fold1_left, fold_left, fold_left2), is there any practical difference between them?</b><i> fold_left is the "standard" folding function that takes a list of elements <code lang=fsharp>[i0, i1, ..., in]</code> and a starting value <c>s</c>. Then, it runs the following computation, returning the answer: <code lang=fsharp>f (... (f (f s i0) i1) ...) in</code>. fold1_left is similar to fold_left, except that it doesn't take a seed value. Instead, it uses the first element of the list as the seed. So, it return the final answer for: <code lang=fsharp>f (... (f (f i0 i1) i2) ...) in</code>. fold_left2 is the same as fold_left, except that it works on 2 lists simultaneously. So, the function <c>f</c> that it takes must take 3 arguments instead of 2. It's calculation is of the form: <code lang=fsharp>f (... (f (f s i0 j0) i1 j1) ...) in jn</code>. Note that the 2 lists should be of the same length to avoid exceptions and other runtime errors. So, in effect, it depends on what you want to do. The majority of the time, I've only used the standard fold_left, but that's mainly because I haven't needed the other 2. I'd say they mainly serve as "syntactic sugar", as you have do everything you need with just fold_left. <b>EDIT:</b> If you look at the source code for list.fs that comes with the F# distribution, you will see that fold1_left is actually implemented using fold_left. On the other hand, fold_left2 has a free-standing implementation. Regards, z.

By on 12/18/2007 2:50 PM ()

Ah, I see. Thank you for the explanation. I had not even thought of actually looking at the source code before, good tip.

By the way, I found a nice explanation of yield and yield! at Tomasp's site:

[link:tomasp.net]

By on 12/19/2007 6:53 AM ()

List's are actually a bad idea for this problem, the reason Seq's are so useful here is that the actual elements of the Seq aren't stored in memory but are calculated as we need them so we never actually store the Fibonacci numbers, which would be a bad thing since we are only interested in the sum. With list's, however, the values are stored by the list so we would be creating a list of one million elements when we don't need to.

Chris's solution in the blog can actually be made slightly simpler, all we are after is the sum smaller then one million so it doesn't actually matter weather the Seq contains one million elements or ten million elements as long as there is enough for the sum to pass one million. In addition, because, Seq's aren't stored in memory there's nothing to stop us from creating an infinite sequence with all of the numbers of the fibonacci sequence (this is mentioned at the end of the blog) so you could just do:

let problem2 =
(1,1)
|> Seq.unfold (fun (n0,n1) -> Some (n0, (n1, n0 + n1)))
|> Seq.fold
(fun acc x ->
if x % 2 = 0 && x < 1000000 then
acc + x
else
acc) 0

Edit: I just noticed I misread the question but I'll leave this post anyway.

By on 12/17/2007 6:45 PM ()

First off, I'm glad to see that people actually do read my blog :)

I've written up a quick solution to the generation step:

// Gets the next number in the fibonacci sequence given the previous sequence in reverse

let genNextFib reverseFibs = [List.hd reverseFibs + List.hd (List.tl reverseFibs)] @ reverseFibs

// Generates the first x fibonacci numbers as a list, in reverse

let rec getFibsAsList x =

match x with

| x when x < 1 -> failwith "Error: x must be positive"

| 1 -> [1];

| 2 -> [1;1];

| 3 -> [2;1;1];

| 4 -> [3;2;1;1]

| x -> genNextFib (getFibsAsList (x - 1))

/// Calculates the list of fibonacci numbers less that 1E6

let mutable i = 1

let mutable fibsList = [1]

while fibsList.Head < 1000000 do

fibsList <- getFibsAsList i

i <- i + 1

// Reverse it

fibsList <- List.rev fibsList

The biggest challenge when working with lists is to avoid unnessecarily itterating through the elements (wasting time) and to take advantage of tail recursion (wasting stack space). Consider the following code (but please, don't put it into fsi.exe!)

let rec getLastTwoOfList list =

match list with

| first :: second :: [] -> (first, second)

| first :: second :: third -> getLastTwoOfList list.Tail

| _ -> failwith "List should have at least two elements."

(* Don't use this, will chew up stack space and crash fsi.exe!*)

let rec getFibList x =

match x with

| high when high > 100 -> failwith "Will cause stack overflow..."

| _ when x < 1 -> failwith "Error: x must be positive"

| 1 -> [1]

| 2 -> [1;1]

| 3 -> [1;1;2]

| 4 -> [1;1;2;3]

| x -> let allFibsBefore = getFibList (x - 1)

let (x_minus_1, x_minus_2) = getLastTwoOfList allFibsBefore

allFibsBefore @ [x_minus_1 + x_minus_2]

Although perhaps a tad easier to understand and read, the problem here is that 1.) it is slow. Notice that since the fib list is kept in order, to get the last two elements you are forced to walk through each element of the list. Which is on order O(n^2) to generate the first n elements. Also, it isn't tail recursive so you run the risk of a StackOverflow. (Jomo's Blog has a great post on Tail Recursion.)

Hope that helps!

-Chris

By on 12/17/2007 5:05 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper