根据先前的值创建序列
Create sequence based on previous value
我正在学习 F#(第二天 O:-))并且我想创建 Collatz sequence,其中每个值都是根据前一个值计算的。我知道,何必用 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;
}
或者如何像 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"
但是没有想出序列解...
您可以使用序列表达式:
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)
}
上面的答案可能是最简单的方法,但是,纯粹为了兴趣,有一个非递归的解决方案:
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)
我们使用Seq.unfold
生成数字,直到达到值1
,此时我们发出停止信号。
CollatzState
类型同样可以是另一种 Option
而不是自定义类型,但我认为以这种方式发生的事情更清楚一些。
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; ...]
为了非常忠实地翻译您的命令式 C# 代码,您需要提供变异;作为 ref
单元格或可变绑定。因此,您可以使用序列表达式和突变 - 但最好是 .
也可以将递归解法与memoisation的概念结合起来。字典有助于检索已计算的值,从而停止递归。
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) } )
您可以使用 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)
我正在学习 F#(第二天 O:-))并且我想创建 Collatz sequence,其中每个值都是根据前一个值计算的。我知道,何必用 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;
}
或者如何像 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"
但是没有想出序列解...
您可以使用序列表达式:
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)
}
上面的答案可能是最简单的方法,但是,纯粹为了兴趣,有一个非递归的解决方案:
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)
我们使用Seq.unfold
生成数字,直到达到值1
,此时我们发出停止信号。
CollatzState
类型同样可以是另一种 Option
而不是自定义类型,但我认为以这种方式发生的事情更清楚一些。
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; ...]
为了非常忠实地翻译您的命令式 C# 代码,您需要提供变异;作为 ref
单元格或可变绑定。因此,您可以使用序列表达式和突变 - 但最好是
也可以将递归解法与memoisation的概念结合起来。字典有助于检索已计算的值,从而停止递归。
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) } )
您可以使用 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)