【问题标题】:Mutual recursion with many parameters多参数的相互递归
【发布时间】:2011-08-23 11:31:04
【问题描述】:

假设我有一堆标记为 A-Z 的组件...我首先向每个组件发送一个 1.0 的值,每个组件返回一个双精度值(比如 a_0、b_0、..、z_0)。下一次迭代我将总和 (1.0 + a_0 + ... + z_0) 发送到每个组件,以获得 26 个新的双精度数 (a_1,...,z_1) 和一个新值 (1.0 + a_0 + ... +z_0 + ... + a_1 + ... + z_1)。计算每天都以这种方式继续。

问题在于每个组件本身都是递归的,并且依赖于前几天的 20 个左右的值。因此,最明显的递归实现变得混乱,因为组件计算的每个独立路径都有大量冗余递归调用。

在我当前的实现中,我将组件分成多个代理,他们负责自己的状态并使用消息传递来完成计算。我现在需要对我的模型进行更改,但我发现这种实现并不灵活。

我的新想法是使用不可变对象来保存组件的状态,每次迭代我都会克隆我的组件对象并使用区分联合更新状态。即

Component(oldcomponent, [Parameter1(22.0), Parameter14(10.0)])

将有状态 oldcomponent 但更新参数 1 和 14。因此,由于大多数路径仅更新少数参数,因此组件计算的每个路径都易于阅读。作为奖励,我可以将计算分成一系列函数,这些函数将突变列表作为输入并输出新的突变列表。

但是,我觉得这个问题非常适合函数式语言,我有点偏离函数式设计,这很好,但我很好奇其他人会如何解决这个问题?

编辑:

我认为当我可以使用带有“with 语法”的记录来传递它时,区分联合方面是没有意义的。

【问题讨论】:

  • 基于代理的消息传递风格对我来说看起来不错 - 所以当然它很好的设计来传递 20-values arround - 为什么不呢?
  • 我将重新考虑基于代理的方法,我现在需要对我的模型进行更改,这涉及使用分布在多个代理中的参数,我觉得实现此更改比它应该的更复杂是...就传递大约 20 个参数而言,当涉及到使用我的模型时,当我需要添加一两个参数并且我发现我自己更新了六个递归调用来解决它时,它开始感到多余.
  • 我认为查看您的代码会有所帮助。

标签: f#


【解决方案1】:

如果我正确理解了您的问题,则可以使用以下代码示例对问题进行建模:

//Rec function type to make state full functions
type RecFn = RecF of (double -> double * RecFn)

//State type for component A
type StateA = double
//Component A
let compa = 
    let startState : StateA = 1.0
    let rec run (st:StateA) (i:double) = 
        (i+st) , (RecF (run (st+1.0)))
    RecF (run startState)

//State type for component B
type StateB = double
//Component B
let compb = 
    let startState : StateA = 1.0
    let rec run (st:StateA) (i:double) = 
        (i*st) , (RecF (run (st+1.0)))
    RecF (run startState)

//Main rec function
let rec execute (input : double) (fns : RecFn list) (count:int) = 
    let (vals, newFns) = 
        fns 
        |> List.map (function RecF v -> v input)
        |> List.unzip
    match count with
    | 0 -> vals
    | _ -> 
        let newSum = (vals |> List.sum) + input
        execute newSum newFns (count-1)

//Start with 1.0 as initial value for compa and compb
execute 1.0  [compa; compb] 5
|> printfn "%A"

为您的状态建模的最佳方法是为每个组件创建记录类型(在此示例中,状态只是一个双精度值)。 为了加快速度,您甚至可以在将组件应用于双精度值时使用 PSeq 模块来获取双精度值列表。

我已经使用 count 值让它运行了 5 次,如果你需要运行很多天,你可以传递一个回调函数来代替 count 并在每次新的 double 列表时从 execute 方法调用该回调生成以便回调可以在需要时进行进一步处理。

【讨论】:

    【解决方案2】:

    您的基本问题是一个 map-reduce 问题,可以如下解决:

    > let next components f x =
        1.0 + Array.reduce (+) (Array.map (f x) components);;
    val next : 'a [] -> ('b -> 'a -> float) -> 'b -> float
    

    函数f 接受输入值(例如1.0)和它必须建模的组件。使用输入值将其映射到每个组件会给出结果数组。在我们添加 1.0 之前,使用 + 函数对这个数组进行归约会对结果求和。

    您描述的下一个问题是每个组件都需要自己独立的累加器的变体。可以这样写:

    > let next f (x, accumulators) =
        let ys = Array.map (f x) accumulators
        1.0 + Array.sumBy fst ys, Array.map snd ys;;
    val next : 'a [] -> ('b -> 'a -> float) -> 'b -> float
    

    f 现在返回一个包含结果和累加器的对。

    请注意,纯度在这里并不有利。命令式解决方案只是:

    > let next f x (accumulators: _ []) =
        for i=0 to accumulators.Length-1 do
          accumulators.[i] <- f(x, snd accumulators.[i])
        1.0 + Array.sumBy fst accumulators;;
    

    f 现在就地改变累加器。

    在我当前的实现中,我将组件分成多个代理,他们负责自己的状态并使用消息传递来完成计算

    我不会为此使用代理。它们是用于并发编程的,这个问题没有并发。

    【讨论】:

      猜你喜欢
      • 2016-06-13
      • 1970-01-01
      • 2011-03-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-19
      • 1970-01-01
      相关资源
      最近更新 更多