Algoritmo per la ricerca veloce di tag


16

Il problema è il seguente.

  • C'è una serie di entità semplici E, ognuna con una serie di tag T attaccati. Ogni entità potrebbe avere un numero arbitrario di tag. Il numero totale di entità è vicino a 100 milioni e il numero totale di tag è di circa 5000.

Quindi i dati iniziali sono qualcosa del genere:

E1 - T1, T2, T3, ... Tn
E2 - T1, T5, T100, ... Tk
..
Ez - T10, T12, ... Tl

Questi dati iniziali vengono aggiornati abbastanza raramente.

  • In qualche modo la mia app genera un'espressione logica su tag come questo:

    T1, T2 e T3 | (T5 &! T6)

  • Ciò di cui ho bisogno è calcolare un numero di entità corrispondenti all'espressione data (nota - non le entità, ma solo il numero). Questo potrebbe non essere del tutto esatto, ovviamente.

Quello che ho ora è una semplice ricerca nella tabella in memoria, che mi dà un tempo di esecuzione di 5-10 secondi su un singolo thread.

Sono curioso, esiste un modo efficace per gestire queste cose? Quale approccio consiglieresti? Esistono alcuni algoritmi o strutture dati comuni per questo?

Aggiornare

Un po 'di chiarimenti come richiesto.

  1. Tgli oggetti sono in realtà stringhe costanti relativamente brevi. Ma in realtà non importa: possiamo sempre assegnare alcuni ID e operare su numeri interi.
  2. Possiamo sicuramente ordinarli.

1
è T1lo stesso riferimento oggetto per E1, E2ecc?
Reactgular,

come sono comparabili i tag? i tag possono essere ordinati in modo che T2 < T3sia sempre vero?
Reactgular,

I tag sono binari? Cioè T1è trueo falseper un dato E, e non variabile in base all'input? (ie Model = "V5") O è T1un'espressione variabile come Model = <input>?
Bobson,

Risposte:


4

lo farei in sql con una tabella di collegamenti EntityCategorytra eidentità di cidriferimento e categoria di riferimento usando i self-join:

    select count(ec1.eid)
    from EntityCategory ec1 
    left join EntityCategory ec2 on ec1.eid=ec2.eid 
    left join EntityCategory ec3 on ec1.eid=ec3.eid 
    ...
    where 
      ec1.cid={categoryId1} and 
      ec2.cid={categoryId2} and
      ec3.cid={categoryId3} ...

1
+1, questo è il classico territorio DB. L'altra risposta potrebbe avere idee ragionevoli su come codificarla manualmente, ma questa dovrebbe essere l'ultima risorsa.
MSalters il

Vorrei anche scegliere sql come tecnica per risolvere questo problema. La maggior parte dei database è piuttosto ottimizzata per questi algoritmi :)
winkbrace,

3

Dopo aver scritto questa risposta, dovrei probabilmente contrassegnare la domanda come "troppo ampia": possiamo parlare per secoli di varie strategie, alla fine dovrà essere eseguito un benchmark con i tuoi dati.

Ogni tag può essere rappresentato in modo efficiente da un numero intero. Ogni entità ha un set di tag. È importante scegliere l'implementazione corretta del set: sono possibili sia alberi B sia array ordinati. Con questo set, eseguiremo solo test di appartenenza. Dato che entrambe le strutture fanno questo in O (log t) (con tag t per entità), preferirei le matrici a causa della loro rappresentazione più densa.

Ora possiamo filtrare tutte le entità in un'operazione O (n · log t · p) , dove p è la lunghezza media del percorso nell'albero decisionale del predicato. Questo albero decisionale può essere ordinato in modo tale che una decisione possa essere raggiunta rapidamente. Senza dati statistici, è possibile solo fattorizzare la sottoespressione comune.

L'ordine in cui vengono cercate le entità non è molto importante. D'altra parte può essere vantaggioso di risolvere tale che le entità a indici 0a itutti hanno un certo tag, mentre il resto non. Ciò riduce la n durante la ricerca di questo tag specifico (nella struttura decisionale, questo dovrebbe essere il primo test). Questo può essere espanso a più livelli, ma ciò complica le cose e richiede memoria O (2 k ) con klivelli. Con più livelli, i tag con il guadagno più alto dovrebbero essere decisi per primi, dove il guadagno è il numero di entità che non è necessario cercare volte la probabilità di scartarle. Il guadagno diventa massimo per le probabilità 50:50 o quando il 50% delle entità ha questo tag specifico. Ciò consentirebbe di ottimizzare anche se i modelli di accesso non sono noti.

È inoltre possibile creare set che indicizzano le entità in base a ciascun tag utilizzato: un set con tutte le entità per T1, il successivo per T2. Un'ovvia ottimizzazione (spazio e tempo) è quella di interrompere quando un set contiene più della metà di tutti gli elementi e di salvare quegli elementi che non hanno questo tag - in questo modo, la costruzione di indici per tutti i tag richiederà meno dello ½ · n · tspazio (con t tag in totale). Notare che il salvataggio di set complementari può rendere più difficili altre ottimizzazioni. Ancora una volta, vorrei (ordinato) gli array per i set.

Se si rappresentano anche le entità attraverso un intervallo intero, è possibile comprimere lo spazio utilizzato per i set di indici memorizzando solo il membro iniziale e finale di un intervallo continuo. Dal punto di vista dell'implementazione, ciò verrebbe probabilmente fatto con un bit alto per indicare se una voce è un limite di intervallo o una voce regolare.

Se ora disponiamo di set di indici (e quindi di statistiche sui tag), possiamo ottimizzare i nostri predicati in modo da testare prima le proprietà improbabili (strategia fail-fast). Ciò significa che se T1è comune ed T2è raro, il predicato T1 & T2deve essere valutato ripetendo tutte le T2voci del set di indici e testando ciascun elemento per T1.

Se utilizziamo array ordinati per implementare i set di indici, è possibile implementare molti passaggi di valutazione come operazioni di fusione. T1 & T2significa che prendiamo gli elenchi T1e T2, allochiamo un array di destinazione delle dimensioni degli input più grandi ed eseguiamo il seguente algoritmo fino a quando entrambi gli input sono vuoti: If T1[0] < T2[0], then T1++(scarta la testa). Se T1[0] > T2[0]allora T2++. Se entrambe le teste sono uguali, allora copi verso la matrice di destinazione, e incrementare tutte tre puntatori ( T1, T2, target). Se il predicato è T1 | T2, allora nessun elemento viene scartato ma quello più piccolo viene copiato. Un predicato della forma T1 & ¬T2può anche essere implementato usando una strategia fusione, ma ¬T1o T1 | ¬T2no.

Questo dovrebbe essere preso in considerazione quando si ordina l'albero delle decisioni del predicato: i complementi dovrebbero essere presenti sull'RHS di un &, o alla fine, quando viene determinato il conteggio finale e gli elementi reali non devono essere esaminati.

Senza usare i set di indici, ogni thread può filtrare attraverso la sua parte delle entità e restituire il conteggio degli elementi che corrispondono al predicato, che può essere riassunto. Quando si utilizzano set di indici, a ciascun thread viene assegnato un nodo nella struttura decisionale. Prende due flussi di input che corrispondono ai set ordinati e restituiscono un flusso unito. Si noti che ogni nodo nella struttura decisionale ha un set corrispondente che rappresenta tutte le entità che soddisfano quella sottoespressione e che, a causa dell'ordinamento degli insiemi, non è necessario conoscere l'intero insieme in una volta per unirli .

Diverse strategie come la fusione di insiemi indicizzati o il filtraggio attraverso un elenco di entità possono essere combinate in una certa misura. Il filtro ha prestazioni molto prevedibili. Se una query è molto specifica in modo che l'utilizzo dei set di indici riduca drasticamente lo spazio di ricerca, le operazioni di unione potrebbero essere migliori. È importante notare che la fusione di molti set di input di grandi dimensioni può comportare prestazioni molto peggiori rispetto alla ricerca della forza bruta. Un algoritmo molto ottimizzato sceglierà una strategia adatta in base alla dimensione dell'input, alla struttura della query e agli indicatori statistici.

A parte questo, la memorizzazione nella cache dei risultati parziali può essere utile se si prevede che query simili verranno eseguite in futuro, anche se non accelerano la corsa iniziale.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.