优化部分字典密钥匹配

时间:2011-10-03 15:15:37

标签: python algorithm optimization

我有一个字典,它使用4元组作为密钥。我需要找到字典中与部分其他元组部分匹配的所有键。我有一些代码可以做到这一点,但它很慢,需要优化。

这就是我追求的目标:

Keys:
(1, 2, 3, 4)
(1, 3, 5, 2)
(2, 4, 8, 7)
(1, 4, 3, 4)
Match:
(1, None, 3, None)
Result:
[(1, 2, 3, 4), (1, 4, 3, 4)]

当前代码:

def GetTuples(self, keyWords):
    tuples = []
    for k in self.chain.iterkeys():
        match = True
        for i in range(self.order):
            if keyWords[i] is not None and keyWords[i] != k[i]:
                match = False
                break
        if match is True:
            tuples.append(k)
    return tuples
  • keyWords是一个包含我想要匹配的值的列表
  • self.chain是字典
  • self.order是元组的大小
  • len(keyWords)always = len(k)
  • '无'被视为外卡
  • 字典非常庞大(这种方法需要大约800毫秒才能运行,大约300mb),所以空间也是一个考虑因素

我基本上都在寻找对这种方法的优化,或者更好的存储方法。

4 个答案:

答案 0 :(得分:4)

也许您可以通过维护密钥的索引来加快速度。基本上,这样的事情:

self.indices[2][5]

将包含set个所有键,其中5位于键的第三个位置。

然后你可以简单地在相关索引条目之间设置交集以获得密钥集:

matching_keys = None

for i in range(self.order):
    if keyWords[i] is not None:
        if matching_keys is None:
            matching_keys = self.indices[i][keyWords[i]]
        else:
            matching_keys &= self.indices[i][keyWords[i]]

matching_keys = list(matching_keys) if matching_keys else []

答案 1 :(得分:4)

使用数据库怎么样?

我更喜欢SQLite + SQLAlchemy,即使是简单的项目,但普通的sqlite3可能会有更温和的学习曲线。

在每个键列上添加索引应该考虑速度问题。

答案 2 :(得分:4)

如果将数据存储在普通字典中,则无法进一步优化这一点,因为它不会提供比以某种不可预测的顺序顺序访问字典中所有元素的速度更快的速度。这意味着您的解决方案不会比O(n)更快。

现在,数据库。数据库不是任何(足够复杂)问题的通用解决方案。您能否可靠地估计数据库的此类查找的速度/复杂性?如果滚动到此回复的底部,您将看到对于大型数据集,数据库性能可能比智能数据结构差得多。

这里需要的是手工制作的数据结构。有很多选择,它很大程度上取决于你使用这些数据做的其他事情。例如:您可以保留N组密钥的排序列表,每个列表按n排序 - 元组元素。然后,您可以快速选择N排序的元素集,这些元素仅匹配位置n处的一个元组元素,并找到它们的交集以获得结果。这将给出O(log n)*O(m)的平均性能,其中m是一个子集中平均元素数。

或者您可以将商品存储在k-d树中,这意味着您必须支付O(log n)插入价格,但您可以在O(log n)时间内执行上述查询。这是python中的一个例子,使用SciPy的k-d树实现:

from scipy.spatial import kdtree
import itertools
import random

random.seed(1)
data = list(itertools.permutations(range(10), 4))
random.shuffle(data)
data = data[:(len(data)/2)]

tree = kdtree.KDTree(data)

def match(a, b):
    assert len(a) == len(b)
    for i, v in enumerate(a):
        if v != b[i] and (v is not None) and (b[i] is not None):
            return False
    return True

def find_like(kdtree, needle):
    assert len(needle) == kdtree.m
    def do_find(tree, needle):
        if hasattr(tree, 'idx'):
            return list(itertools.ifilter(lambda x: match(needle, x),
                                          kdtree.data[tree.idx]))
        if needle[tree.split_dim] is None:
            return do_find(tree.less, needle) + do_find(tree.greater, needle)
        if needle[tree.split_dim] <= tree.split:
            return do_find(tree.less, needle)
        else:
            return do_find(tree.greater, needle)
    return do_find(kdtree.tree, needle)

def find_like_bf(kdtree, needle):
    assert len(needle) == kdtree.m
    return list(itertools.ifilter(lambda x: match(needle, x),
                                  kdtree.data))

import timeit
print "k-d tree:"
print "%.2f sec" % timeit.timeit("find_like(tree, (1, None, 2, None))",
                                "from __main__ import find_like, tree",
                                number=1000)
print "brute force:"
print "%.2f sec" % timeit.timeit("find_like_bf(tree, (1, None, 2, None))",
                                "from __main__ import find_like_bf, tree",
                                number=1000)

测试运行结果:

$ python lookup.py
k-d tree:
0.89 sec
brute force:
6.92 sec

只是为了好玩,还添加了基于数据库的解决方案基准。初始化代码从上面改为:

random.seed(1)
data = list(itertools.permutations(range(30), 4))
random.shuffle(data)

现在,“数据库”实现:

import sqlite3

db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE a (x1 INTEGER, x2 INTEGER, x3 INTEGER, x4 INTEGER)")
db.execute("CREATE INDEX x1 ON a(x1)")
db.execute("CREATE INDEX x2 ON a(x2)")
db.execute("CREATE INDEX x3 ON a(x3)")
db.execute("CREATE INDEX x4 ON a(x4)")

db.executemany("INSERT INTO a VALUES (?, ?, ?, ?)",
               [[int(x) for x in value] for value in tree.data])

def db_test():
    cur = db.cursor()
    cur.execute("SELECT * FROM a WHERE x1=? AND x3=?", (1, 2))
    return cur.fetchall()

print "sqlite db:"
print "%.2f sec" % timeit.timeit("db_test()",
                                 "from __main__ import db_test",
                                 number=100)

测试结果,每个基准测试减少100次运行(产生657720元素的键组):

$ python lookup.py
building tree
done in 6.97 sec
building db
done in 11.59 sec
k-d tree:
1.90 sec
sqlite db:
2.31 sec

值得一提的是,构建树花费的时间几乎要少两倍,然后将此测试数据集插入到数据库中。

此处填写完整资料来源:https://gist.github.com/1261449

答案 3 :(得分:2)

对Amber的回答嗤之以鼻:

>>> from collections import defaultdict
>>> index = defaultdict(lambda:defaultdict(set))
>>> keys = [(1, 2, 3, 4),
...         (1, 3, 5, 2),
...         (2, 4, 8, 7),
...         (1, 4, 3, 4),
...         ]
>>> for key in keys:
...     for i, val in enumerate(key):
...         index[i][val].add(key)
... 
>>> def match(goal):
...     res = []
...     for i, val in enumerate(goal):
...         if val is not None:
...             res.append(index[i][val])
...     return reduce(set.intersection, res)
... 
>>> match((1, None, 3, None))
set([(1, 4, 3, 4), (1, 2, 3, 4)])