与多个字典中给定键对应的一组值的交集

时间:2019-03-22 16:46:57

标签: python python-3.x dictionary set-intersection

我正在创建一个变量,用于测量3个不同字典中给定键的值的子样本的大小。例如,我想要对应于字典dict_a中的键A1,字典dict_b中的键b2和字典dict_c中的键c5的值集(即对应于3个字典中给定键的值集的交集)。

我已经编写了一个使用循环的代码,如下所示:

import numpy as np


dict_a = {'a1':[1,3,4], 'a2':[1,5,6,7,8,9,13]} 
dict_b = {'b1':[85,7,25], 'b2':[1,8,10,70], 'b3':[1,5,69,13], 'b4':[1,75,15,30]} 
dict_c = {'c1':[1,3,4], 'c2':[725,58,2,89], 'c3':[5,684,6,8,2], 'c4':[4,8,88,55,75,2,8], 'c5':[8,5,6,28,24,6], 'c6':[8,52,3,58,26,2]} 

keys_a =  list(dict_a.keys())
keys_b =  list(dict_b.keys())
keys_c =  list(dict_c.keys())


a= []
b= []
c= []
size = []
for y in keys_a:
    for u in keys_b:
        for w in keys_c:
            a.append(u)
            b.append(w)
            c.append(y)

            # Define subsample
            subsample = np.intersect1d(dict_a[y],dict_b[u],dict_c[w])
            size.append(len(subsample))

问题在于我的词典比示例中的词典大得多,并且运行时间很长。

有没有办法使它更有效?

2 个答案:

答案 0 :(得分:0)

如何使用集合?

size = []
for y in keys_a:
    for u in keys_b:
        for w in keys_c:
            common = set.intersection(set(dict_a[y]),
                                      set(dict_b[u]),
                                      set(dict_c[w]))
            size.append(len(common))

计算集合的相交也比将数字列表首先转换为数组然后再使用np.intersection快得多。

您可以对列表中的任何可哈希类型使用此方法。

答案 1 :(得分:0)

我将把它分成几部分。首先生成abc列表,然后使用numpy生成主要的size列表,最后对Python列表执行相同的操作。

获取密钥列表

因此,如果我们看一下,那么c实际上是dict_a中的键的列表,依此类推。我将假设这样做是有目的的,但是如果不是故意的,则将y替换为key_a,您会明白我的意思。

我们可以在不进入主循环的情况下轻松地预先计算出该值。每个项目仅由其他两个列表中的键数乘积来重复。我们可以用类似的方法做到这一点:

from itertools import repeat, chain


def key_summary(dict_1, dict_2, dict_3):
    count = len(dict_2) * len(dict_3)
    return chain(*(repeat(k, count) for k in dict_1.keys()))


a = list(key_summary(dict_b, dict_a, dict_c))
b = list(key_summary(dict_c, dict_a, dict_b))
c = list(key_summary(dict_a, dict_b, dict_c))

这应该更快,因为它没有深深地嵌套在循环中,但是鉴于计算起来很容易,我认为您可能想考虑为什么需要这样做。您可以在没有实际列出清单的情况下实现目标吗?

获取尺寸列表

我认为您没有正确使用intersect1d()函数。 The docs指出第三个参数是assume_unique,这不是我认为您要尝试的操作。我假设您希望出现在所有列表中的元素的一种实​​现方法是

np.intersect1d(np.intersect1d(val_a, val_b), val_c))

这建议一种优化循环的方法。代替在每个循环中计算val_aval_b的交集,我们可以只执行一次并重新使用它。

for val_a in dict_a.values():
    for val_b in dict_b.values():
        # Get the intersection of a and b first
        cache = np.intersect1d(val_a, val_b)

        if not len(cache):
            # Our first two sets have nothing in common, we know that we are
            # just going to add a bunch of zeros for everything in dict_c
            size.extend(repeat(0, len(dict_c)))
        else:
            size.extend(
                len(np.intersect1d(cache, val_c)) for val_c in dict_c.values())

这也使我们可以应用另一种优化方法,即如果dict_cval_a的交集不包含任何内容,则完全跳过对val_b的循环。如果val_a为空,我们也可以做类似的事情。

作为最后的优化,您应该始终使dict_a最小,dict_c最大,因为这给了我们最好的跳过步骤的机会。

进行上述操作后,我的速度提高了约200%(在给出的示例中为1.493ms-> 0.8ms)。

获取尺寸列表(使用Python集)

我假设您有充分的理由使用numpy函数,但是如果它们不是必需的,则可以将列表转换为集合,这些列表非常快速地在Python中执行交集。我们可以采用与上述非常相似的方法:

dset_a = {k: set(v) for k, v in dict_a.items()}
dset_b = {k: set(v) for k, v in dict_b.items()}
dset_c = {k: set(v) for k, v in dict_c.items()}

size = []
for val_a in dset_a.values():
    for val_b in dset_b.values():
        cache = val_a & val_b

        if not cache:
            size.extend(repeat(0, len(dict_c)))
        else:
            size.extend(len(cache & val_c) for val_c in dset_c.values())

在给定的示例上,这快得多。原来的时间为0.019毫秒,而原始时间为1.493毫秒(快约80倍!)。