Python 的 bisect 函数如何使用这个元组作为参数?

How does Python's bisect function use this tuple as an argument?

这是我正在学习的 Coursera 基因组学课程中提出的问题。我们目前正在处理搜索方法,特别是如何检测(对齐)较长 DNA 序列(t)中较短 DNA 序列(p,字符串)的所有出现,也是一个字符串)。所讨论的方法涉及为 t 创建一个 散列 table,由长度为 k 的所有可能子串组成 及其位置(每个子串都是所谓的 kmer)。然后,我们使用从 p 派生的相同长度 (kmers) 的子字符串搜索散列 table。这涉及创建 Index class 和最终使用 Python 的 bisect_left 函数.这是代码:

import bisect
import sys

class Index(object):

    def __init__(self, t, k):
        ''' Create index from all substrings of size 'length' '''
        self.k = k  # k-mer length (k)
        self.index = []
        for i in range(len(t) - k + 1):  # for each k-mer
            self.index.append((t[i:i+k], i))  # append (k-mer, offset) pair (tuple)
        self.index.sort()  # alphabetize by k-mer
    
    def query(self, p):
        ''' Return index hits for first k-mer of P '''
        kmer = p[:self.k]  # query with first k-mer
        i = bisect.bisect_left(self.index, (kmer, -1))  # binary search
        hits = []
        while i < len(self.index):  # collect matching index entries
            if self.index[i][0] != kmer:
                break
            hits.append(self.index[i][1])
            i += 1
        return hits

我的问题涉及 bisect_left 函数 的格式。 Python 文档给出了这个函数的语法如下:
bisect.bisect_left(a, x, lo=0, hi=len(a), *, key=None)
我课程中使用的bisect_left函数是这样格式化的:
i = bisect.bisect_left(self.index, (kmer, -1))
我知道参数 a 已经传递给参数 self.index,这是从 [= 创建的散列 table 22=]t。但是后来我对后面的 tuple 感到困惑。我看不到以下任何参数如何接受元组。我可以想象散列 table 被组织为 (kmer, position) 形式的元组,但是这些条目的 none 将具有位置 -1。讲师声明他出于特定目的将值设置为 -1,如下所示:

“所以第一步就是找到 p 的第一个 k 基础,以便在 tables 中查找它们。所以,我要说 kmer 等于 p 到长度 k,并且我们需要做 self.k。现在我想 找到这个 kmer 出现的列表中的第一个位置 ,所以我要使用 bisect 快速完成。说 i = bisect.bisect_left。然后我传入我们正在搜索的列表,然后是我们正在搜索的对象。记住这是一个列表元组所以我要传入kmer,然后我要放-1。并且 因为列表中的所有索引都大于负数,这确保我们总是得到第一次出现 。”

我的意思是,我懂英语,​​但我不明白 bisect 如何 (a) 将元组作为参数,以及 (b) 假设它可以(显然可以),它如何使使用的位置 -1 在散列 table 中不存在?我有一种预感,我不知道排序方面发生了什么,但我不能把我的手指放在上面。

非常感谢,很抱歉我的问题陈述太长了。

在 Python 中,您可以按字典顺序比较元组(就像在字典中一样)。首先比较第一个元素,然后比较第二个元素,依此类推。

>>> (1, 2, 3) < (1, 2, 4)
True
>>> (1, 3, 3) < (1, 2, 4)
False
>>> ('a', 'p', 'p', 'l', 'e') > ('a', 'r', 't')
False
>>> 

bisect.bisect_left 不限于整数或字符串,它适用于任何可以比较的对象。事实上,你可以在这里看到它是如何实现的:GitHub link

documentation on bisect_left says 一样,它会搜索要插入新元组的索引,以便列表仍然排序。

例如,这个列表排序:

[("a", 1), ("b", -42), ("c", 2), ("c", 3)]

这不是:

[("a", 1), ("c", 3),  ("b", -42), ("c", 3)]

bisect.bisect_left 与元组一起使用的示例:

from bisect import bisect_left

# The shopping list must be ordered by food type first, by
# quantity second.
shopping_list = [("apples", 100), ("bananas", 42), ("grapes", 250)]

# Say, we want to figure out where to put our cherries.
print(bisect_left(shopping_list, ("cherries", 666)))  # 2
# Our new list might look like this:
[("apples", 100), ("bananas", 42), ("cherries", 666), ("grapes", 250)]
# (just for illustration, bisect_left doesn't change the list)

# What if we want to find out where the bananas start?
print(bisect_left(shopping_list, ("bananas",)))  # 1

感谢 Kelly Bundy 的评论:(kmer,)(kmer, -1) 更适合使用。 ("foo",)确实小于("foo", whatever)。如果您的要求发生变化并且现在 -1 是一个有效的第二个元素,这会更好。


P.S。 self.index 列表不是散列 table。它只是一个排序的元组列表。它没有任何特殊的搜索功能(除了允许二进制搜索,这就是您正在做的)。 hash tables.

的解释有a great talk by Brandon Rhodes and this SO question