加快Erlang索引功能

时间:2009-09-25 08:35:26

标签: erlang

所以继续这个问题:

Erlang lists:index_of function?

我有以下代码可以正常工作:

-module(test_index_of).
-compile(export_all).

index_of(Q)->
    N=length(Q),
    Qs=lists:zip(lists:sort(Q), lists:seq(1, N)),
    IndexFn=fun(X)->
                {_, {_, I}}=lists:keysearch(X, 1, Qs),
            I
        end,     
    [IndexFn(X) || X <- Q].

test()->
    Q=[random:uniform() || _X <- lists:seq(1, 20)],
    {T1, _}=timer:tc(test_index_of, index_of, [Q]),
    io:format("~p~n", [T1]).

问题是,我需要在长度为20-30个字符的列表上运行index_of函数非常多次[10,000]; index_of函数是我的代码中的性能瓶颈。因此,虽然它看起来对我的合理有效实施,但我不相信它是最快的解决方案。

那里的任何人都可以在index_of的当前实现上改进[性能方面]吗? [Zed提到了gb_trees]

谢谢!

4 个答案:

答案 0 :(得分:4)

您正在针对错误的数据类型优化操作。

如果要在20-30项的同一列表上进行10 000次查找,那么进行预先计算以加快查找速度确实是值得的。例如,让我们在{key,index}元组的键中对元组进行排序。

1> Ls = [x,y,z,f,o,o].
[x,y,z,f,o,o]
2> Ls2 = lists:zip(Ls, lists:seq(1, length(Ls))).
[{x,1},{y,2},{z,3},{f,4},{o,5},{o,6}]
3> Ts = list_to_tuple(lists:keysort(1, Ls2)).         
{{f,4},{o,5},{o,6},{x,1},{y,2},{z,3}}

对此元组上的键的递归二进制搜索将非常快速地归入正确的索引。

  • 使用proplists:normalize删除重复项,也就是说,如果在查找'o'而不是5时返回6​​是错误的。或者使用折叠和集合来实现自己的删除重复项的过滤器。
  • 尝试使用dict构建一个dict:from_list / 1并在该字典上进行查找。

但这仍然引出了一个问题:你为什么要将索引列入某个列表?带有列表的查找:第n / 2个具有O(n)复杂度。

答案 1 :(得分:2)

不确定我是否完全理解这一点,但如果以上是您的实际用例,那么......

首先,您可以生成如下Q,并且您已经保存了压缩部分。

Q=[{N,random:uniform()} || N <- lists:seq(1, 20)]

更进一步,您可以生成一个从开头的值索引的树:

Tree = lists:foldl(
              fun(T, N) -> gb_trees:enter(uniform:random(), N, T) end,
              gb_trees:empty(),
              lists:seq(1, 20)
       ).

然后查找你的索引:

index_of(Item, Tree) ->
  case gb_trees:lookup(Item, Tree) of
    {value, Index} -> Index;
    _ -> not_found
  end.

答案 2 :(得分:0)

只有一个问题:WTF你在尝试吗?

我只是找不到这个功能的实际用途。我觉得你做的很奇怪。看起来你刚刚从O(N M ^ 2)改进到O(N M * logM),但它仍然非常糟糕。

修改

当我综合什么是目标时,似乎你正在尝试使用蒙特卡罗方法来确定英格兰首映联赛中球队“完成位置”的概率。但我还是不确定。您可以将最可能的位置[1,1,2] -> 1或分数确定为某种平均值1.33 - 例如,最后一个可以比其他方式更轻松地实现。

在功能编程语言中,数据结构在程序或OO中更为重要。他们更关注工作流程。你会做到这一点,而不是......而且......在Erlang这样的函数式语言中你应该以方式思考,我有这个输入,我想要那个输出。我可以从此确定所需的输出等等。可能没有必要列出过去的程序方法。

在过程方法中,您习惯使用数组进行持续随机访问的存储。列表不是那样的东西。在Erlang中没有可以编写的数组(甚至是实际上是平衡树的数组模块)。您可以使用元组或二进制文件作为只读数组,但没有人可以读写。我可以写很多关于不存在具有恒定访问时间的数据结构(从RAM,通过过程语言中的数组到HASH映射)但是没有足够的空间来详细解释它(来自RAM技术,通过L {1,2,3} CPU缓存必须在密钥数量增加时增加HASH长度和密钥长度的密钥HASH计算依赖性。)

列表是具有O(N)随机访问时间的数据结构。对于存储数据的最佳结构,您想要按照列表中存储的顺序逐个进行。对于小N,当相应的常数很小时,它可以是小N的随机访问的结构。例如,当N是您的问题中的团队数量(20)时,它可能比O(logN)访问某种树更快。但你必须注意你的常数有多大。

算法的常见组件之一是键值查找。在某些情况下,可以使用数组作为程序世界中的支持数据结构。键必须是整数,可能键的空格不能稀疏等。列表不能作为它的替代,除了这里非常小的N.我了解编写功能代码的最佳方法是避免键值查找不必要的地方。它通常需要重新安排工作流程或重构数据结构等。有时它看起来像翻转问题解决方案,如手套。

如果我忽略你的概率模型是错误的。根据您提供的信息,您的模型团队的季节点似乎是独立的随机事件,当然不是这样。所有球队都不可能有很高的分数,例如82因为所有球队在一个赛季中都有一些限制。所以现在忘了这件事。然后我将模拟一个“路径” - 季节并以[{78,'Liverpool'}, {81,'Man Utd'}, ...]形式获取结果,然后我可以使用列表对其进行排序:排序而不会丢失哪些团队所在的信息。结果我将使用路径迭代收集。对于每个路径,我将迭代已排序的模拟结果并将其收集在字典中,其中团队是关键的(从原子和常量存储的常量和非常便宜的哈希计算,因为键集是固定的,有可能使用元组/记录但似乎过早优化)。值可以是大小为20的元组,元组中的位置是最终位置,值是它的计数。

类似的东西:

% Process simulation results.
% Results = [[{Points, Team}]]
process(Results) ->
  lists:foldl(fun process_path/2,
    dict:from_list([{Team, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}} ||
       Team <- ['Liverpool', 'Man Utd', ...]]),
    Results).

% process simulation path result
process_path(R, D) ->
  process_path(lists:reverse(lists:sort(R)), D, 1).

process_path([], _, D) -> D;
process_path([{_, Team}|R], D, Pos) ->
  process_path(R, update_team(Team, Pos, D), Pos + 1).

% update team position count in dictionary
update_team(Team, Pos, D) ->
  dict:update(Team, fun(T) -> add_count(T, Pos) end, D).

% Add final position Pos to tuple T of counts
add_count(T, P) -> setelement(P, T, element(P, T) + 1).

请注意,没有类似lists:index_of或列表的内容:第n个函数。对于少数M个团队,产生的复杂性看起来像O(N M)或O(N M logM),但实际复杂度为O(N M ^ 2) setelement/3中的O(M)add_count/2。对于更大的M,您应该将add_count/2更改为更合理。

答案 3 :(得分:0)

我认为你需要自定义排序功能来记录它对输入列表的排列。例如,您可以使用列表:排序源。这应该给你O(N * log N)的表现。