我有一本约150,000键的字典。没有重复的密钥。每个密钥长度为127个字符,每个密钥在1-11个位置不同(大多数差异发生在密钥的末尾)。每个键的值是唯一ID和空白列表[]。对于给定的密钥,我想找到所有其他密钥相差1个字符,然后将ID附加到给定的密钥空白列表中。最后,我想要一个键及其值(一个ID和一个字符的列表,不同于一个字符)。
我的代码有效,但问题是它太慢了。双循环是150,000 ^ 2 = ~25亿。在我的计算机上,我每分钟可以循环大约200万次(每次都执行match1函数)。这需要大约8天才能完成。没有match1函数的循环运行速度快〜7倍,因此可以在~1天内完成。
我想知道是否有人知道如何提高速度呢?
# example dictionary
dict = {'key1' : ['1', []], 'key2' : ['2', []], ... , 'key150000' : ['150000', []]}
def match1(s1,s2,dict):
s = 0
for c1, c2 in zip(reversed(s1), reversed(s2)):
if s < 2:
if c1 != c2:
s = s + 1
else:
break
if s == 1:
dict1[s1][1].append(dict1[s2][0])
for s1 in dict:
for s2 in dict:
match1(s1,s2,dict)
答案 0 :(得分:2)
目前,您正在针对每个其他密钥检查每个密钥,以进行总共O(n^2)
次比较。我们只需要检查其他密钥的一小部分即可。
假设每个键的字符具有k
个不同值的字母表。例如,如果您的密钥是由a-z
和0-9
组成的简单ASCII字符串,那么k = 26 + 10 = 30
。
给定任何密钥,我们可以生成所有可能的密钥,这些密钥只有一个字符:有127 * k
个字符串。在您将每个密钥与~150,000个其他密钥进行比较之前,现在我们只需要与127 * k
进行比较,对于k = 30
的情况,这是3810。这会将总时间复杂度从O(n^2)
降低到O(n * k)
,其中k
是一个独立于n
的常量。 此是您扩展n
时真正加速的地方。
这里有一些代码用于生成密钥的所有可能邻居:
def generate_neighbors(key, alphabet):
for i in range(len(key)):
left, right = key[:i], key[i+1:]
for char in alphabet:
if char != key[i]:
yield left + char + right
所以,例如:
>>> set(generate_neighbors('ab', {'a', 'b', 'c', 'd'}))
{'aa', 'ac', 'ad', 'bb', 'cb', 'db'}
现在我们计算每个键的邻域:
def compute_neighborhoods(data, alphabet):
keyset = set(data.keys())
for key in data:
possible_neighbors = set(generate_neighbors(key, alphabet))
neighbors = possible_neighbors & keyset
identifier = data[key][0]
for neighbor in neighbors:
data[neighbor][1].append(identifier)
现在举个例子。假设
data = {
'0a': [4, []],
'1f': [9, []],
'27': [3, []],
'32': [8, []],
'3f': [6, []],
'47': [1, []],
'7c': [2, []],
'a1': [0, []],
'c8': [7, []],
'e2': [5, []]
}
然后:
>>> alphabet = set('abcdef01234567890')
>>> compute_neighborhoods(data, alphabet)
>>> data
{'0a': [4, []],
'1f': [9, [6]],
'27': [3, [1]],
'32': [8, [5, 6]],
'3f': [6, [8, 9]],
'47': [1, [3]],
'7c': [2, []],
'a1': [0, []],
'c8': [7, []],
'e2': [5, [8]]}
我还没有在这里实施一些优化措施。首先,你说这些关键字在后来的字符上大多不同,并且它们最多在11个位置上有所不同。这意味着我们可以更聪明地计算交叉点possible_neighbors & keyset
并生成邻域。首先,我们修改generate_neighbors
以首先修改密钥的尾随字符。然后,我们不是一次生成整个邻居集,而是一次生成一个邻居并检查是否包含在data
字典中。我们记录了我们找到了多少,如果我们找到11,我们就会破解。
我在答案中没有实现这一点的原因是我不确定它会导致显着的加速,并且实际上可能更慢,因为这意味着删除使用纯Python循环优化Python内置(集合交集)。
答案 1 :(得分:1)
这是未经测试的,所以可能只是闲置的推测,但是......你可以减少字典查找的数量(更重要的是)通过将dict构建到列表中并仅比较剩余项目来消除一半的比较在列表中。
_dict = {'key1' : ['1', []], 'key2' : ['2', []], ... , 'key150000' : ['150000', []]}
# assuming python 3
itemlist = list(_dict.items())
while itemlist:
key1, value1 = itemlist.pop()
for key2, value2 in itemlist:
# doesn't have early short circuit but may have fewer lookups to compensate
if sum(c1 == c2 for c1, c2 in zip(key1, key2)) == 1:
value1[1].append(key2)
value2[1].append(key1)
答案 2 :(得分:1)
试试这段代码:
# example dictionary
dict = {'key1' : ['1', []], 'key2' : ['2', []], ... , 'key150000' : ['150000', []]}
def match1(s1,s2,dict):
s = 0
#reverse and zip computations are avoided
index = 127-1
while (index>=0 && s<2):
if(s1[index] == s2[index]):
s = s + 1
if (s == 1):
#we are modifying both s1 and s2 instead of only s1 to improve performance
dict1[s1][1].append(dict1[s2][0])
dict1[s2][1].append(dict1[s1][0])
keys = dict.keys()
#no of times match1 will be invoked is (n-1)*n/2 instead of n*n
for i in range(0, len(keys)):
for j in range(i+1, len(keys)):
#if match1(s1,s2,dict) is invoked then no need to call match1(s2,s1,dict) because now match1 function will take care of it. So only either one needs to be called
match1(keys[i],keys[j],dict)
Optmizations:
答案 3 :(得分:0)
对于键匹配部分,使用Levenshtein匹配进行极快的比较。 Python-Levenshtein是基于c-extention的实现。使用它的hamming
()函数来确定不同字符的数量。
使用Git链接安装它:
pip install git+git://github.com/ztane/python-Levenshtein.git
现在,通过将其插入@ tdelaney的答案,使用如下:
import Levenshtein as lv
itemlist = list(_dict.items())
while itemlist:
if lv.hamming(key1, key2) == 1:
key1, value1 = itemlist.pop()
for key2, value2 in itemlist:
value1[1].append(key2)
value2[1].append(key1)