Reputation: 17845
I'm learning F# (second day O:-)) and I want to create Collatz sequence, where is every value computed based on previous one. I know, ho to do it in C#
public static void Main(string[] args)
{
Console.WriteLine(string.Join(", ", collatz(13)));
}
static IEnumerable<int> collatz(int n)
{
while (n != 1)
{
yield return n;
if (n % 2 == 0)
n /= 2;
else
n = 3 * n + 1;
}
yield return 1;
}
or how to create array like that in F#
let rec collatz = function
| 1 -> [ 1 ]
| n when n % 2 = 0 -> n::collatz (n / 2)
| n -> n::collatz (3 * n + 1)
collatz 13 |> Seq.map string |> String.concat ", " |> printfn "%s"
but don't figure out sequence solution...
Upvotes: 4
Views: 397
Reputation: 28745
You can do it pretty concisely using Seq.unfold
...
let collatzSeq = Seq.unfold <| function
| 1 -> Some(1, -1)
| -1 -> None
| n when n % 2 = 0 -> Some(n, n / 2)
| n -> Some(n, 3 * n + 1)
Upvotes: 7
Reputation: 5741
let collatz n =
let nr = ref n
seq{
while (!nr > 1) do
yield !nr
if !nr % 2 = 0 then
nr := !nr / 2
else
nr := 3 * !nr + 1 }
collatz 13
// val it : seq<int> = seq [13; 40; 20; 10; ...]
For a pretty faithful translation of your imperative C# code, you'd need to provide for mutation; as a ref
cell or a mutable binding. You can therefore use a sequence expression, and mutation - but preferably recursion.
You can also combine the recursive solution with the concept of memoisation. A dictionary facilitates the retrieval of values already computed, stopping the recursion.
let memoRec f =
let d = new System.Collections.Generic.Dictionary<_,_>()
let rec g x =
match d.TryGetValue x with
| true, res -> res
| _ -> let res = f g x in d.Add(x, res); res
g
let collatzRec =
memoRec (fun f n -> seq{
if n <= 1 then
yield 1
else
yield n
if n % 2 = 0 then yield! f (n / 2)
else yield! f (3 * n + 1) } )
Upvotes: 1
Reputation: 12184
The above answer is probably the easiest way of doing this but, purely for the sake of interest, there is a non-recursive solution:
type CollatzState =
|Continue of int
|Stop
let collatz v =
let unfolder =
function
|Stop -> None
|Continue 1 -> Some (1, Stop)
|Continue n when n % 2 = 0 -> Some(n, Continue <| n/2)
|Continue n -> Some (n, Continue <| 3*n+1)
Seq.unfold (unfolder) (Continue v)
We use Seq.unfold
to generate numbers until the value 1
is reached, at which point we signal to stop.
The CollatzState
type could equally well be another Option
instead of a custom type but I think it's a little clearer what's happening this way.
Upvotes: 5
Reputation: 144136
You can use a sequence expression:
let rec collatzSeq n = seq {
match n with
| 1 -> yield 1
| n when n % 2 = 0 ->
yield n
yield! collatzSeq (n / 2)
| n ->
yield n
yield! collatzSeq (3 * n + 1)
}
Upvotes: 6