生成序列使用以前的值

问题描述 投票:2回答:2

我正在学习用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.foldList.fold或类似的东西应该已经在这里使用的,但我仍然无法掌握如何有效地使用它们。


所以,问题是:如何重新写在F#的GenerateSequence功能,所以它会在函数式编程风格,并有更好的表现?

任何其他建议也将受到欢迎。

functional-programming f#
2个回答
3
投票

您的问题的描述是优异的:“序列与初始值开始,并且每个下列项目是应用所述变换功能到该序列中的前一个值的结果。”

这是Seq.unfold方法的完美描述。它有两个参数:初始状态和一个变换函数,并返回其中每个值被从先前状态计算出的序列。有参与其使用可能Seq.unfold没有很好地解释the rather terse documentation一些细微之处:

  1. Seq.unfold预计转换功能,我会从现在请f,返回一个选项。它应该返回None如果序列应该结束,或者Some (...)如果有剩下的序列中的另一个值。您可以创建无限序列这种方式,如果你有去无回None;无限序列是完全罚款,因为F#评估序列懒洋洋的,但你必须要小心,不要永远环比完全的无限序列。 :-)
  2. 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

The difference between Seq.fold and Seq.unfold

记住你是否要使用Seq.foldSeq.unfold最简单的方法就是问自己这两个陈述是真实的:

  1. 我有项目的列表(或阵列,或序列),我想通过在对列表中的项目的重复运行的计算,以产生一个结果值。例如,我想利用这个全数字系列的产品。这是一个折叠操作:我需要很长的名单,“压缩”它(这么说),直到它的一个值。
  2. 我有一个单一的初始值,并产生从电流值的下一个值的功能,并且我想与值的列表(或序列,或阵列)来结束。这是一个展开操作:我取一小起始值和“扩大”它(这么说),直到它的值的整个列表。

6
投票

从@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去。突变可能是“一般邪恶”,但在这种情况下,是不以任何方式破坏引用透明本地化可变变量,所以我不认为这是有害的。

© www.soinside.com 2019 - 2024. All rights reserved.