我在that question上了解了如何在元组列表中使用bisect
,并使用该信息回答that question。它有效,但我想要一个更通用的解决方案。
由于bisect
不允许指定key
函数,如果我有这个:
import bisect
test_array = [(1,2),(3,4),(5,6),(5,7000),(7,8),(9,10)]
我希望找到第x > 5
个(x,y)
元组的第一个项目(根本不考虑y
,我目前正在这样做:
bisect.bisect_left(test_array,(5,10000))
我得到了正确的结果,因为我知道没有y
大于10000,所以bisect
将我指向(7,8)
的索引。如果我改为1000
,那就错了。
对于整数,我可以做
bisect.bisect_left(test_array,(5+1,))
但是在一般情况下可能存在浮点数时,如何在不知道第二个元素的最大值的情况下进行该操作?
test_array = [(1,2),(3,4),(5.2,6),(5.2,7000),(5.3,8),(9,10)]
我试过这个:
bisect.bisect_left(test_array,(min_value+sys.float_info.epsilon,))
它没有用,但我试过了:
bisect.bisect_left(test_array,(min_value+sys.float_info.epsilon*3,))
它有效。但这感觉就像一个糟糕的黑客。任何干净的解决方案?
答案 0 :(得分:3)
这是一个允许任意键功能的(quick'n'dirty)bisect_left实现:
def bisect(lst, value, key=None):
if key is None:
key = lambda x: x
def bis(lo, hi=len(lst)):
while lo < hi:
mid = (lo + hi) // 2
if key(lst[mid]) < value:
lo = mid + 1
else:
hi = mid
return lo
return bis(0)
> from _operator import itemgetter
> test_array = [(1, 2), (3, 4), (4, 3), (5.2, 6), (5.2, 7000), (5.3, 8), (9, 10)]
> print(bisect(test_array, 5, key=itemgetter(0)))
3
这可以保持O(log_N)
的效果,因为它不组装新的list
个键。二进制搜索的实现广泛可用,但这是直接从bisect_left
source获得的。
还应注意,列表需要根据相同的键功能进行排序。
答案 1 :(得分:3)
bisect
支持任意序列。如果您需要将bisect
与密钥一起使用,而不是将密钥传递给bisect
,则可以将其构建为序列:
class KeyList(object):
# bisect doesn't accept a key function, so we build the key into our sequence.
def __init__(self, l, key):
self.l = l
self.key = key
def __len__(self):
return len(self.l)
def __getitem__(self, index):
return self.key(self.l[index])
然后,您可以将bisect
与KeyList
一起使用,具有O(log n)性能,无需复制bisect
来源或编写自己的二分搜索:
bisect.bisect_right(KeyList(test_array, key=lambda x: x[0]), 5)
答案 2 :(得分:2)
为此:
...想找到第一个项目,其中x&gt; 5为那些(x,y)元组(根本不考虑y)
类似的东西:
import bisect
test_array = [(1,2),(3,4),(5,6),(5,7000),(7,8),(9,10)]
first_elem = [elem[0] for elem in test_array]
print(bisect.bisect_right(first_elem, 5))
bisect_right函数将第一个索引过去,因为你只关心元组的第一个元素,所以这部分看起来很简单。 ...仍然没有推广到我意识到的特定关键功能。
正如@ Jean-FrançoisFabre指出的那样,我们已经在处理整个数组了,所以使用bisect可能甚至不是很有帮助。
不确定它是否更快,但我们可以选择使用像itertools这样的东西(是的,这有点难看):
import itertools
test_array = [(1,2),(3,4),(5,6),(5,7000),(7,8),(9,10)]
print(itertools.ifilter(
lambda tp: tp[1][0]>5,
((ix, num) for ix, num in enumerate(test_array))).next()[0]
)
答案 3 :(得分:2)
作为一个很好的建议的补充,我想添加我自己的回答,它适用于花车(正如我刚想的那样)
bisect.bisect_left(test_array,(min_value+abs(min_value)*sys.float_info.epsilon),))
会起作用(min_value
是否为正)。 epsilon
乘以min_value
保证在添加到min_value
时有意义(未被吸收/取消)。因此,它与min_value
的最接近的值更大,而bisect
可以使用它。
如果你只有整数仍然会更快&amp;更清晰:
bisect.bisect_left(test_array,(min_value+1,))