Questo è uno dei progetti di ricerca che sto attualmente attraversando. Il requisito è quasi esattamente come il tuo e abbiamo sviluppato dei simpatici algoritmi per risolvere il problema.
L'input
L'input è un flusso infinito di parole o frasi inglesi (le chiamiamo tokens
).
Il risultato
- Emetti i migliori N token che abbiamo visto finora (da tutti i token che abbiamo visto!)
- Produci i primi N token in una finestra storica, ad esempio l'ultimo giorno o la settimana scorsa.
Un'applicazione di questa ricerca è trovare l'argomento caldo o le tendenze dell'argomento su Twitter o Facebook. Abbiamo un crawler che esegue la scansione del sito Web, che genera un flusso di parole, che alimenterà il sistema. Il sistema quindi emetterà le parole o le frasi di massima frequenza sia a livello generale che storico. Immagina che nelle ultime due settimane la frase "Coppa del mondo" sia apparsa molte volte su Twitter. Così fa "Paul the octopus". :)
String in numeri interi
Il sistema ha un ID intero per ogni parola. Sebbene ci siano quasi infinite parole possibili su Internet, ma dopo aver accumulato un ampio insieme di parole, la possibilità di trovare nuove parole diventa sempre più bassa. Abbiamo già trovato 4 milioni di parole diverse e assegnato un ID univoco a ciascuna. L'intero set di dati può essere caricato in memoria come tabella hash, consumando circa 300 MB di memoria. (Abbiamo implementato la nostra tabella hash. L'implementazione di Java richiede un enorme sovraccarico di memoria)
Ogni frase può quindi essere identificata come una matrice di numeri interi.
Questo è importante, perché l'ordinamento e il confronto sugli interi è molto più veloce che sulle stringhe.
Dati di archivio
Il sistema conserva i dati di archivio per ogni token. Fondamentalmente sono coppie di (Token, Frequency)
. Tuttavia, la tabella che memorizza i dati sarebbe così enorme da dover partizionare fisicamente la tabella. Una volta che lo schema di partizione è basato su ngram del token. Se il token è una singola parola, è 1 grammo. Se il token è una frase di due parole, è 2gram. E questo continua. All'incirca a 4 grammi abbiamo 1 miliardo di record, con una dimensione della tabella di circa 60 GB.
Elaborazione di flussi in entrata
Il sistema assorbirà le frasi in arrivo fino a quando la memoria non sarà completamente utilizzata (Sì, abbiamo bisogno di un MemoryManager). Dopo aver preso N frasi e averlo memorizzato, il sistema si ferma e inizia a tokenizzare ogni frase in parole e frasi. Ogni token (parola o frase) viene conteggiato.
Per i gettoni molto frequenti, vengono sempre mantenuti in memoria. Per i token meno frequenti, vengono ordinati in base agli ID (ricorda che traduciamo la stringa in un array di numeri interi) e serializzati in un file su disco.
(Tuttavia, per il tuo problema, dal momento che stai contando solo parole, puoi mettere tutta la mappa della frequenza delle parole solo in memoria. Una struttura di dati progettata con cura consumerebbe solo 300 MB di memoria per 4 milioni di parole diverse. Qualche suggerimento: usa caratteri ASCII per rappresentano stringhe), e questo è molto accettabile.
Nel frattempo, ci sarà un altro processo che si attiva una volta che trova un file su disco generato dal sistema, quindi inizia a unirlo. Poiché il file su disco è ordinato, l'unione richiederebbe un processo simile come l'ordinamento di unione. Anche in questo caso è necessario curare alcuni progetti, poiché vogliamo evitare troppe ricerche casuali su disco. L'idea è di evitare la lettura (processo di unione) / scrittura (output di sistema) allo stesso tempo e lasciare che il processo di unione legga da un disco mentre scrive su un disco diverso. È simile all'implementazione di un blocco.
Fine del giorno
Alla fine della giornata, il sistema avrà molti token frequenti con la frequenza archiviata in memoria e molti altri token meno frequenti archiviati in diversi file su disco (e ogni file è ordinato).
Il sistema scarica la mappa in memoria in un file su disco (ordinalo). Ora, il problema diventa l'unione di un insieme di file su disco ordinati. Utilizzando un processo simile, otterremmo un file su disco ordinato alla fine.
Quindi, l'operazione finale è unire il file del disco ordinato nel database di archivio. Dipende dalla dimensione del database di archivio, l'algoritmo funziona come di seguito se è abbastanza grande:
for each record in sorted disk file
update archive database by increasing frequency
if rowcount == 0 then put the record into a list
end for
for each record in the list of having rowcount == 0
insert into archive database
end for
L'intuizione è che dopo qualche tempo il numero di inserimenti diventerà sempre più piccolo. Sempre più operazioni saranno solo sull'aggiornamento. E questo aggiornamento non sarà penalizzato da index.
Spero che questa intera spiegazione possa aiutare. :)
what is the most frequent item in the subsequence [2; 2; 3; 3; 3; 4; 4; 4; 4; 5; 5] of your sequence?