2015-05-15 2 views
8

Есть ли альтернатива для конструкции F # «seq» в Эрланге? Например, в F # можно написать O (1) памяти интегрировать функциюErlang альтернатива последовательности f #

let integrate x1 x2 dx f = 
    let N = int (abs (x2-x1)/dx) 
    let sum = seq { for i in 0..N do yield dx*f(x1 + dx * (double i)) } 
       |> Seq.sum 
    if x2>x1 then sum else -sum 

В Erlang, я есть реализация, которая использует списки, и, следовательно, имеет O (п) требования к памяти что неприемлемо для такой простой функции ,

create(Dx, N)->[0| create(Dx, N,[])]. 

create(Dx, 0, Init)->Init; 
create(Dx, N, Init)->create(Dx,N-1, [Dx*N |Init]). 

integral(X1,X2,Dx, F) -> 
    N=trunc((X2-X1)/Dx), 
    Points = create(Dx,N),  
    Vals = lists:map(fun(X)->F(X)*Dx end, Points), 
    lists:sum(Vals). 

ответ

6

Отказ от ответственности: нижеследующее написано в предположении, что Эрланг полностью запрещает мутацию, о которой я не уверен, потому что я не знаю Эрланга достаточно хорошо.

Seq внутренне мутационный. Он поддерживает «текущее состояние» и мутирует его на каждой итерации. Так что, когда вы делаете одну итерацию, вы получаете «следующее значение», но вы также получаете побочный эффект, который изменил внутреннее состояние счетчика, и когда вы выполните следующую итерацию, вы получите другое «следующее значение», , и так далее. Обычно это красиво покрывается функционально выглядящими соображениями, но если вы когда-либо работали с IEnumerator, вы увидите нечистоту невооруженным глазом.

Другой способ подумать об этом, учитывая «последовательность», вы получаете два результата: «следующее значение» и «остальная последовательность», а затем «остальная последовательность» становится вашей новой «последовательностью» и вы можете повторить этот процесс. (И оригинальная «последовательность» навсегда ушла)

Эта линия мысли может быть непосредственно выражена в F #:

type MySeq<'a> = MySeq of (unit -> ('a * MySeq<'a>)) 

Значения: «ленивая последовательность представляет собой функцию, которая при их применении, возвраты его голову и хвост, где хвост - еще одна ленивая последовательность ». MySeq of включен, чтобы сохранить тип бесконечным.
(извините, я буду использовать F #, я не знаю, Erlang достаточно хорошо, я уверен, что вы можете перевести)

Но затем, видя, как последовательности, как правило, конечно, то все это должно быть факультативным:

type MySeq<'a> = MySeq of (unit -> ('a * MySeq<'a>) option) 

Учитывая это определение, вы можете тривиальным сделать некоторые конструкторы:

module MySeq = 
    let empty = MySeq <| fun() -> None 
    let cons a rest = MySeq <| fun() -> Some (a, rest) 
    let singleton a = cons a empty 
    let rec repeat n a = 
     if n <= 0 then empty 
     else MySeq <| fun() -> Some (a, (repeat (n-1) a)) 
    let rec infinite a = MySeq <| fun() -> Some (a, infinite a) 
    let rec ofList list = 
     match list with 
     | [] -> empty 
     | x :: xs -> MySeq <| fun() -> Some (x, ofList xs) 

Карта и сгиб также тривиальна:

let rec map f (MySeq s) = MySeq <| fun() -> 
    match s() with 
    | None -> None 
    | Some (a, rest) -> Some (f a, map f rest) 

let rec fold f acc0 (MySeq s) = 
    match s() with 
    | None -> acc0 
    | Some (a, rest) -> fold f (f acc0 a) rest 

И от fold вы можете построить все, что не является ленивой последовательностью. Но строить ленивые последовательности, вам нужен «качению складку» (иногда называемый «сканирование»):

let rec scan f state0 (MySeq s) = MySeq <| fun() -> 
    match s() with 
    | None -> None 
    | Some (a, rest) -> 
     let newState = f state0 a 
     Some (newState, scan f newState rest) 

// reformulate map in terms of scan: 
let map f = scan (fun _ a -> f a) Unchecked.defaultof<_> 

Вот как его использовать:

let emptySeq = MySeq.empty 
let numbers = MySeq.ofList [1; 2; 3; 4] 
let doubles = MySeq.map ((*) 2) numbers // [2; 4; 6; 8] 
let infiniteNumbers = 
    MySeq.infinite() 
    |> MySeq.scan (fun prev _ -> prev+1) 0 
let infiniteDoubles = MySeq.map ((*) 2) infiniteNumbers 

И в заключение я хотел бы добавьте, что решение на основе мутации почти всегда будет более эффективным (при прочих равных условиях), по крайней мере, немного. Даже если вы сразу выбросите старое состояние при вычислении нового, память все еще нуждается в исправлении, что само по себе является хитом производительности. Преимущества неизменности не включают настройку производительности.

Update:
Вот моя трещина в Erlang версии. Имейте в виду, что это самый первый код, который я когда-либо писал в Erlang. Таким образом, я уверен, что есть лучшие способы кодирования этого и что для этого уже есть библиотека.

-module (seq). 
-export ([empty/0, singleton/1, infinite/1, repeat/2, fold/3, scan/3, map/2, count/1]). 

empty() -> empty. 
singleton(A) -> fun() -> {A, empty} end. 
infinite(A) -> fun() -> {A, infinite(A)} end. 

repeat(0,_) -> empty; 
repeat(N,A) -> fun() -> {A, repeat(N-1,A)} end. 

fold(_, S0, empty) -> S0; 
fold(F, S0, Seq) -> 
    {Current, Rest} = Seq(), 
    S1 = F(S0, Current), 
    fold(F, S1, Rest). 

scan(_, _, empty) -> empty; 
scan(F, S0, Seq) -> fun() -> 
    {Current, Rest} = Seq(), 
    S1 = F(S0, Current), 
    {S1, scan(F, S1, Rest)} 
end. 

map(F, Seq) -> scan(fun(_,A) -> F(A) end, 0, Seq). 
count(Seq) -> fold(fun(C,_) -> C+1 end, 0, Seq). 

Использование:

1> c(seq). 
{ok,seq} 
2> FiveTwos = seq:repeat(5,2). 
#Fun<seq.2.133838528> 
3> Doubles = seq:map(fun(A) -> A*2 end, FiveTwos). 
#Fun<seq.3.133838528> 
5> seq:fold(fun(S,A) -> S+A end, 0, Doubles). 
20 
6> seq:fold(fun(S,A) -> S+A end, 0, FiveTwos). 
10 
11> seq:count(FiveTwos). 
5 
+0

Благодарим за помощь, но ваш код не компилируется под VS 2012. В разделе «Некоторые (a, repeat (n-1) a)» или « Некоторые (a, бесконечные a) "он жалуется:' Тип несоответствия. Ожидание 'a ' a , но с учетом блока -> ('b *' a) Результирующий тип будет бесконечным при объединении опции '' a 'и' unit -> ('b *' a) ' К сожалению, я знаю Erlang около 3 часов, поэтому я не знаю, как перевести большую часть этого, особенно когда я не могу смотреть, как он работает. –

+0

@JacekGajek, вы должны простить меня за некомпилирующий код, я печатал его на своем телефоне в то время. Я думал, что идея должна быть достаточно ясной сама по себе и включала только код для иллюстрации. Я заменил все примеры на правильный, проверенный код. –

+0

@JacekGajek Я также выпустил версию Erlang только сейчас, не стесняйтесь использовать ее для справки. –

5

Это не проверено, но это один из способов сделать это.

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

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

Другой способ заключается в реализации ленивых потоков, основанный на идее о том, что любой Expr может быть отложено на fun() -> Expr end пожалуй, лучше всего записать в виде -define(DELAY(X), fun() -> X end). как макрос, а затем используется вместе с -define(FORCE(X), X()).

-module(z). 

-export([integral/4]). 

create(Dx, N) -> 
    spawn_link(fun() -> create_loop(Dx, N) end). 

create_loop(Dx, 0, Acc)-> 
    receive 
     {grab, Target} -> Target ! done, 
     ok 
    after 5000 -> 
     exit(timeout_error) 
    end; 
create_loop(Dx, N, Acc) -> 
    receive 
     {grab, Target} -> 
      Target ! {next, Dx*N}, 
      create_loop(Dx, N-1) 
    after 5000 -> 
     exit(timeout_error) 
    end. 

next(Pid) -> 
    Pid ! {grab, self()}, 
    receive 
     {next, V} -> {next, V}; 
     done -> done 
    after 5000 -> 
     exit(timeout_error) 
    end. 

sum(F, Points, Acc) -> 
    case next(Points) of 
     {next, V} -> sum(F, Points, Acc + F(V)); 
     done -> Acc 
    end. 

integral(X1, X2, Dx, F) -> 
    N = trunc((X2 - X1)/Dx), 
    Points = create(Dx, N), 
    sum(fun(X) -> F(X) * Dx end, Points, 0). 

Решение, основанное на ЗАДЕРЖКИ/FORCE - это что-то вроде:

-module(z). 
-define(DELAY(X), fun() -> X end). 
-define(FORCE(X), X()). 

create(Dx, N) -> 
    [0 | ?DELAY(create_loop(Dx, N))]. 

create_loop(Dx, N) -> 
    [Dx*N | ?DELAY(create_loop(Dx, N-1)]; % This is an abuse of improper lists 
create_loop(_, 0) -> []. 

map(F, []) -> []; 
map(F, [V | Thunk]) -> 
    [F(V) | ?DELAY(map(F, ?FORCE(Thunk)))]. 

sum([], Acc) -> Acc; 
sum([V | Thunk], Acc) -> 
    sum(?FORCE(Thunk), V + Acc). 

integral(X1,X2,Dx, F) -> 
    N = trunc((X2-X1)/Dx), 
    Points = create(Dx, N), 
    Vals = map(fun(X) -> F(X)*Dx end, Points), 
    sum(Vals). 

Но не проверено.

+0

Путь слишком сложный ... Многопоточность - еще один шаг вперед. Я знаю, что предпочел бы иметь самое простое решение. –

+0

Если вы немного почистите его, это не сложно.Большая часть структуры может быть выгружена в библиотеку. Кроме того, он имеет добавленную стоимость, что он может создавать значения параллельно с вашими вычислениями, что хорошо для некоторых проблем. Тем не менее, я добавил идею DELAY/FORCE, чтобы сделать явный поток. –

2

Самый популярный способ создания памяти стабильной обработки является определение хвост рекурсивной функции. Например:

integrate_rec(X1, X2, DX, F) when X2 >= X1 -> 
    integrate_rec(X1, X2, DX, F, X1, 0, 1); 
integrate_rec(X1, X2, DX, F) when X2 < X1 -> 
    integrate_rec(X2, X1, DX, F, X2, 0, -1). 

integrate_rec(X1, X2, _DX, _F, X, Sum, Sign) when X >= X2 -> 
    Sign*Sum; 
integrate_rec(X1, X2, DX, F, X, Sum, Sign) -> 
    integrate_rec(X1, X2, DX, F, X + DX, Sum + DX*F(X), Sign). 

Но это не выглядит понятно ... У меня была та же проблема, раз и сделали short helper for function, что позволяет перебирать без списков:

integrate_for(X1, X2, DX, F) -> 
    Sign = if X2 < X1 -> -1; true -> 1 end, 
    Sum = (for(0, {X1, X2, Sign*DX}))(
      fun (X, Sum) -> 
       Sum + DX*F(X) 
      end), 
    Sign*Sum. 

К сожалению, это немного немного медленнее, чем прямая рекурсия:

benchmark() -> 
    X1 = 0, 
    X2 = math:pi(), 
    DX = 0.0000001, 
    F = fun math:sin/1, 
    IntegrateFuns = [fun integrate_rec/4, fun integrate_for/4], 
    Args = [X1, X2, DX, F], 
    [timer:tc(IntegrateFun, Args) || IntegrateFun <- IntegrateFuns]. 

> [{3032398,2.000000000571214},{4069549,2.000000000571214}] 

Так что ~ 3.03s до ~ 4.07s - не так уж плохо.

0

мне нравится краткое выражение, именно поэтому я предлагаю это вперед решение (определенный в оболочке, но это должно быть легко адаптироваться к модулю):

1> Int = fun Int(X,N,N,D,F,V,LF) -> (V + (F(N*D+X)+LF)*D)/2; Int(X,C,N,D,F,V,LF) -> NF = F(X+C*D), Int(X,C+1,N,D,F,V+(NF+LF)*D,NF) end. 
#Fun<erl_eval.27.90072148> 
2> Integral = fun(X1,X2,Dx,F) -> S = abs(X2-X1)/(X2-X1), N = round(S*(X2-X1)/Dx), Int(X1,1,N,S*Dx,F,0,F(X1)) end. 
#Fun<erl_eval.4.90072148> 
3> F = fun(X) -> 2*X end. 
#Fun<erl_eval.6.90072148> 
4> Integral(0,2,0.00001,F). 
4.000000000000002 
5> Integral(2,0,0.00001,F). 
-3.9999999999999996 
6> 

Int делает оценку цикл рекурсивно, Интегральная подготавливает параметры перед вызовом Int.