簡體   English   中英

在元組列表中查找所有常見的N大小元組

[英]Find all common N-sized tuples in list of tuples

我必須創建一個執行以下操作的應用程序(我必須只解析一次數據並將它們存儲在數據庫中):

我得到K元組(K超過1000000),每個元組都是

(UUID, (tuple of N integers))

讓我們假設每個k元組的N等於20,並且每個20個大小的元組都被排序。 我已將以下兩種形式(2個不同的表)中的所有數據保存在數據庫中,以便我可以更輕松地處理它們:

  1. _id,UUID,tuple.as_a_string()
  2. _id,UUID,1st_elem,2nd_elem,3rd_3lem,... 20th_elem

目標是從元組列表中找到所有10個大小的元組,這樣每個元組都存在於多個20個大小的元組中。**

例如,如果給出以下兩個20個大小的元組:

(1, (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,161,17,18,19,20))
(2, (1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39))

常見的元組是:(1,3,5,7,9,11,13,15,17,19)

這是一個10大小的元組,結果如下:

(1, 2, (1,3,5,7,9,11,13,15,17,19))

為了實現這一點,我目前正在做的是(在Python 3中):

  • 使用數據庫中第1行的20大小元組的元素創建一個集合。
  • 使用數據庫中其余行的20個大小的元素的元素為每一行創建一個集合。
  • 對於第二個集合列表中的每個集合,我與第一個集合進行交集。
  • 然后我創建了10個元素的交集組合(在Python中它是itertools.combinations(new_set,10)) ,它給了我想要的結果。

但這個程序很慢 即使使用多處理來充分利用我的8個CPU內核,每個計算不同的數字,也需要永遠。 我的程序現在運行了2天,只有20%。

您對如何優化流程有任何想法嗎? NumPy數組會幫助執行速度嗎? 在SQL中有什么方法可以計算我想要的每一行,甚至一次一行?

提前致謝。

看起來您可以將元組放入矩陣的行中,並將行號從行號轉換為UUID。 然后將所有元組存儲在一個numpy數組中是可行的,因為元組的元素很小。 numpy具有能夠計算這種數組的行之間的交叉點的代碼。 此代碼生成組合以首先作為元組進行處理,然后進行比較。

from itertools import combinations
import numpy as np
from time import time

minInt=1
maxInt=100
tupleSize=20
intersectionSize=10

K=100
rows=np.zeros((K,tupleSize),dtype=np.int8)
print ('rows uses', rows.nbytes, 'bytes')

for i,c in enumerate(combinations(range(minInt,maxInt),tupleSize)):
    if i>=K:
        break
    for j,_ in enumerate(c):
        rows[i,j]=_

t_begin=time()
for i in range(K-1):
    for j in range(i+1,K):
        intrsect=np.intersect1d(rows[i],rows[j],True)
        if intrsect.shape[0]==intersectionSize:
            print (i,j,intrsect)
t_finish=time()

print ('K=', K, t_finish-t_begin, 'seconds')

以下是我家中舊的雙核P4 clunker的一些樣本測量結果。

行使用200字節K = 10 0.0009770393371582031秒

行使用1000個字節K = 50 0.0410161018371582秒

行使用2000字節K = 100 0.15625秒

行使用10000字節K = 500 3.610351085662842秒

行使用20000字節K = 1000 14.931640863418579秒

行使用100000字節K = 5000 379.5498049259186秒

如果您在計算機上運行代碼,則可以進行推斷。 我不知道它是否會使你的計算成為可能。

也許我會得到一堆反對票!

比爾,我認為這會產生更隨意的組合。 您的組合版本系統地通過選擇步驟。 對於小K,交叉點大小接近tupleSize

choices = np.arange(minInt, maxInt)
for i in range(K):
    rows[i,:] = np.random.choice(choices, tupleSize, replace=False)

使用setsnp.intersect1d快約4倍。

sets = [set(row) for row in rows]
dd = collections.defaultdict(int)
for i in range(K-1):
    for j in range(i+1,K):
        intrsect=sets[i].intersection(sets[j])
        dd[len(intrsect)] += 1

我切換到收集交叉點大小,因為它更有趣,對迭代策略不太敏感。

K = 5000時:

K= 5000 221.06068444252014 seconds
{0: 77209, 1: 514568, 2: 1524564, 3: 2653485, 4: 3044429, 5: 2436717, 6: 1408293, 7: 596370, 8: 188707, 9: 44262, 10: 7783, 11: 1012, 12: 93, 13: 8}

較短的時間僅適用於sets創建步驟; 那很快。

K= 5000 0.058181047439575195 46.79403018951416 seconds
{0: 77209, 1: 514568, 2: 1524564, 3: 2653485, 4: 3044429, 5: 2436717, 6: 1408293, 7: 596370, 8: 188707, 9: 44262, 10: 7783, 11: 1012, 12: 93, 13: 8}

對於更大的K.

K= 10000 818.3419544696808 seconds
{0: 309241, 1: 2058883, 2: 6096016, 3: 10625523, 4: 12184030, 5: 9749827, 6: 5620209, 7: 2386389, 8: 752233, 9: 176918, 10: 31168, 11: 4136, 12: 407, 13: 18, 14: 2}
K= 10000 0.09764814376831055 151.11484718322754 seconds

似乎沒有太多的希望:忘記combinations()部分,你需要檢查每對K元組的交集。 choose(K, 2) = K*(K-1)/2對,所以有一百萬元組有近5000億對。

您可以玩的一個低級技巧是將元組表示為整數而不是集合,其中當且僅當i在元組中時,位2**i在整數中設置。 既然你在評論中說元組不包含重復項,並且每個元組元素都在range(1, 100) ,那么100位整數就足夠了(它可以切成99位,但不值得麻煩)。

重點是整數的按位“和”比設置的交叉點快得多。 在我的盒子上,大約快7倍。 這里有一些代碼來說明這個概念,以及一些草率的計時結果(在同時執行大量其他工作的機器上運行):

def build(K, values, N):
    from random import sample
    sets = []
    ints = []
    for i in range(K):
        t = sample(values, N)
        sets.append(set(t))
        ints.append(sum(1 << i for i in t))
    return sets, ints

def run(K, values, N):
    from time import time
    as_sets, as_ints = build(K, values, N)
    for meth, collection in [("sets", as_sets),
                             ("ints", as_ints)]:
        s = time()
        for i in range(K-1):
            base = collection[i]
            for j in range(i+1, K):
                x = base & collection[j]
        t = time()
        print("K", K, meth, t-s)

for K in 5000, 10000, 20000:
    run(K, range(1, 100), 20)

並輸出:

K 5000 sets 7.322501182556152
K 5000 ints 1.0357279777526855
K 10000 sets 30.60071086883545
K 10000 ints 4.150524377822876
K 20000 sets 128.24610686302185
K 20000 ints 15.933331727981567

請注意,正如預期的那樣,任何一種方法的運行時間都是K的二次方(因此,加倍K需要大約4倍;而將K大10倍會使運行時間增加約10 ** 2 = 100)。

雖然使用整數的交點要快得多,但確定結果的基數較慢。 有很多方法可以做到這一點,但“最好的”取決於預期的分布和你對編碼疼痛的容忍度;-)這里是主要方法的概述。

暫無
暫無

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

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