【问题标题】:Erlang: choosing unique items from a list, using recursionErlang:使用递归从列表中选择唯一项目
【发布时间】:2013-03-01 18:24:42
【问题描述】:

给定 Erlang 中的任何列表,例如:

L = [foo, bar, foo, buzz, foo].

如何使用递归函数仅显示该列表的唯一项目? 我不想使用内置函数,例如列表函数之一(如果存在)。

在我的例子中,我想去的地方是一个新的列表,比如

SL = [bar, buzz].

我的猜测是,在应用过滤器之前,我会先使用快速排序功能对列表进行排序?

任何建议都会有所帮助。该示例是 Cesarini 和 Thompson 的优秀“Erlang 编程”一书第 3 章中练习的变体。

【问题讨论】:

  • 感谢您的编辑。我是 Stack Overflow 的新手,非常感谢您的建议/风格指南。
  • @Muzaaya Joshua:我只想显示该列表中唯一的项目,而不是仅仅删除重复项。

标签: recursion erlang


【解决方案1】:

最简单的方法是使用带有“累加器”的函数来跟踪您已经拥有的元素。 所以你会写一个像

这样的函数

% unique_acc(累加器,List_to_take_from)。

你仍然可以拥有一个干净的函数,不导出累加器版本,而是导出它的调用者:

-module(uniqueness).
-export([unique/1]).

unique(List) ->
    unique_acc([], List).

如果要从中获取的列表是空的,那么您就完成了:

unique_acc(Accumulator, []) ->
    Accumulator;

如果不是:

unique_acc(Accumulator, [X|Xs]) ->
   case lists:member(X, Accumulator) of
       true  -> unique_acc(Accumulator, Xs);
       false -> unique_acc([X|Accumulator], Xs)
   end.

需要注意的两件事:
-- 这确实使用列表 BIF -- lists:member/2。不过,您可以自己轻松编写。
-- 元素的顺序是颠倒的,从原始列表到结果。如果您不喜欢这样,您可以将unique/1 定义为lists:reverse(unique_acc([], List))。或者更好的是,自己编写一个反向函数! (很简单)。

【讨论】:

  • 这会从列表中删除重复项,不会为您提供唯一项。在 case 语句中,您应该执行 true -> unique_acc(lists:delete(X, Accumulator), Xs); .即便如此,它也只会对它起作用,该项目出现偶数次并在奇数 # 次出现时失败。
【解决方案2】:

使用两个累加器。一个保留您目前看到的元素,一个保留实际结果。如果您是第一次看到该项目(不在“已见列表”中),请将该项目添加到两个列表并递归。如果您以前看过该项目,请在递归之前将其从结果列表 (Acc) 中删除。

-module(test).

-export([uniques/1]).

uniques(L) ->
    uniques(L, [], []).

uniques([], _, Acc) ->
    lists:reverse(Acc);
uniques([X | Rest], Seen, Acc) ->
    case lists:member(X, Seen) of
        true -> uniques(Rest, Seen, lists:delete(X, Acc));
        false -> uniques(Rest, [X | Seen], [X | Acc])
    end.

【讨论】:

  • 我想知道谁做了-1来正确解决?唯一困扰我的是lists:delete/2 在已知项目不是唯一的情况下使用。我想你可以有两个没有交叉点的列表NotUniqueUniqueByNow。而且你必须检查他们两个的 X 成员资格。
  • 可能稍微不那么优雅,但仍然是一个适当的解决方案。 +1
  • 我喜欢累加器的想法——作为 Erlang 的一般原则。以前没有想过这个。谢谢你的建议。
  • @DmitryBelyaev 我认为您需要 lists:delete/2 即使您在第二次看到该项目时建议从 UniqueByNow 中删除该项目。对吗?
  • 是的。我只是不喜欢 true -> uniques(Rest, Seen, lists:delete(X, Acc)); 看到超过 2 次的元素。在这种情况下,Acc 中不会有 X。
【解决方案3】:

此解决方案仅过滤掉列表中的重复项。可能需要建立在它做你想做的事。

删除重复项(列表)-> 列表:反向(删除(列表,[]))。 删除([],这个)->这个; 移除([A|Tail],Acc)-> 删除(删除全部(A,尾巴),[A|Acc])。 delete_all(Item, [Item | Rest_of_list]) -> delete_all(Item, Rest_of_list); delete_all(Item, [Another_item| Rest_of_list]) -> [另一个项目 | delete_all(Item, Rest_of_list)]; 删除全部(_,[])-> []。

编辑


Microsoft Windows [版本 6.1.7601]
版权所有 (c) 2009 Microsoft Corporation。版权所有。

C:\Windows\System32>erl
Eshell V5.9(使用 ^G 中止)
1> 列表 = [1,2,3,4,a,b,e,r,a,b,v,3,2,1,g,{红,绿},d,2,5,6,1 ,4,6,5,{红,绿}]。
[1,2,3,4,a,b,e,r,a,b,v,3,2,1,g,
 {红,绿},
 d,2,5,6,1,4,6,5,
 {红,绿}]
2> 删除重复项(列表)。
[1,2,3,4,a,b,e,r,v,g,{红,绿},d,5,6]
3>

【讨论】:

  • 删除重复项不会为您提供唯一性。试试他的例子。如果你想删除重复的 sets:to_list(sets:from_list(List)) 可能会比这更好。
  • @MuzaayaJoshua 作者想删除所有不唯一的元素。 [a, a, b, b, c, d] 必须只留下 [c, d]。
  • @MuzaayaJoshua 作者想要 [foo,bar,foo,buzz,foo] => [bar,buzz]。您的解决方案给出 [foo, bar, foo, buzz, foo] => [foo, bar, buz]。我提出了集合解决方案,因为您说您在某些项目中使用了它。
  • 我仍然信守诺言;删除重复项不会为您提供列表中的唯一项。再次阅读问题。 Alexander 不想删除重复项,他想找出哪些元素只出现过一次。至少我有正当理由投反对票。
  • 感谢您的代码示例,这当然帮助我更好地学习了 Erlang。我倾向于同意羊绒,尽管您的示例确实删除了重复项,但同时并没有给我独特的项目。
【解决方案4】:

我建议这个:

unique(L) ->
    unique([],L).
unique(R,[]) -> R; 
unique(R,[H|T]) ->
    case member_remove(H,T,[],true) of
        {false,Nt} -> unique(R,Nt);
        {true,Nt} -> unique([H|R],Nt)
    end.

member_remove(_,[],Res,Bool) -> {Bool,Res};
member_remove(H,[H|T],Res,_) -> member_remove(H,T,Res,false);
member_remove(H,[V|T],Res,Bool) -> member_remove(H,T,[V|Res],Bool).

member_remove 函数一次性返回剩余的尾部,而不是所有出现的元素都被检查重复和测试结果。

【讨论】:

  • 非常优雅。工作精美。非常感谢您的回复。
【解决方案5】:

我可以这样做:)

get_unique(L) ->
    SortedL = lists:sort(L),
    get_unique(SortedL, []).

get_unique([H | T], [H | Acc]) ->
    get_unique(T, [{dup, H} | Acc]);
get_unique([H | T], [{dup, H} | Acc]) ->
    get_unique(T, [{dup, H} | Acc]);
get_unique([H | T], [{dup, _} | Acc]) ->
    get_unique(T, [H | Acc]);
get_unique([H | T], Acc) ->
    get_unique(T, [H | Acc]);
get_unique([], [{dup, _} | Acc]) ->
    Acc;
get_unique([], Acc) ->
    Acc.

【讨论】:

    【解决方案6】:

    我认为想法可能是:检查您是否已经看到列表的头部。如果是这样,跳过它并递归检查尾部。如果不是 - 将当前头部添加到结果中,以“看到”并递归检查尾部。检查您是否已经看到该项目的最合适的结构已设置。

    所以,我建议如下:

     remove_duplicates(L) -> remove_duplicates(L,[], sets:new()). 
    
      remove_duplicates([],Result,_) -> Result;
      remove_duplicates([Head|Tail],Result, Seen) ->
        case sets:is_element(Head,Seen) of
          true -> remove_duplicates(Tail,Result,Seen);
          false -> remove_duplicates(Tail,[Head|Result], sets:add_element(Head,Seen))
        end.
    

    【讨论】:

    • 谢谢。您的代码示例确实删除了重复项,但同时不只返回列表中的唯一项?
    • 哦,我明白了。您需要在列表中出现一次且仅出现一次的元素。在那种情况下,我相信你根本不需要递归函数。您只需要计算计数器 = 1 的每个项目和过滤元素。就像 D = lists:foldl( fun(X,Acc) -> dict:update_counter(X,1,Acc) end, dict:new(), List), [X|| {X,1} <- dict:to_list(D)].
    • 甚至更好:[X||{X,1} <- dict:to_list(lists:foldl( fun(X,Acc) -> dict:update_counter(X,1,Acc) end, dict:new(), List))].
    【解决方案7】:

    试试下面的代码

    -module(util).
    
    -export([unique_list/1]).
    
    unique_list([]) -> [];
    unique_list(L)  -> unique_list(L, []).
    
    % Base Case
    unique_list([], Acc) -> 
        lists:reverse(Acc);
    
    % Recursive Part 
    unique_list([H|T], Acc) ->
        case lists:any(fun(X) -> X == H end, T) of
            true  -> 
                unique_list(lists:delete(H,T), Acc);
            false -> 
                unique_list(T, [H|Acc])
    end.
    

    【讨论】:

      【解决方案8】:

      unique(L) -> 集合:to_list(sets:from_list(L))。

      【讨论】:

      • 感谢您的贡献!请添加几句话来解释您的代码是如何工作的以及它到底做了什么。这将大大改善您的答案。
      【解决方案9】:
      unique(List) ->
          Set = sets:from_list(List),
          sets:to_list(Set).
      

      【讨论】:

      • 如果您添加有关解决方案的描述文本,以改善您的答案应该会更好
      • 感谢您提供此代码 sn-p,它可能会提供一些有限的即时帮助。一个正确的解释would greatly improve 它的长期价值通过展示为什么这是一个很好的解决问题的方法,并将使它对未来有其他类似问题的读者更有用。请edit您的回答添加一些解释,包括您所做的假设。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多