Erlang替代f#序列

时间:2015-05-15 17:57:11

标签: f# erlang seq

是否有替代F#" seq"在Erlang构建?例如,在F#中我可以编写一个O(1)内存集成函数

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(n)内存要求,这对于这样的简单函数是不可接受的,

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).

4 个答案:

答案 0 :(得分:6)

免责声明:以下是在假设Erlang完全不允许突变的情况下编写的,我不确定,因为我不太了解Erlang。

Seq是基于内部突变的。它保持"当前状态"并在每次迭代时改变它。因此,当你进行一次迭代时,你会得到"下一个值",但是你也会得到副作用,即枚举器的内部状态已经改变,当你进行下一次迭代时,你会得到一个不同的"下一个值",依此类推。这通常很好地涵盖了功能性的理解,但是如果你直接使用IEnumerator,你将会用肉眼看到非纯洁。

另一种思考方式是,给定一个"序列",你得到两个结果:"下一个值" "序列的其余部分",然后"序列的其余部分"成为你的新序列",你可以重复这个过程。 (原来的#34;序列"永远消失了)

这种思路可以直接用F#表示:

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

含义:&#34;懒惰序列是一个函数,当应用时,返回其头部和尾部,其中尾部是另一个懒惰序列&#34;。包含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你可以构建一切,这本身并不是一个懒惰的序列。但是要构建懒惰的序列,你需要一个&#34;滚动折叠&#34; (有时称为&#34;扫描&#34;):

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

总而言之,我想补充一点,基于突变的解决方案几乎总是更高效(所有条件都相同),至少是一点点。即使您在计算新内容时立即丢弃旧状态,仍需要回收内存,这本身就是性能损失。不变性的好处不包括性能微调。

<强>更新
这是我对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

答案 1 :(得分:4)

这未经过测试,但却是一种方法。

我们的想法是将列表转换为一个在被询问时产生下一个值的过程。如果需要,您可以轻松概括该想法。

或者,您可以编写展开,然后可以一次展开列表并将其用作通用处理器的输入。

另一种方法是实现延迟流,这是基于以下观点:任何Expr都可以被fun () -> Expr end延迟最好写为-define(DELAY(X), fun() -> X end).作为宏,然后与{{一起使用1}}

-define(FORCE(X), X()).

基于DELAY / FORCE的解决方案如下:

-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).

但没有经过测试。

答案 2 :(得分: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 - 并没有那么糟糕。

答案 3 :(得分:0)

我喜欢简洁的表达,这就是为什么我提出这个解决方案(在shell中定义,但应该很容易适应模块):

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以递归方式进行评估循环,Integral在调用Int。

之前准备参数