我有一个包含[[1, 2], [2, 1], [1, 3] ..]
对的列表。如何以最快的方式获得独特的对?我写了一个函数,但它太慢了。
-module(test).
-export([unique/1, unique/2, pairs/1]).
unique(L) -> unique(L, []).
unique([], UL) -> UL;
% L: list of lists
unique(L, UL) ->
[X,Y] = hd(L),
case lists:member([Y,X], L) of
true ->
unique(L--[[Y,X]], [[X,Y]|UL]);
false ->
unique(tl(L), UL)
end.
pairs(L) -> [[X,Y] || X <- L, Y <- L, X=/=Y].
来自shell,
1> test:pairs([1,2,3]).
[[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]]
2> test:unique(test:pairs)). %Very slow for large list. How to improve?
[[2,3],[1,3],[1,2]]
我有一个列表长度为9900
的对列表,其中一半是重复的。我正在使用对列表进行进一步的计算。使用原始列表(带有重复对),时间为3.718s
,如果我过滤掉唯一列表并使用if进行计算,则时间7.375s
会更糟。
我将功能更改为不使用--
运算符。
unique(L, UL) ->
[X,Y] = hd(L),
case lists:member([Y,X], L) of
true ->
unique(tl(L), [[Y,X]|UL]);
false ->
unique(tl(L), UL)
end.
即便如此,它在0.047s
处仅提高了7.375s
,这表明该算法速度不够快。
你能指出更好的算法吗?是否有内置的库函数?
感谢。
答案 0 :(得分:1)
你试过lists:usort([lists:sort(X) || X <- L])
,我用9900元素列表尝试了它,不到1秒。
18> F = fun(X,L) -> [[X,Y] || Y <- L] end.
#Fun<erl_eval.12.82930912>
19> L = lists:seq(1,100).
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,
23,24,25,26,27,28,29|...]
20> L1 = lists:foldl(fun(X,Acc) -> F(X,lists:delete(X,L)) ++ Acc end,[],L).
[[100,1],
[100,2],
[100|...],
[...]|...]
21> length(L1).
9900
22> io:format("~p~n",[erlang:now()]),lists:usort([lists:sort(X) || X <- L1]),io:format("~p~n",[erlang:now()]).
{1383,395086,328000}
{1383,395086,515000}
ok
23> lists:usort([lists:sort(X) || X <- [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]]]).
[[1,2],[1,3],[2,3]]
24>
显示执行时间小于0.2秒,第23行的命令测试它是否有效。
答案 1 :(得分:1)
有几种方法可以做到这一点。 v1
是最快但最脏的方式:
-module(uniq).
-export([v1/1, v2/1, v3/1, v4/1, gen/1]).
-compile({inline, [s/1]}).
s([X, Y]) when X > Y -> [Y, X];
s(L) -> L.
v1(L) ->
erase(),
[put(s(K), ok) || K <- L],
[K || {K, _} <- erase() ].
v2(L) ->
sets:to_list(sets:from_list([s(K) || K <- L])).
v3(L) ->
T = ets:new(set, [private, set]),
ets:insert(T, [{s(K)} || K <- L]),
R = [K || {K} <- ets:tab2list(T)],
ets:delete(T),
R.
v4(L) ->
lists:usort([s(K) || K <- L]).
gen(N) ->
[[random:uniform(100), random:uniform(100)] || _ <- lists:seq(1, N)].
结果速度:
1> L = uniq:gen(1000000).
...
2> [ element(1, timer:tc(uniq,Alg,[L]))/1000000 || Alg <- [v1, v2, v3, v4]].
[0.243595,1.042272,0.35633,1.309971]
3> [ element(1, timer:tc(uniq,Alg,[L]))/1000000 || Alg <- [v1, v2, v3, v4]].
[0.236856,1.000818,0.359761,1.309743]
4> [ element(1, timer:tc(uniq,Alg,[L]))/1000000 || Alg <- [v1, v2, v3, v4]].
[0.242901,1.039107,0.357476,1.30691]
注意lists:usort/1
版本v4
是最慢的版本。在版本v1
中使用进程字典是非常脏的,你应该避免它,但在特殊情况下它是可行的。在版本ets
中使用v3
具有良好的性能,您应该将此版本用于任何认真的工作。对于较小的列表,sets
版本v2
也是不错的选择。它简短而且非常好。
避免处理器字典污染并且仍具有相同性能的技巧是使用子流程:
v1(L) ->
Self = self(),
PID = spawn_link(fun() ->
[put(s(K), ok) || K <- L],
Self ! {result, self(), [K || {K, _} <- erase() ]}
end),
receive
{result, PID, Result} -> Result
after 10000 -> error(timout)
end.
通过将数据复制到单独的堆中(除非使用二进制文件),您将失去一些性能,但它仍然是最快的选项。在这种情况下,它需要更多约50毫秒,所以仍然是最快的。