可以在Erlang中定义循环列表吗?

时间:2012-01-16 18:32:32

标签: list erlang circular-list

是否可以在erlang中定义循环列表? http://en.wikipedia.org/wiki/Linked_list

第一个问题是循环列表在erlang中的意思是什么? 它有两个元素,一个元素是它自己,旁边的元素是否存放在列表中?

如果是这样我可以说有可能在erlang中定义循环列表。 但我需要澄清天气是我认为循环列表是在erlang吗?

5 个答案:

答案 0 :(得分:8)

没有内置列表机制来执行此操作。但是,您可以使用包含您访问过的元素的元组来构建一个元素。

基本结构是一个包含两个列表的元组:{Old, New}。首次使用空列表时,它看起来像{[],[]}。当您填写列表时,请将其填入New列表:

new() -> {[], []}.

insert(X, {Old, New}) -> {Old, [X|New]}.

peek({_Old, [H|_]}) -> X.

要在列表中移动,您要做的是先在New列表中搜索,然后将值放在旧列表中:

next({Old, [H|New]}) -> {[H|Old], New}.

这很好,就像我们只是丢弃旧元素一样。当我们到达列表末尾时会发生什么?我们需要修复这个函数(以及偷看函数):

peek({Old, []}) -> hd(lists:reverse(Old));
peek({_Old, [H|_]}) -> X.

next({Old, []}) -> 
    {[], lists:reverse(Old)}}.
next({Old, [H|New]}) -> 
    {[H|Old], New}}.

如果列表中没有任何内容,则会崩溃。如果你想通过特殊的套管它也可以返回'undefined':

next({[], []}) ->
    undefined;
next({Old, []}) -> 
    {[], lists:reverse(Old)}.
next({Old, [H|New]}) -> 
    {[H|Old], New}.

然后,您可以使用“next”,“peek”和“删除”功能(见下文)来执行常规操作。我们还可以添加'prev'功能以允许向后浏览:

prev({[], []}) ->
    undefined;
prev({[], New}) -> 
    {lists:reverse(New), Old}.
prev({[H|Old], New}) -> 
    {Old, [H|New]}.

delete({Old, []}) -> {[], tl(lists:reverse(Old))};
delete({Old,[H|New]}) -> {Old, New};

这应该涵盖大部分内容。

答案 1 :(得分:5)

看到erlang和erlang虚拟机只支持不可变数据,因此无法构建循环列表。如果你是以某种“非法”方式自己构建一个,那么不确定内存管理是否可以正确处理它。

答案 2 :(得分:4)

虚拟机支持的Erlang中没有循环列表。如果你需要,你必须自己构建它们。

答案 3 :(得分:3)

为什么你可以;)

14> X = ll:new().         
20496
15> ll:push(X, 1).        
1
16> ll:push(X, 2).        
2
17> ll:push(X, 3).        
3
18> ll:pop(X).            
3
19> ll:hd(X).
2
20> {V0,R0} = ll:first(X).
{2,#Ref<0.0.0.80>}
21> {V1,R1} = ll:next(X, R0). 
{1,#Ref<0.0.0.76>}
22> {V2,R2} = ll:next(X, R1).
{2,#Ref<0.0.0.80>}

这里有一些蹩脚的代码来证明它

-module(ll).
-export([new/0, delete/1, push/2, pop/1, first/1, hd/1, next/2]).

-define (META_KEY, '$meta_list').

-record(elt, {id, val, next}).
-record(meta, {id =?META_KEY, size, hd, tl}).

% Returns TID of ETS table representing linked list
new() -> 
    Tid = ets:new(alist,[{keypos, 2}]),
    ets:insert(Tid, #meta{size=0, hd=undefined, tl=undefined}),
    Tid.

% Delete list / ETS table representing linked list
delete(AList) ->
    ets:delete(AList).

% Returns the value of what was pushed
push(AList, AnElt) ->
    #meta{size = Size} = Meta = get_meta(AList),
    Hd = get_head(AList, Meta),

    Ref = make_ref(),
    NewElt = #elt{id=Ref, val=AnElt, next=iif(Size, 0, Ref, Hd#elt.id)},
    ets:insert(AList, NewElt),

    case Size of
        0 -> ets:insert(AList, Meta#meta{size=1,hd=Ref,tl=Ref});
        N ->
            Tl = get_tail(AList, Meta),
            ets:insert(AList, Tl#elt{next = Ref}),
            ets:insert(AList, Meta#meta{size=N+1,hd=Ref})
        end,
    AnElt.

% Returns the value of the popped element
pop(AList) ->
    #meta{size = Size} = Meta = get_meta(AList),
    Hd = get_head(AList, Meta),
    case Size of
    0 -> ok;
    1 ->
        ets:insert(AList, Meta#meta{size=0, hd=undefined,tl=undefined});
    N ->
        Next = get_next(AList, Hd),
        Tail = get_tail(AList, Meta),
        ets:insert(AList, Meta#meta{size=N-1, hd=Next#elt.id}),
        ets:insert(AList, Tail#elt{next=Next#elt.id})
    end,
    ets:delete(AList, Hd#elt.id),
    Hd#elt.val.

% Returns the value of the first element
hd(AList)->
    {First, _Next} =first(AList),
    First.

% Returns {val, ptr_to_tail}. The prt_to_tail can be used in next/2
first(AList)->
    #meta{size = Size} = Meta = get_meta(AList),
    if
    Size == 0 -> {undefined, undefined};
    true ->
        Hd = get_head(AList, Meta),
        {Hd#elt.val, Hd#elt.id}
    end.

% Given ptr_to_tal, returns {hd(tail), ptr_to_tail}. 
next(_AList, undefined) ->    
    {undefined, undefined};
next(AList, Id) ->    
    case ets:lookup(AList, Id) of
    [] -> {error, node_missing};
    [#elt{next=Next}] ->
        case ets:lookup(AList, Next) of
        []-> {error, node_missing};
        [#elt{val=Value}] ->
            {Value, Next}
        end
    end.



%helper functions
get_meta(List)->
    case  ets:lookup(List, ?META_KEY)  of
    []         -> {error, corruptlist};
    [Meta] -> Meta
    end.

get_head(AList, #meta{size = Size, hd=Hd} ) ->
    case Size of
    0 -> #elt{};
    _N -> 
        case ets:lookup(AList, Hd) of
        []     -> {error, corruptlist};
        [Head] -> Head
        end
   end.

get_tail(AList, #meta{size = Size, tl=Tl} ) ->
    case Size of
    0 -> #elt{};
    _N -> 
        [Tail] = ets:lookup(AList, Tl),
        Tail
    end.

get_next(_AList, #elt{next=undefined}) -> #elt{};
get_next(AList, #elt{next=Next}) ->
    case ets:lookup(AList, Next) of
    [] -> {error, corruptlist};
    [Elt] -> Elt
    end.


iif(A, B, TruePart, ElsePart)->
case A == B of
    true -> TruePart;
    false -> ElsePart
end.

答案 4 :(得分:1)

如上所述,您必须自己实施它们。但是,由于您可以在erlang中以各种方式将数据与其他数据相关联,因此没有什么可以阻止您这样做。 基本上,您只需要一个表示当前索引的东西,另一个表示指向下一个索引的指针。一种有趣的方式是为列表中的每个元素启动一个过程,该过程通过其PID指向下一个(或前一个)过程(元素)。一个(或许多)特殊目的进程可以抓取其他“列表”进程。不那么疯狂的aproaches可能会使用ets或mnesia。