【问题标题】:Faster re-implementation for finding unique keys in python更快地重新实现在 python 中查找唯一键
【发布时间】:2021-01-09 01:59:14
【问题描述】:

我有一个代码 sn-p 用于根据其值查找唯一键。这样,对于包含在具有相同 value 的另一个键 D 中的所有键 s,键 s 被丢弃/删除并返回键 D。

输入:mydict = { 'C': 4, 'A': 4, 'B': 4, 'CA': 3, 'AB': 4, 'BC': 4, 'ABC': 3 }

输出:{'CA': 3, 'AB': 4, 'BC': 4, 'ABC': 3}

newdict = {}

for key1, value1 in mydict.items():
  for key2, value2 in mydict.items():
    if ((key1 in key2) and (value1 == value2)):
      if key1 in newdict:
        del newdict[key1]
      newdict[key2] = value2

print(newdict)

我希望在 python 3 中实现更快的计算时间和更少的内存消耗。

请帮忙?!!!

【问题讨论】:

  • 如果您的代码确实有效并且您“只”想要改进,请考虑将其发布到code review stack exchange
  • 您需要就地修改,还是创建一个新的dict 可以?
  • 它可以去任何一种方式。主要问题是得到一个代码 sn-p,它可以用更快的计算时间和更少的内存来做到这一点
  • @JanStránský,感谢您的评论。

标签: python python-3.x dictionary key dictionary-comprehension


【解决方案1】:

首先,您的实现中有一个错误。想象一下mydict = {'AB': 1, 'A': 1},并且项目是按书面顺序读取的:

  • 第一步,key1 = 'AB'value1 = 1

    • key2 = 'AB', value2 = 1 我们有key1 in key2 and value1 == value2,因此newdict['AB'] = 1
    • key2 = 'A', value2 = 1 我们没有key1 in key2,因此newdict 仍然是{'AB': 1}
  • 第二步,key1 = 'A'value1 = 1

    • key2 = 'AB', value2 = 1 我们有key1 in key2 and value1 == value2。由于key1 不在newdict 中,所以它不会被丢弃。再说一次,newdict['AB'] = 1
    • key2 = 'A'value2 = 1 我们有key1 in key2 and value1 == value2。由于key1 不在newdict 中,所以它不会被丢弃。然后,newdict['A'] = 1

结论:mydict = {'AB': 1, 'A': 1} 但应该是{'AB': 1}

要解决此问题,您必须通过增加 len 对键进行排序,以确保丢弃较小的键:

def original_func(mydict):
    newdict = {}
    # sort by inc key len
    items = sorted(mydict.items(), key=lambda i: len(i[0]))

    for key1, value1 in items:
        for key2, value2 in items:
            if key1 in key2 and value1 == value2:
                if key1 in newdict:
                    del newdict[key1]
                newdict[key2] = value2

    return newdict

其次,算法的时间复杂度为O(n^2 * K),其中K是最长key的大小。我假设您在实际用例中有更大的字典。您可以通过以下方式改进:

  1. 只比较具有相同值的键;
  2. 仅将键与之前的超级键进行比较,而不是所有键;

如果您有 V 不同的值和 S < n/V 相同值的平均超级键,则平均时间复杂度将大致为 O(V * S^2 * K)。除非您处于退化的情况下,否则这会更快。

为了测试这一点,我们创建了一个巨大的字典:

from random import shuffle, randint

mydict = {}
s = list("ABCDEFGHIJ")
for i in range(1000):
    shuffle(s)
    key = "".join(s[:randint(1, 7)])
    mydict[key] = randint(1, 7)

然后是函数:

def new_func(mydict):
    # group the keys by value
    keys_by_value = {}
    for k, v in mydict.items():
        keys_by_value.setdefault(v, []).append(k)

    newdict = {}
    for v, ks in keys_by_value.items():
        # for each value, find the superkeys
        for sk in find_superkeys(ks):
            # and add the mapping superkey -> value
            newdict[sk] = v

    return newdict

当然,程序的核心是find_superkeys 函数。这里没有魔术,但是您可以将键分组len,因为同一 len 的两个键要么相等要么不相交(也许有人知道更快的方法?):

def find_superkeys(ks):
    # group keys by len (-len to sort by decreasing len)
    keys_by_neg_len = {}
    for k in ks:
        keys_by_neg_len.setdefault(-len(k), []).append(k)

    superkeys  = []
    for _, ks in sorted(keys_by_neg_len.items()):
        cur = []
        for k in ks:
            # keep k if k not in any of superkeys
            # by using a decreasing len, we avoid useless tests like 'AB' in 'A'
            if all(k not in k2 for k2 in superkeys):
                cur.append(k)

        # here, cur contains superkeys of a given len
        superkeys += cur

    return superkeys

还有基准:

import timeit

assert original_func(mydict) == new_func(mydict)
print(timeit.timeit(lambda: original_func(mydict), number=100))
# 1.6872997790005684
print(timeit.timeit(lambda: new_func(mydict), number=100))
# 0.15774182000041037

【讨论】:

  • 这个解决方案很有帮助。但您可以在这里找到其他解决方案:codereview.stackexchange.com/questions/249732/…
  • @VictorAdeyemo 有趣。这是通常的时空权衡:all(k not in k2 for k2 in superkeys) 的结果是预先计算的。我做了一些基准测试:codereview 答案在大字典中获胜(使用小键甚至更好)。但是对于较小的字典(大约 1000 个元素),我的版本更快。我认为你可以两全其美:预先计算代码审查版本中的结果,并使用我的优化。
  • 没错,我运行了基准测试并得出了相同的结论。
猜你喜欢
  • 2019-07-24
  • 1970-01-01
  • 2014-01-12
  • 2013-09-07
  • 2023-02-16
  • 2012-10-18
  • 2010-12-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多