Partizione di un elenco di set di elementi condivisi
Domanda
Ecco il jist del problema:Dato un elenco di imposta, come ad esempio:
[ (1,2,3), (5,2,6), (7,8,9), (6,12,13), (21,8,34), (19,20) ]
Restituire un elenco di gruppi di imposta, in modo tale che gli insiemi che hanno un comune elemento sono nello stesso gruppo.
[ [ (1,2,3), (5,2,6), (6,12,13) ], [ (7,8,9), (21,8,34) ], [ (19,20) ] ]
Nota il stickeyness - set (6,12,13) non hanno condiviso l'elemento con (1,2,3), ma vengono messe nello stesso gruppo a causa di (5,2,6).
A complicare le cose, devo dire che non ho questi graziosi set, ma piuttosto una tabella DB con diversi milioni di righe che si presenta come:
element | set_id
----------------
1 | 1
2 | 1
3 | 1
5 | 2
2 | 2
6 | 2
e così via.Così mi piacerebbe un modo per farlo in SQL, ma vorrei essere felice con una direzione generale per la soluzione.
MODIFICA:Cambiato i nomi delle colonne della tabella a (elemento, set_id) invece di (chiave, group_id), per rendere i termini più coerente.Nota che Kev risposta usa il vecchio nomi di colonna.
Soluzione
Il problema è esattamente il calcolo delle componenti connesse di un hypergraph:i numeri sono i vertici, e il set sono le hyperedges.Un modo consueto di calcolo dei componenti collegati è da alluvioni uno dopo l'altro:
- per tutti i = 1 to N do:
- se mi è stata contrassegnata da alcuni j < io, per poi continuare (intendo passare alla prossima mi)
- altro flood_from(i,i)
dove flood_from(i,j) sono definiti come
- per ogni insieme S contenente l'ho, se non è già contrassegnati da j quindi:
- tag S j e per ogni elemento di k di S, se k non è già contrassegnati da j, allora il tag da parte di j, e chiamata flood_from(k,j)
Il tag dell'imposta poi darà i componenti collegati che stai cercando.
In termini di banche dati, l'algoritmo può essere espresso come segue:si aggiunge un TAG colonna del database, e si calcola il componente collegato di impostare i lavoretti
- S = selezionare tutte le righe in cui set_id = = = = = i
- set di TAG per i per le righe in S
- S' = selezionare tutte le righe in cui il TAG non viene impostato e il cui elemento è l'elemento(S)
- mentre S' non è vuoto, non
- ---- set di TAG per i per le righe in S'
- ---- S" = selezionare tutte le righe in cui il TAG non viene impostato e il cui elemento è l'elemento(S')
- ---- S = S union S'
- ---- S' = S"
- ritorno set_id(S)
Altro (teorico) modo di presentare questo algoritmo, sarebbe a dire che siete in cerca di punti fissi di un mapping:
- se A = {A1, ..., Unn} è un insieme di insiemi, definire unione(A) = A1 unione ...unionen
- se K = {k1, ..., kp} è un insieme di numeri interi, definire incidenze(K) = l'insieme degli insiemi che si intersecano K
Quindi, se S è un insieme, il componente collegato di S è ottenuto mediante iterazione (incidenze)o(unione) in S fino a un punto fisso è raggiunto:
- K = S
- K' = incidenza(unione(K)).
- se K == K', per poi tornare a K altro K = K' e vai a 2.
Altri suggerimenti
Si potrebbe pensare ad esso come un grafico problema in cui il set (1,2,3) è collegato al set (5,2,6) via 2.E quindi utilizzare un algoritmo standard per bene collegato sub-grafici.
Ecco una rapida implementazione di python:
nodes = [ [1,2,3], [2,4,5], [6,7,8], [10,11,12], [7,10,13], [12], [] ]
links = [ set() for x in nodes ]
#first find the links
for n in range(len(nodes)):
for item in nodes[n]:
for m in range(n+1, len(nodes)):
if (item in nodes[m]):
links[n].add(m)
links[m].add(n)
sets = []
nodes_not_in_a_set = range(len(nodes))
while len(nodes_not_in_a_set) > 0:
nodes_to_explore = [nodes_not_in_a_set.pop()]
current_set = set()
while len(nodes_to_explore) > 0:
current_node = nodes_to_explore.pop()
current_set.add(current_node)
if current_node in nodes_not_in_a_set:
nodes_not_in_a_set.remove(current_node)
for l in links[current_node]:
if l not in current_set and l not in nodes_to_explore:
nodes_to_explore.append(l)
if len(current_set) > 0:
sets.append(current_set)
for s in sets:
print [nodes[n] for n in s]
output:
[[]]
[[6, 7, 8], [10, 11, 12], [7, 10, 13], [12]]
[[1, 2, 3], [2, 4, 5]]
Questo è probabilmente piuttosto inefficiente, ma dovrebbe funzionare, almeno:Iniziare con un tasto, selezionare tutti i gruppi che lo contengono tasto, selezionare tutte le chiavi di quei gruppi, selezionare tutte le categorie che contengono le chiavi, ecc.... e non appena un passo che non aggiunge nuove chiavi o gruppi, si dispone di un elenco di tutti i gruppi di una sub-grafico.Escludere tali e ripetere dall'inizio fino a quando non si hanno dati a sinistra.
In termini di SQL questo avrebbe bisogno di una stored procedure, penso.CON RICORSIVA potrebbe aiutare in qualche modo, ma non ho alcuna esperienza con esso ancora, e io non sono sicuro disponibile sul vostro DB backend.
Dopo aver pensato a questo un po ' di più, mi è uscito questo:
- Creare una tabella denominata
groups
con colonne(group_id, set_id)
- Ordinare l'
sets
tabellaelement
.Ora dovrebbe essere facile da trovare elementi duplicati. - Scorrere il set di tavolo, e quando si trova un elemento duplicato fare:
- se uno dei
set_id
campi esiste ingroups
tabella, aggiungere l'altra con la stessagroup_id
. - Se non
set_id
esiste ingroups
tabella, generare un nuovo ID di gruppo e aggiungere siaset_id
sgroups
tabella.
- se uno dei
Alla fine dovrei avere un groups
tabella contenente tutti i set.
Questo non è puro SQL, ma sembra che O(nlogn), quindi credo che posso vivere con questo.
Matt risposta sembra più corretto, ma non so come implementarlo nel mio caso.