我有一个元组列表,每个元组中有3个成员,如下所示:
[(-5092793511388848640, 'test1', 1),
(-5092793511388848639, 'test0', 0),
(-5092793511388848638, 'test3', 3),
(-5092793511388848637, 'test2', 2),
(-5092793511388848636, 'test5', 5)]
元组按照每个元组的第一个元素按升序排序 - 每个键的哈希值(例如'test0'
)。我想找到一种快速搜索这些元组的方法,使用二进制搜索其哈希值来查找特定键。问题是我发现使用for循环的最快方法:
def get(key, D, hasher=hash):
'''
Returns the value in the dictionary corresponding to the given key.
Arguements:
key -- desired key to retrieve the value of.
D -- intended dictionary to retrieve value from.
hasher -- the hash function to be used on the key.
'''
for item in D:
if item[0] == hash(key):
return item[2]
raise TypeError('Key not found in the dictionary.')
上面写的函数在搜索更长的元组列表时似乎非常慢,让我们说一个包含6000个不同元组的列表。如果存在任何哈希冲突,它也会中断。我想知道是否有更有效/快捷的方法来搜索列表中正确的元组?
旁注:我知道使用词典将是一种更快捷,更简单的方法来解决我的问题,但我想避免使用它们。
答案 0 :(得分:3)
首先,预先关键,不要一遍又一遍地做。其次,您可以将next
与解包生成器表达式一起使用以优化位:
def get(key, D, hasher=hash):
keyhash = hasher(key)
try:
return next(v for hsh, k, v in D if keyhash == hsh and key == k)
except StopIteration:
raise TypeError('Key not found in the dictionary.')
那就是说,你声称要进行二分搜索,但上面仍然是线性搜索,只是为了避免冗余工作而优化,并在找到所需的密钥时停止(它首先检查哈希,假设密钥比较是昂贵的,然后只在哈希匹配上检查密钥相等,因为你抱怨重复的问题)。如果目标是二进制搜索,并且D
按哈希码排序,则您需要使用the bisect
module。这样做并不简单(因为bisect
不会像key
那样使用sorted
参数,但是如果你可以将D
分成两部分,一部分就是哈希代码,以及代码,键和值的代码,您可以这样做:
import bisect
def get(key, Dhashes, D, hasher=hash):
keyhash = hasher(key)
# Search whole list of hashes for beginning of range with correct hash
start = bisect.bisect_left(Dhashes, keyhash)
# Search for end point of correct hashes (limit to entries after start for speed)
end = bisect.bisect_right(Dhashes, keyhash, start)
try:
# Linear search of only start->end indices for exact key
return next(v for hsh, k, v in D[start:end] if key == k)
except StopIteration:
raise TypeError('Key not found in the dictionary.')
这样可以获得真正的二分查找,但如上所述,要求在搜索之前将哈希码提前与tuple
的{{1}}分开。在每次搜索时拆分哈希码是不值得的,因为将它们分开的循环可能只是直接找到了你想要的值(如果你一次执行多次搜索,那只会分裂)。 p>
正如Padraic在his answer中所说的那样,以放弃C加速器代码为代价,你可以复制和修改bisect.bisect_right
和bisect.bisect_left
的纯Python实现,改变addEventListener的每次使用{1}} hashcode, key, value
a[mid]
会为您提供a[mid][0]
代码,不需要您维护单独的bisect
哈希值。节省内存可能值得更高的查找成本。不要使用list
来执行切片,因为带有itertools.islice
索引的islice
会迭代整个start
直到那一点;真正的切片只会读取和复制您关心的内容。如果您想避免第二次list
操作,您可以随时编写自己的bisect
- 优化Sequence
并将其与islice
结合使用,以获得类似的效果,而无需预先计算itertools.takewhile
索引。代码可能类似于:
end
注意:from itertools import takewhile
# Copied from bisect.bisect_left, with unused arguments removed and only
# index 0 of each tuple checked
def bisect_idx0_left(a, x):
lo, hi = 0, len(a)
while lo < hi:
mid = (lo+hi)//2
if a[mid][0] < x: lo = mid+1
else: hi = mid
return lo
def sequence_skipper(seq, start):
return (seq[i] for i in xrange(start, len(seq)))
def get(key, D, hasher=hash):
keyhash = hasher(key)
# Search whole list of hashes for beginning of range with correct hash
start = bisect_idx0_left(D, keyhash)
# Make lazy iterator that skips start values in the list
# and stops producing values when the hash stops matching
hashmatches = takewhile(lambda x: keyhash == x[0], sequence_skipper(D, start))
try:
# Linear search of only indices with matching hashes for exact key
return next(v for hsh, k, v in hashmatches if key == k)
except StopIteration:
raise TypeError('Key not found in the dictionary.')
实际成为Dhashes
对,您可以以更多内存为代价来节省更多工作;假设唯一性,这意味着单个(hashcode, key)
调用,而不是两个,并且不需要在bisect.bisect*
匹配的索引之间进行扫描;你要么在二进制搜索中找到它,要么你没有找到它。例如,我生成了1000个键值对,将它们存储为key
中的(hashcode, key, value)
tuple
(我在list
上排序)或{{{ 1}}映射hashcode
s-&gt; dict
s。 key
s都是65位value
s(足够长,哈希码不是一个简单的自映射)。使用我在上面提供的线性搜索代码,找到位于索引321的值需要大约15微秒;使用二进制搜索(仅将哈希复制到单独的key
),它只需要超过2微秒。在相应的int
中查找~55 _nano_seconds;即使对于二进制搜索,运行时间开销也达到了~37x,线性搜索运行高出约270x。这是在我们进入增加的内存成本,增加代码复杂性以及增加维护排序顺序的开销之前(假设list
被修改)。
最后:你说“我想避免使用[dict
s]”,但不解释原因。 D
是解决这类问题的正确方法;假设没有自我散列(即dict
是一个散列到自身的dict
,可能会节省散列码的代价),仅key
int
的内存开销s(不包括单独的list
哈希码)将(大致)是值的简单tuple
映射键的两倍。 list
还可以防止意外存储重复项,插入费用为dict
(即使使用dict
,插入维护排序顺序也会有O(1)
次查找和bisect
内存移动成本),〜O(log n)
查询成本(与O(n)
对比〜O(1)
),除了大O差异外,还可以使用C内置函数完成所有工作经过大量优化,真正节省的成本会更高。
答案 1 :(得分:1)
您可以修改bisect
以检查第一个元素:
def bisect_left(a, x, lo=0, hi=None):
if lo < 0:
raise ValueError('lo must be non-negative')
if hi is None:
hi = len(a)
while lo < hi:
mid = (lo+hi) // 2
if a[mid][0] < x:
lo = mid+1
else: hi = mid
return lo
def get_bis(key, d):
h = hash(key)
ind = bisect_left(d, h)
if ind == -1:
raise KeyError()
for i in xrange(ind, len(d)):
if d[i][0] != h:
raise KeyError()
if d[i][1] == key:
return d[i][2]
raise KeyError()
复制一些碰撞,它会做它应该做的事情:
In [41]: l = [(-5092793511388848640, 'test1', 1), (-5092793511388848639, 'test9', 0), (-5092793511388848639, 'test0', 3), (-5092793511388848637, 'test2', 2), (-5092793511388848636, 'test5', 5)]
In [42]: get("test0", l)
Out[42]: 3
In [43]: get("test1", l)
Out[43]: 1
In [44]: get(-5092793511388848639, l)
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-44-81e928da1ac8> in <module>()
----> 1 get(-5092793511388848639, l)
<ipython-input-30-499e71432196> in get(key, d)
6 for sub in islice(d, ind, None):
7 if sub[0] != h:
----> 8 raise KeyError()
9 if sub[1] == key:
10 return sub
KeyError:
一些时间:
In [91]: l = sorted((hash(s), s,randint(1,100000)) for s in ("".join(sample(ascii_letters,randint(10,26))) for _ in xrange(1000000)))
In [92]: l[-1]
Out[92]: (9223342880888029755, 'FocWPinpYZXjHhBqRkJxQeGMa', 43768)
In [93]: timeit get_bis(l[-1][1],l)hed
100000 loops, best of 3: 5.29 µs per loop
In [94]: l[250000]
Out[94]: (-4616437486317828880, 'qXsybdhFPLczWwCQkm', 86136)
In [95]: timeit get_bis(l[250000][1],l)
100000 loops, best of 3: 4.4 µs per loop
In [96]: l[750000]
Out[96]: (4623630109115829672, 'dlQewhpMoBGmn', 39904)
In [97]: timeit get_bis(l[750000][1],l)
100000 loops, best of 3: 4.46 µs per loop
为了获得更好的想法,您必须抛出碰撞,但要找到散列可能属于的部分非常有效。
只需键入一些变量并使用cython进行编译:
def cython_bisect_left(a, long x, long lo=0):
if lo < 0:
raise ValueError('lo must be non-negative')
cdef long hi = len(a)
while lo < hi:
mid = (lo + hi) // 2
if a[mid][0] < x:
lo = mid + 1
else:
hi = mid
return lo
def cython_get(str key, d):
cdef long h = hash(key)
cdef ind = cython_bisect_left(d, h)
if ind == -1:
raise KeyError()
for i in xrange(ind, len(d)):
if d[i][0] != h:
raise KeyError()
if d[i][1] == key:
return d[i][2]
raise KeyError()
让我们几乎降到1微秒:
In [13]: timeit cython_get(l[-1][1],l)
The slowest run took 40.77 times longer than the fastest. This could mean that an intermediate result is being cached
1000000 loops, best of 3: 1.44 µs per loop
In [14]: timeit cython_get(l[250000][1],l)
1000000 loops, best of 3: 1.33 µs per loop
In [15]: timeit cython_get(l[750000][1],l)
1000000 loops, best of 3: 1.33 µs per loop
答案 2 :(得分:0)
尝试使用列表推导。我不确定它是否是最有效的方式,但它是 pythonic 方式并且非常有效!
public static string GetMood()
{
return mood;
}