【问题标题】:Optimising partial dictionary key match优化部分字典键匹配
【发布时间】:2011-10-04 19:53:16
【问题描述】:

我有一个字典,它使用 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) 总是 = len(k)
  • “无”被视为通配符
  • 字典非常庞大(此方法运行大约需要 800 毫秒,大约需要 300 mb),所以空间也是一个考虑因素

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

【问题讨论】:

  • Nones可以出现在keyWords的任意位置吗?
  • +1 用于提出reduce 在答案中的问题。
  • 是的,任何位置都可以有任意数量的None。
  • 一个可能对某些人有用的小提示 - 仅调用 range() 一次(并使用它提供的范围,而不是在 for 循环中每次都调用它)减少了功能需要约 50% 才能完成。

标签: python algorithm optimization


【解决方案1】:

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

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

您需要的是手工制作的数据结构。有很多选择,这在很大程度上取决于您对这些数据所做的其他事情。例如:您可以保留 N 键的排序列表集,每个列表按 n-th 元组元素排序。然后你可以快速选择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

【讨论】:

    【解决方案2】:

    只使用数据库怎么样?

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

    在每个键列上放置索引应该可以解决速度问题。

    【讨论】:

    • 这是一个对我的程序进行更高级别优化的好主意,谢谢!完全没有想到这个:)
    • +1 不使用数据库的人注定要重新发明它们。
    • 公平地说,“我正在重新发明一个数据库!”在我开始写一个涉及设置交叉点的建议之后,蜂鸣器才在我脑海里响起......
    • SQL 数据库在性能方面很少是适合内存的数据集的最佳解决方案,尤其是在此类问题的查询语义有限的情况下。
    【解决方案3】:

    也许您可以通过维护键的索引来加快速度。本质上是这样的:

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

    【讨论】:

    • 这是个好主意,但可能的键范围很大——我以个位数为例,但实际上键是一个 4 元组的字符串。
    • 您仍然可以使用相同的想法 - 使用完整的字符串,或者如果字符串非常长,则使用它们的哈希值。哎呀,您甚至可以通过简单地将字符串的单个整数校验和存储为其“索引键”来加快速度。即使有冲突,简单地减少搜索空间也会有很大帮助。
    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 2019-05-01
      • 2021-07-09
      • 2013-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-04
      相关资源
      最近更新 更多