【问题标题】:Single pass algorithm for finding the topX percent of items用于查找 topX 百分比项目的单通道算法
【发布时间】:2011-07-14 14:22:22
【问题描述】:

我正在寻找一种单通道算法,用于在我不知道提前总数的流中找到最高百分比的浮点数......但它大约为 5-30 百万个浮点数。它需要单次传递,因为数据是动态生成的,并且会再次重新创建准确的流。

到目前为止,我使用的算法是保留到目前为止我见过的 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]

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

任何建议将不胜感激。

【问题讨论】:

  • 这个程序有效吗?问题是什么?要查看您的代码,请尝试Code Review
  • 是的,它工作得很好(虽然很慢)......我想知道是否有人有任何更棘手的方法......我不知道代码审查......有没有迁移它的方法?
  • 对于工作代码,最好试试Code Review 这个网站是 mor 对于不工作的代码
  • 如果在迭代过程中的某个时刻,您存储了前 100 个项目,然后出现了一个小于第 100 个项目的项目,但不是很多。然后,稍后您扩展以存储 101 个项目,但是您丢弃的上一个项目现在比您现在看到的所有项目都大......换句话说,您丢弃了真正的第 101 个项目。这有意义吗?
  • @Lasse:这很有道理......也看不到任何方法。hmmm

标签: python algorithm


【解决方案1】:

我不确定是否有任何方法可以真正可靠地做到这一点,因为当您看到更多元素时,“前 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% 的元素,您最终会从前十个元素中选择 101 和 102,但在看到足够多的零之后,您最终不得不选择前十个元素.这种相同的模式可以扩展到任何足够大的流——总是有可能最终被外表误导并丢弃你实际上应该保留的元素。因此,除非您提前知道流的确切长度,否则我认为这是不可能的(除非您将每个元素都保留在内存中,直到您到达流的末尾)。

【讨论】:

  • 他必须创建一个最坏情况的数据结构。如果他知道它不能超过 3000 万个项目,他将不得不抓取 X 相对于 3000 万个计算的前 X 个项目。然后,一旦他知道实际计数,就丢弃不需要的值。
  • 这是个好主意...我可以对上限进行最坏情况的猜测,然后将其缩小。
【解决方案2】:

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

证明:你有一个数字序列,n1,…,nkk 的值未知。你怎么知道 ni 什么时候可以忘记?当您看到大于 ni 的 x*k/100 数时。但是,由于 k 是未知的,因此您永远不能这样做。

所以唯一的“一次性”算法必须将整个序列存储在内存中。

【讨论】:

  • 正如@Lasse 评论的那样:您必须存储整个流除非您知道k的上限。
【解决方案3】:

正如其他答案所讨论的那样,除了将整个流存储在内存中之外,您真的不能做得更好。考虑这样做,特别是因为 5-30 百万个浮点数可能只有 40-240 MB 的内存,这是可管理的。

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

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

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

这种方法是线性时间和线性空间,这是你可以期待的最好的。

【讨论】:

    【解决方案4】:

    下面是使用 cProfile 运行它的输出。您的代码似乎运行良好,因为大多数调用都是 0.000(percall)。它似乎很慢,仅仅是因为您有很多项目要处理。如果你想优化,你必须尝试和popless items,因为这就是它被称为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
    

    【讨论】:

    • 所有pop 调用都来自这样一个事实,即在这种情况下,当数据已经排序时,我遇到了最坏的情况。
    猜你喜欢
    • 2020-05-23
    • 2014-04-06
    • 2021-11-22
    • 1970-01-01
    • 1970-01-01
    • 2013-11-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-07
    相关资源
    最近更新 更多