单通道算法,用于查找topX百分比的项目

时间:2011-07-14 00:16:41

标签: python algorithm

我正在寻找一种单程算法,用于在流中查找浮点数的topX百分比,其中我不知道提前的总数...但是其大约有5到3千万个浮点数。它需要单次传递,因为数据是在运行中生成的,并且第二次重新创建精确的流。

到目前为止,我所拥有的算法是保存到目前为止我见过的topX项目的排序列表。随着流的继续,我根据需要扩大列表。然后,如果需要,我使用bisect_left来查找插入点。

以下是我到目前为止的算法:

from bisect import bisect_left
from random import uniform
from itertools import islice


def data_gen(num):
    for _ in xrange(num):
        yield uniform(0,1)

def get_top_X_percent(iterable, percent = 0.01, min_guess = 1000):

    top_nums = sorted(list(islice(iterable, int(percent*min_guess)))) #get an initial guess

    for ind, val in enumerate(iterable, len(top_nums)):
        if int(percent*ind) > len(top_nums):
            top_nums.insert(0,None)
        newind = bisect_left(top_nums, val)
        if newind > 0:
            top_nums.insert(newind, val)
            top_nums.pop(0)

    return top_nums

if __name__ == '__main__':

    num = 1000000
    all_data = sorted(data_gen(num))
    result = get_top_X_percent(all_data)
    assert result[0] == all_data[-int(num*0.01)], 'Too far off, lowest num:%f' % result[0] 
    print result[0]

在实际情况中,数据不是来自任何标准分布(否则我可以使用一些统计知识)。

任何建议都将不胜感激。

4 个答案:

答案 0 :(得分:5)

我不确定有没有办法真正可靠地做到这一点,因为当你看到更多元素时,“前X%”所表示的范围会不可预测地增长。请考虑以下输入:

 101 102 103 104 105 106 107 108 109 110 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0...

如果你想要前25%的元素,你最终会从前10个元素中选择101和102,但是在那之后看到足够的零后你最终必须选择所有前10个元素。同样的模式可以扩展到任何足够大的流 - 它总是可能最终被外观误导,并丢弃你实际应该保留的元素。因此,除非您提前知道流的确切长度,否则我认为这是不可能的(在您到达流的末尾之前,不要将每个元素保留在内存中)。

答案 1 :(得分:4)

必须将整个流存储在内存中。

证明:您有一系列数字,n 1 ,...,n k k 的值未知。你怎么知道n i 什么时候可以忘记?当你看到x * k / 100的数字大于n i 时。但是,由于 k 未知,您永远不能这样做。

因此,唯一的“一次通过”算法必须将整个序列存储在内存中。

答案 2 :(得分:1)

正如其他答案所讨论的那样,除了将整个流存储在内存中之外,你真的不能做得更好。考虑这样做,特别是因为500万到3千万的花车可能只有40-240 MB的内存,这是可以管理的。

鉴于您存储整个流,获得topX百分比的算法最快的方法是首先使用线性时间选择算法找到截止元素(topX百分比中的最小元素):

http://en.wikipedia.org/wiki/Selection_algorithm

然后,再次通过流并过滤掉小于截止元素的所有元素。

这种方法是线性时间和线性空间,这是你所希望的最好的。

答案 3 :(得分:0)

以下是使用cProfile运行此命令的输出。看起来你的代码运行正常,因为大多数调用都是0.000(percall)。它似乎很慢,因为你有很多项需要处理。如果你想进行优化,你必须尝试pop更少的项目,因为它被称为999999,这似乎是不必要的。

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.000    0.000    0.000    0.000 __future__.py:48(<module>)
    1    0.000    0.000    0.000    0.000 __future__.py:74(_Feature)
    7    0.000    0.000    0.000    0.000 __future__.py:75(__init__)
    1    0.001    0.001    0.001    0.001 bisect.py:1(<module>)
    1    0.001    0.001    0.001    0.001 hashlib.py:55(<module>)
    6    0.000    0.000    0.000    0.000 hashlib.py:91(__get_openssl_constructor)
    1    0.000    0.000    0.000    0.000 os.py:743(urandom)
    1    0.000    0.000    0.000    0.000 random.py:100(seed)
1000000    0.731    0.000    0.876    0.000 random.py:355(uniform)
    1    0.003    0.003    0.004    0.004 random.py:40(<module>)
    1    0.000    0.000    0.000    0.000 random.py:647(WichmannHill)
    1    0.000    0.000    0.000    0.000 random.py:72(Random)
    1    0.000    0.000    0.000    0.000 random.py:797(SystemRandom)
    1    0.000    0.000    0.000    0.000 random.py:91(__init__)
    1    2.498    2.498   13.313   13.313 test.py:12(get_top_X_percent)
    1    0.006    0.006   16.330   16.330 test.py:3(<module>)
1000001    0.545    0.000    1.422    0.000 test.py:8(data_gen)
1000000    1.744    0.000    1.744    0.000 {_bisect.bisect_left}
    1    0.000    0.000    0.000    0.000 {_hashlib.openssl_md5}
    1    0.000    0.000    0.000    0.000 {_hashlib.openssl_sha1}
    1    0.000    0.000    0.000    0.000 {_hashlib.openssl_sha224}
    1    0.000    0.000    0.000    0.000 {_hashlib.openssl_sha256}
    1    0.000    0.000    0.000    0.000 {_hashlib.openssl_sha384}
    1    0.000    0.000    0.000    0.000 {_hashlib.openssl_sha512}
    1    0.000    0.000    0.000    0.000 {binascii.hexlify}
    1    0.000    0.000    0.000    0.000 {function seed at 0x100684a28}
    6    0.000    0.000    0.000    0.000 {getattr}
    6    0.000    0.000    0.000    0.000 {globals}
1000004    0.125    0.000    0.125    0.000 {len}
    1    0.000    0.000    0.000    0.000 {math.exp}
    2    0.000    0.000    0.000    0.000 {math.log}
    1    0.000    0.000    0.000    0.000 {math.sqrt}
    1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
1009989    0.469    0.000    0.469    0.000 {method 'insert' of 'list' objects}
 999999    8.477    0.000    8.477    0.000 {method 'pop' of 'list' objects}
1000000    0.146    0.000    0.146    0.000 {method 'random' of '_random.Random' objects}
    1    0.000    0.000    0.000    0.000 {posix.close}
    1    0.000    0.000    0.000    0.000 {posix.open}
    1    0.000    0.000    0.000    0.000 {posix.read}
    2    1.585    0.792    3.006    1.503 {sorted}

顺便说一句,你可以使用cProfile:

python -m cProfile test.py