4

我有一个元组列表,如下所述(此元组按第二个值的降序排序):

from string import ascii_letters
myTup = zip (ascii_letters, range(10)[::-1])
threshold = 5.5

>>> myTup
[('a', 9), ('b', 8), ('c', 7), ('d', 6), ('e', 5), ('f', 4), ('g', 3), ('h', 2), \
('i', 1), ('j', 0)]

给定一个阈值,丢弃第二个值小于该阈值的所有元组的最佳方法是什么。

我有超过 500 万个元组,因此不想逐个元组执行比较元组,从而删除或添加到另一个元组列表。

4

5 回答 5

7

由于元组已排序,您可以简单地搜索值低于阈值的第一个元组,然后使用切片表示法删除剩余的值:

index = next(i for i, (t1, t2) in enumerate(myTup) if t2 < threshold)
del myTup[index:]

正如 Vaughn Cato 所指出的,二分搜索会加快速度。bisect.bisect会很有用,除非您创建一个单独的键序列,否则它将不适用于您当前的数据结构,如此所述。但这违反了您禁止创建新列表的规定。

不过,您可以使用源代码作为您自己的二进制搜索的基础。或者,您可以更改数据结构:

>>> myTup
[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e'), (5, 'f'), 
 (6, 'g'), (7, 'h'), (8, 'i'), (9, 'j')]
>>> index = bisect.bisect(myTup, (threshold, None))
>>> del myTup[:index]
>>> myTup
[(6, 'g'), (7, 'h'), (8, 'i'), (9, 'j')]

这里的缺点是删除可能会在线性时间内发生,因为 Python 必须将整个内存块移回......除非 Python 很聪明地删除从0. (有人知道吗?)

最后,如果你真的愿意改变你的数据结构,你可以这样做:

[(-9, 'a'), (-8, 'b'), (-7, 'c'), (-6, 'd'), (-5, 'e'), (-4, 'f'), 
 (-3, 'g'), (-2, 'h'), (-1, 'i'), (0, 'j')]
>>> index = bisect.bisect(myTup, (-threshold, None))
>>> del myTup[index:]
>>> myTup
[(-9, 'a'), (-8, 'b'), (-7, 'c'), (-6, 'd')]

(请注意,Python 3 会抱怨None比较,所以你可以使用类似的东西(-threshold, chr(0))。)

我怀疑我一开始建议的线性时间搜索在大多数情况下都是可以接受的。

于 2012-09-12T15:37:14.630 回答
2

这是一种奇特的方法,它在执行 bisect 之前将列表包装在类似列表的对象中。

import bisect

def revkey(items):
    class Items:
        def __getitem__(self, index):
            assert 0 <= index < _len
            return items[_max-index][1]
        def __len__(self):
            return _len
        def bisect(self, value):
            return _len - bisect.bisect_left(self, value)
    _len = len(items)
    _max = _len-1
    return Items()

tuples = [('a', 9), ('b', 8), ('c', 7), ('d', 6), ('e', 5), ('f', 4), ('g', 3), ('h', 2), ('i', 1), ('j', 0)]

for x in range(-2, 12):
    assert len(tuples) == 10
    t = tuples[:]
    stop = revkey(t).bisect(x)
    del t[stop:]
    assert t == [item for item in tuples if item[1] >= x]
于 2012-09-12T18:02:17.433 回答
1

可能比@Curious 的代码快一点:

newTup=[]
for tup in myTup:
    if tup[1]>threshold:
        newTup.append(tup)
    else:
        break

因为元组是有序的,所以您不必遍历所有元组。

另一种可能性也是,使用二分法,并找到i最后一个元素的索引,该索引高于阈值。然后你会这样做:

newTup=myTup[:i]

我认为最后一种方法是最快的。

于 2012-09-12T15:40:19.070 回答
0

你也可以使用itertools例如

from itertools import ifilter
iterable_filtered = ifilter(lambda x : x[1] > threshold, myTup)

如果您想要一个可迭代的过滤列表,或者只是:

filtered = filter(lambda x: x[1] > threshold, myTup)

直接进入列表。

我对这些方法的相对性能不太熟悉,因此必须对其进行测试(例如,在IPython 中使用 %timeit)。

于 2012-09-12T15:40:25.050 回答
0

鉴于您正在处理的元组数量,您可能需要考虑使用NumPy

定义一个结构化数组,如

my_array= np.array(myTup, dtype=[('f0',"|S10"), ('f1',float)])

您可以访问元组的第二个元素,myarray['f1']从而为您提供一个浮点数组。你可以知道使用花哨的索引技术来过滤你想要的元素,比如

my_array[myarray['f1'] < threshold]

仅保留 yourf1小于 your threshold..的条目

于 2012-09-12T15:35:16.343 回答