Erlang算法返回彼此相加的X对整数对的列表

时间:2014-11-08 03:53:47

标签: erlang

给出一个列表,例如

[1, 45, 1, 99, 3, 5, 95, 1, 5, 97, 3, 99, 87]

从该列表中返回最多为100的对。当项目被消耗时,不应重新评估它。在上面的例子中,输出将是:

[{1,99}, {1, 99}, {3,97}, {5,95}]

不假设列表被排序,因为重复对应该起作用。

方法的优点/缺点很好理解(BigO复杂性,空间/时间)。

3 个答案:

答案 0 :(得分:4)

你可以使用list comprehension和guard来做。

find_pairs(List) ->
  [{X, Y} || X <- List,
             Y <- List,
             X+Y =:= 100].

这种方法当然具有n^2的复杂性,但几乎任何其他方法都是如此。最后,您必须采用每个元素(n)并相互验证(并且* n)。您可以像another answer中建议的那样引入一些优化,但仍然会留下大的O n^2。所以我认为没有意义。

如果这种复杂性会给我带来一些问题,事实上我必须进行优化,我会尝试减少第二次*n。由于第二部分只是值查找(对于给定X的eny,您在其余值中寻找100 -X。你可以尝试在gb_tree中查找。创建这样的树需要一些n log n,但这只做一次。所有的观察都会带你log n。所以最终这种方法会有n log n复杂性。

在其他语言中,而不是使用gb_tree,只需对列表进行排序,并进行二进制搜索以进行值查找(同样位O为n log n)。但必须记住,在Erlang列表中不是数组。它们是“链表”,查找列表中的一个值不是常量,但可能具有n复杂度。这个n会对我们的算法产生影响,这可能会让我们n * n long n比初始n^2更糟糕。


@Steve Vionski评论我的算法未通过某些要求后,

编辑。使用我的方法,在配对来自[1, 99, 99]而不是[{1, 99}]的值时,我会返回[{1,99},{1,99},{99,1},{99,1}]

我真的需要仔细阅读问题。谢谢史蒂夫指出这一点。我仍然想留下我最初的答案,因为它清楚地显示了算法的复杂性,这是我在答案中集中注意力的。

那就是说我想为任何可能的混淆道歉,并提供有效的解决方案:

find_pairs(List) ->
  find_pairs(List, _Pairs = [], _Sum = 100).

find_pairs([], Pairs, _Sum) ->
  Pairs;
find_pairs([First | Tail], Pairs, Sum) -> 
  case pop(Tail, Sum - First) of
    {Second, Rest} ->
      find_pairs(Rest, 
                 [{First, Second} | Pairs],
                 Sum);
    not_found ->
      find_pairs(Tail,
                 Pairs,
                 Sum)
  end.


pop(List, Value) ->
  pop(List, Value, []).

pop([], _Value, _Processed) ->
  not_found;
pop([Value |  Tail], Value, Processed) ->
  {Value, Processed ++ Tail};
pop([Different | Tail], Value, Processed) ->
  pop(Tail, Value, [Different|Processed]).

再次关于这种算法的复杂性。 find_pairs只是通过列表,pop也是如此,所以它似乎是n^2。事实证明,它并非如此简单。还有一个附加功能++,由于链接列表的性质可能会n复杂化。所以最后,根据输入,我们可以体验n*(2*n)。仍然在BigO中它是n^2,但值得注意的是,在算法中添加更多工作(或代码行)并不能保证提高性能。

还有一个简单的解决方法。 ++具有左侧元素的复杂性。因此,在pop中连接两个列表,而不是将Processed添加到Tail,可以将Tail添加到Processed。这样,当我们在Value位置(以及k次调用之后)找到k时,我们在连接期间只需执行n - k次额外工作。这可以保证pop的工作时间超过n。我们回到直接n^2寻找整个算法,(而不依赖于数据顺序)。

答案 1 :(得分:2)

在shell中,因为它使用递归匿名函数,它只适用于R17,但在具有早期erlang版本的模块中可以正常

1> L = [1, 45, 1, 99, 3, 5, 95, 1, 5, 97, 3, 99, 87].
2> F= fun F([],R) -> R;
2>        F([H|T],R) -> Rest = lists:dropwhile(fun(X) -> X+H /= 100 end,T),                 
2>                      case Rest of
2>                          [] -> F(T,R);
2>                          [Found|_] -> F(lists:delete(Found,T),[{H,Found}|R])
2>                      end
2> end.
#Fun<erl_eval.36.90072148>
3> F(L,[]).                                                                                                   
[{5,95},{3,97},{1,99},{1,99}]
4> 

如果我必须自己完成,我会完全重现我会做的事情:

  • 获取列表的第一个元素
  • 在列表的其余部分寻找一对,
    • 如果没有一对用列表的其余部分重新启动进程,
    • 如果找到一对,记录该对,从列表的其余部分中删除找到的元素,并使用剩余的列表重新启动。
  • 继续,直到列表是emty。

第一个实现是在使其工作精神,我做了一个更快的,首先排序列表。以下模块实现了2个解决方案以及一些测试和评估性能的功能(在2种极端情况下,新解决方案对于长列表来说要快得多:没有解决方案或每个术语都属于一对)。在我的电脑上,s2比s1快2500多倍,随机列表包含100000个元素。

-module (sum).

-compile([export_all]).

s1(L,S) -> s1(L,S,[]).

s1([],_S,R) -> R;
s1([H|T],S,R) -> 
    Rest = lists:dropwhile(fun(X) -> X+H /= S end,T),                 
    case Rest of
        [] -> s1(T,S,R);
        [Found|_] -> s1(lists:delete(Found,T),S,[{H,Found}|R])
    end.

s2(L,S) ->
    Linc = lists:sort(L),
    Ldec = lists:reverse(Linc),
    s2(Linc,Ldec,S,[]).

s2(Linc,Ldec,_S,R) when Linc == [] ; Ldec == [] ; hd(Linc) > hd(Ldec) -> R;
s2([H,H|Linc],[H,H|Ldec],S,R) when S == 2*H -> s2(Linc,Ldec,S,[{H,H}|R]);
s2([H1|Linc],[H2|Ldec],S,R) when S == H1+H2, H1/=H2 -> s2(Linc,Ldec,S,[{H1,H2}|R]);
s2([H|Linc],Ldec,S,R) when H + hd(Ldec) < S -> s2(Linc,Ldec,S,R);
s2(Linc,[_H|Ldec],S,R) -> s2(Linc,Ldec,S,R).


%% Test and performance

compare(S1,S2) ->
    S = normalize(S1),
    S = normalize(S2).


normalize(S) -> lists:sort([{min(X,Y),max(X,Y)} || {X,Y} <- S]).

shuffle(P) when is_list(P) ->
    Max = length(P)*10000,
    {_,R}= lists:unzip(lists:keysort(1,[{random:uniform(Max),X} || X <- P])),
    R.

test1(S) -> % every term is part of a solution pair
    random:seed(erlang:now()),
    L = shuffle(lists:seq(1,S)),
    test(L,S+1).

test2(S) -> % no solution
    random:seed(erlang:now()),
    L = shuffle(lists:seq(1,S)),
    test(L,2*S).

test3(S) -> % random
    random:seed(erlang:now()),
    L = [random:uniform(2*S) || _ <- lists:seq(1,S)],
    test(L,S).

test(L,S) -> 
    {T1,S1} = timer:tc(sum,s1,[L,S]),
    {T2,S2} = timer:tc(sum,s2,[L,S]),
    compare(S1,S2),
    {T1,T2,S1}.

答案 2 :(得分:0)

我不是那种流利的算法和他们的表现,但这是我已经想到的。我的一般想法是将列表拆分为两个子列表,其中元素&gt; = 50和&lt; 50.至少这可以确保您不必在每个列表中查找对,而只需在它们之间查找:

-module(test).
-export([add_to_100/1]).

add_to_100([]) -> [];
add_to_100(List) ->
    {L1, L2} = split(List, [], []),
    find_match(L1, L2).

%% Do matching between 2 lists
find_match([], _) -> [];
find_match(_, []) -> [];
find_match([H1|T1], L2) ->
    case match_element(H1, L2, []) of
        {{A, B}, Left} -> [{A, B} | find_match(T1, Left)];
        {{}, Left} -> find_match(T1, Left)
    end.

%% Match every element with list of integers.
%% Return match pair and exclude matched element from list of integers.
match_element(_, [], Rest) -> {{}, Rest};
match_element(E, [H|T], Rest) when E + H == 100 -> {{E,H}, Rest ++ T};
match_element(E, [H|T], Rest) -> match_element(E, T, [H|Rest]).

%% Split input list into two sublists - L1 < 50 and L2 >= 50
split([], L1, L2) -> {L1, L2};
split([H|T], L1, L2) when H < 50 -> split(T, [H|L1], L2);
split([H|T], L1, L2) -> split(T, L1, [H|L2]).

示例:

85> c(test).
{ok,test}
86> test:add_to_100([1, 45, 1, 99, 3, 5, 95, 1, 5, 97, 3, 99, 87]).
[{3,97},{5,95},{1,99},{1,99}]
87> test:add_to_100([1, 45, 1, 99, 3, 5, 95, 1, 5, 97, -50, 3, 99, 87, 100, 0, 150]).
[{0,100},{3,97},{-50,150},{5,95},{1,99},{1,99}]

实现之后,我意识到它不处理对{50,50} - 无论如何你可以将它作为特殊情况添加到这个算法中。即使这个解决方案不完整,也应该让你深入了解Er​​lang中的模式匹配,尾递归和列表操作,在解决这个问题时你肯定需要它。