是否有替代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).
答案 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。
之前准备参数