簡體   English   中英

如何加速Python中集合字典的交集

[英]How to speed up intersection of dict of sets in Python

我有一本包含一組整數的字典。

{'A': {9, 203, 404, 481},
 'B': {9},
 'C': {110},
 'D': {9, 314, 426},
 'E': {59, 395, 405}
}

您可以使用以下方法生成數據:

data = {}
for i in string.ascii_uppercase:
    n = 25
    rng = np.random.default_rng()
    data[i] = set(rng.choice(100, size=n, replace=False))

我需要獲取字典子集的交集列表。 因此,在示例中,['A','B','D'] 的交集的輸出將返回 [9]

我已經想出了 2 種不同的方法來做到這一點,但是當套裝價值增長時,這兩種方法都會變慢。

cols = ['A','B','D']

# method 1 
lis = list(map(data.get, cols))
idx = list(set.intersection(*lis))

#method 2 (10x slower then method 1)
query_dict = dict((k, data[k]) for k in cols)
idx2 = list(reduce(set.intersection, (set(val) for val in query_dict.values())))

當集合增長(每集 >10k int)時,運行時會快速增長。

我可以使用其他數據類型,然后在 dict 中設置,如列表或 numpy 數組等。

有沒有更快的方法來完成這個?

編輯:

我最初遇到的問題是這個數據框:

    T       S       A   B   C   D
0   49.378  1.057   AA  AB  AA  AA
1   1.584   1.107   BC  BA  AA  AA
2   1.095   0.000   BB  BB  AD  
3   10.572  1.224   BA  AB  AA  AA
4   0.000   0.000   DC  BA  AB  

對於每一行,我必須對具有共同 A、B、C、D 的所有行求和 'T',如果達到閾值,則繼續在 B、C、D 上繼續,然后是 C、D,然后只有 D 如果還沒有達到門檻。

但是這真的很慢,所以首先我嘗試使用 get_dummies 然后獲取列的乘積。 然而,這很慢,所以我轉向帶有索引的 numpy 數組進行求和。 這是迄今為止最快的選擇,但是相交是唯一仍然需要太多時間來計算的東西。

編輯2:

事實證明,我對自己太苛刻了,使用 pandas groupby 是可能的,而且速度非常快。

代碼:

parts = [['A','B','C','D'],['B','C','D'],['C','D'],['D']]
for part in parts:
    temp_df = df.groupby(part,as_index=False).sum()
    temp_df = temp_df[temp_df['T'] > 100]
    df = pd.merge(df,temp_df,on=part,how='left',suffixes=["","_" + "".join(part)])

df['T_sum'] = df[['T_ABCD','T_BCD','T_CD','T_D']].min(axis=1)
df['S_sum'] = df[['S_ABCD','S_BCD','S_CD','S_D']].min(axis=1)
df.drop(['T_ABCD','T_BCD','T_CD','T_D','S_ABCD','S_BCD','S_CD','S_D'],, axis=1, inplace=True)

可能代碼可以更簡潔一些,但我不知道如何在合並中僅替換 NaN 值。

這里的問題是如何有效地找到幾個集合的交集。 根據評論: “最大 n 是 1000 萬 - 3000 萬,列 a、b、c、d 幾乎可以是唯一的行,共有 100 萬行。” 所以集合很大,但大小不一樣。 集合交集是一個結合交換操作,所以我們可以按照我們喜歡的任何順序取交集。

兩個集合相交的時間復雜度是O(min(len(set1), len(set2))) ,所以我們應該選擇一個順序來做交集,這樣可以最小化中間集的大小。


如果我們事先不知道哪些集合對有小的交集,我們能做的最好的事情就是按大小順序將它們相交。 在第一個交集之后,最小的集合總是最后一個交集的結果,所以我們想把它與下一個最小的輸入集相交。 這是更好地利用set.intersection一次,而不是對所有的套reduce在這里,因為這是實現基本相同的方式reduce會做,但在C.

def intersect_sets(sets):
    return set.intersection(*sorted(sets, key=len))

在我們對成對交集一無所知的情況下,C 實現中唯一可能的放緩可能是為多個中間集分配了不必要的內存。 這可以通過例如{ x for x in first_set if all(x in s for s in other_sets) }來避免,但結果證明要慢得多。


我用最大 600 萬的設置對其進行了測試,大約有 10% 的成對重疊。 這是四組相交的時間; 四點之后,累加器大約是原始大小的 0.1%,因此任何進一步的交叉點無論如何都將花費可以忽略不計的時間。 橙色線表示最佳順序(最小的兩個在前)的相交集,藍線表示最差的順序(最大的兩個在前)的相交集。

次

正如預期的那樣,兩者都在設定的大小中花費大致線性的時間,但有很多噪音,因為我沒有對多個樣本進行平均。 在相同的數據上測量,最優順序始終是最差順序的 2-3 倍,大概是因為這是最小和第二大集合大小之間的比率。

在我的機器上,4組大小2-600萬相交大約需要100ms,所以上到3000萬應該需要半秒左右; 我認為你不太可能擊敗它,但半秒應該沒問題。 如果它始終比您的真實數據花費的時間長得多,那么問題就在於您的數據不是均勻隨機的。 如果是這種情況,那么除此之外,Stack Overflow 可能不會為您做太多事情,因為提高效率將在很大程度上取決於您的真實數據的特定分布(盡管請參閱下文有關您必須回答相同問題的許多查詢的情況)套)。

我的計時代碼如下。

import string
import random

def gen_sets(m, min_n, max_n):
    n_range = range(min_n, max_n)
    x_range = range(min_n * 10, max_n * 10)
    return [
        set(random.sample(x_range, n))
        for n in [min_n, max_n, *random.sample(n_range, m - 2)]
    ]

def intersect_best_order(sets):
    return set.intersection(*sorted(sets, key=len))

def intersect_worst_order(sets):
    return set.intersection(*sorted(sets, key=len, reverse=True))

from timeit import timeit

print('min_n', 'max_n', 'best order', 'worst order', sep='\t')
for min_n in range(100000, 2000001, 100000):
    max_n = min_n * 3
    data = gen_sets(4, min_n, max_n)
    t1 = timeit(lambda: intersect_best_order(data), number=1)
    t2 = timeit(lambda: intersect_worst_order(data), number=1)
    print(min_n, max_n, t1, t2, sep='\t')

如果你需要做很多查詢,那么首先計算成對交集可能是值得的:

from itertools import combinations

pairwise_intersection_sizes = {
    (a, b): set_a & set_b
    for ((a, set_a), (b, set_b)) in combinations(data.items(), 2)
}

如果某些交集比其他交集小很多,那么可以使用預先計算的成對交集來選擇更好的順序進行set.intersection in。 給定一些集合,您可以選擇具有最小預計算交集的對,然后進行set.intersection on該預先計算的結果以及其余的輸入集。 特別是在一些成對交叉點幾乎為空的非均勻情況下,這可能是一個很大的改進。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM