【问题标题】:using bisect on list of tuples but compare using first value only在元组列表上使用 bisect 但仅使用第一个值进行比较
【发布时间】:2017-02-09 20:42:26
【问题描述】:

我阅读了 that question 关于如何在元组列表上使用 bisect 的信息,并使用该信息来回答 that question。它有效,但我想要一个更通用的解决方案。

由于bisect 不允许指定key 函数,如果我有这个:

import bisect
test_array = [(1,2),(3,4),(5,6),(5,7000),(7,8),(9,10)]

我想为那些(x,y) 元组找到x > 5 的第一项(根本不考虑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,))

它奏效了。但这感觉像是一个糟糕的黑客攻击。有什么干净的解决方案吗?

【问题讨论】:

  • 有这个SortedCollectioncode recipe是在bisect docs中推荐的,用于使用bisect和key函数。
  • 是的;我可以复制对分代码并更改比较功能(当您想创建一个快速的答案时不太方便)
  • @schwobaseggl 不错的发现。我不知道如何在不依赖链接的情况下将其转化为答案。如果您找到方法,我会投票并接受。他们什么时候会在库或语言本身中集成如此出色的食谱?

标签: python comparison tuples bisect


【解决方案1】:

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])

然后您可以使用bisectKeyList,具有O(log n) 性能,无需复制bisect 源或编写自己的二进制搜索:

bisect.bisect_right(KeyList(test_array, key=lambda x: x[0]), 5)

【讨论】:

  • 我接受那个,只是因为复制 bisect 的源代码失去了使用编译版本的优势,在流行的平台上可用。
【解决方案2】:

这是一个 (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_leftsource。 还需要注意的是,列表需要针对相同的按键功能进行排序。

【讨论】:

  • 这应该在 bisect_left 中作为一个选项来实现...你考虑过吗?
【解决方案3】:

为此:

...想要为那些 (x,y) 元组找到 x > 5 的第一项(根本不考虑 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]
)

【讨论】:

  • 所以它需要你创建一个辅助列表,但如果有很多 bisect 要做,这甚至可能更快,因为你甚至没有看第二个元素。一点也不差。
  • @Jean-FrançoisFabre:是的,不幸的是,有点取舍(这里没有免费的午餐)。
  • 因为您正在遍历列表,您还可以计算第二个元素的最大值(将避免创建另一个列表)。
  • @Jean-FrançoisFabre:是的...我想一旦需要遍历列表,仅使用实际循环可能比使用 bisect 更好
  • 如果您只有 1 个插入要执行,则为 true,但如果您有很多插入要执行,则不是 true(在这种情况下,您必须在辅助列表中插入 / 比较插入的元组的第二个值反对最大)。不是微不足道的。
【解决方案4】:

作为不错的建议的补充,我想添加我自己的答案,它适用于浮点数(正如我刚刚发现的那样)

bisect.bisect_left(test_array,(min_value+abs(min_value)*sys.float_info.epsilon),))

会起作用(无论min_value 是否为正)。 epsilon 乘以 min_value 保证在添加到 min_value 时是有意义的(它不会被吸收/取消)。所以它是最接近min_value 的更大值,bisect 可以使用它。

如果您只有整数仍然会更快、更清晰:

bisect.bisect_left(test_array,(min_value+1,))

【讨论】:

    猜你喜欢
    • 2019-11-14
    • 1970-01-01
    • 2013-11-19
    • 1970-01-01
    • 1970-01-01
    • 2012-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多