Взаимная рекурсия со многими параметрами - PullRequest
1 голос
/ 23 августа 2011

Предположим, у меня есть набор компонентов, помеченных 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. Таким образом, каждый путь вычисления компонента будет легко читаться, так как большинство путей обновляют только несколько параметров. И в качестве бонуса я могу разделить вычисления на ряд функций, которые принимают список мутаций в качестве входных данных и выводят новый список мутаций.

Однако я чувствую, что эта проблема хорошо подходит для функционального языка, и я несколько отклоняюсь от функционального дизайна, что хорошо, но мне любопытно, как другие будут решать эту проблему?

Edit:

Я полагаю, что аспект различающего объединения не имеет смысла, когда я мог бы использовать запись с «синтаксисом» для ее передачи.

Ответы [ 2 ]

1 голос
/ 09 декабря 2011

Ваша основная проблема - это проблема уменьшения карты, которую можно решить следующим образом:

> 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 теперь мутирует аккумулятор на месте.

В моей текущей реализации я разделил компоненты на несколько агентов, которые отвечают за собственное состояние и используют передачу сообщений для завершения вычисления

Я бы не использовал агентов для этого. Они предназначены для параллельного программирования, и в этой проблеме нет ничего параллельного.

1 голос
/ 23 августа 2011

Если я правильно понял ваш вопрос, то проблему можно смоделировать с помощью приведенного ниже примера кода:

//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 раз. Если вам нужно запустить много дней, вы можете передать функцию обратного вызова вместо счетчика и вызывать этот обратный вызов из метода execute каждый раз, когда создается новый список типа double. генерируется так, чтобы обратный вызов мог выполнять дальнейшую обработку, если требуется.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...