我正在学习用F#函数式编程,我想写这将产生一个序列对我来说功能。
有一个一些预定函数用于转化的值,并且在功能我需要写应该有两个输入 - 起始值和所述序列的长度。序列与所述初始值开始,并且每个下列项目是应用所述变换功能到该序列中的前一个值的结果。
在C#中我通常会写这样的事情:
public static IEnumerable<double> GenerateSequence(double startingValue, int n)
{
double TransformValue(double x) => x * 0.9 + 2;
yield return startingValue;
var returnValue = startingValue;
for (var i = 1; i < n; i++)
{
returnValue = TransformValue(returnValue);
yield return returnValue;
}
}
当我试图把这种功能,F#,我做了这个:
let GenerateSequence startingValue n =
let transformValue x =
x * 0.9 + 2.0
seq {
let rec repeatableFunction value n =
if n = 1 then
transformValue value
else
repeatableFunction (transformValue value) (n-1)
yield startingValue
for i in [1..n-1] do
yield repeatableFunction startingValue i
}
没有与此实现两个明显的问题。
第一是,因为我试图避免使一个可变的值(以C#实现returnValue
可变的类比),我没有同时产生序列重用前计算的值。这意味着,该序列的100元我必须作出额外99个呼叫transformValue
功能,而不只是一个的(正如我在C#实现所做的那样)。这与恶臭极为恶劣的表现。
二是整体功能似乎并没有按照函数式编程写入。我敢肯定,有更优雅和紧凑的实现。我怀疑Seq.fold
或List.fold
或类似的东西应该已经在这里使用的,但我仍然无法掌握如何有效地使用它们。
所以,问题是:如何重新写在F#的GenerateSequence
功能,所以它会在函数式编程风格,并有更好的表现?
任何其他建议也将受到欢迎。
您的问题的描述是优异的:“序列与初始值开始,并且每个下列项目是应用所述变换功能到该序列中的前一个值的结果。”
这是Seq.unfold
方法的完美描述。它有两个参数:初始状态和一个变换函数,并返回其中每个值被从先前状态计算出的序列。有参与其使用可能Seq.unfold
没有很好地解释the rather terse documentation一些细微之处:
Seq.unfold
预计转换功能,我会从现在请f
,返回一个选项。它应该返回None
如果序列应该结束,或者Some (...)
如果有剩下的序列中的另一个值。您可以创建无限序列这种方式,如果你有去无回None
;无限序列是完全罚款,因为F#评估序列懒洋洋的,但你必须要小心,不要永远环比完全的无限序列。 :-)Seq.unfold
还预计,如果f
返回Some (...)
,它将返回不仅仅是下一个值,但未来值的元组和下一个状态。这显示在文档,其中该状态实际上是当前值和先前值,这将被用于计算出的下一个值的元组在Fibonacci例子。文档示例没有说的很清楚,所以这就是我认为这是一个更好的例子:
let infiniteFibonacci = (0,1) |> Seq.unfold (fun (a,b) ->
// a is the value produced *two* iterations ago, b is previous value
let c = a+b
Some (c, (b,c))
)
infiniteFibonacci |> Seq.take 5 |> List.ofSeq // Returns [1; 2; 3; 5; 8]
let fib = seq {
yield 0
yield 1
yield! infiniteFibonacci
}
fib |> Seq.take 7 |> List.ofSeq // Returns [0; 1; 1; 2; 3; 5; 8]
并要回你的GenerateSequence
的问题,我会写这样的:
let GenerateSequence startingValue n =
let transformValue x =
let result = x * 0.9 + 2.0
Some (result, result)
startingValue |> Seq.unfold transformValue |> Seq.take n
或者,如果你需要包括序列中的初始值:
let GenerateSequence startingValue n =
let transformValue x =
let result = x * 0.9 + 2.0
Some (result, result)
let rest = startingValue |> Seq.unfold transformValue |> Seq.take n
Seq.append (Seq.singleton startingValue) rest
记住你是否要使用Seq.fold
或Seq.unfold
最简单的方法就是问自己这两个陈述是真实的:
从@rmunn答案显示了使用unfold
一个相当不错的解决方案。我认为有其他两个选项值得考虑,这实际上只使用一个可变变量和使用递归序列表达式。选择可能是个人喜好的问题。另外两种选择是这样的:
let generateSequenceMutable startingValue n = seq {
let transformValue x = x * 0.9 + 2.0
let mutable returnValue = startingValue
for i in 1 .. n do
yield returnValue
returnValue <- transformValue returnValue }
let generateSequenceRecursive startingValue n =
let transformValue x = x * 0.9 + 2.0
let rec loop value i = seq {
if i < n then
yield value
yield! loop (transformValue value) (i + 1) }
loop startingValue 0
我修改你的逻辑稍微让我不必yield
两次 - 我只是做更新值之前的迭代和产量的一个步骤。这使得generateSequenceMutable
功能相当简单,易于理解。该generateSequenceRecursive
实现使用递归相同的逻辑,也是相当不错的,但我觉得有点不太清楚。
如果你想使用这些版本之一,并产生无限序列从你需要你就可以采取许多元素,你可以改变for
在第一种情况下,以while
或删除if
在第二种情况下:
let generateSequenceMutable startingValue n = seq {
let transformValue x = x * 0.9 + 2.0
let mutable returnValue = startingValue
while true do
yield returnValue
returnValue <- transformValue returnValue }
let generateSequenceRecursive startingValue n =
let transformValue x = x * 0.9 + 2.0
let rec loop value i = seq {
yield value
yield! loop (transformValue value) (i + 1) }
loop startingValue 0
如果我在写这个,我可能要么与可变变量或unfold
去。突变可能是“一般邪恶”,但在这种情况下,是不以任何方式破坏引用透明本地化可变变量,所以我不认为这是有害的。