Implementazione efficiente di Trie per stringhe unicode


12

Ho cercato un'implementazione efficiente di String trie. Principalmente ho trovato un codice come questo:

Implementazione referenziale in Java (per Wikipedia)

Non mi piacciono queste implementazioni per principalmente due motivi:

  1. Supportano solo 256 caratteri ASCII. Devo coprire cose come il cirillico.
  2. Sono estremamente inefficienti dalla memoria.

Ogni nodo contiene una matrice di 256 riferimenti, ovvero 4096 byte su una macchina a 64 bit in Java. Ognuno di questi nodi può avere fino a 256 nodi secondari con 4096 byte di riferimenti ciascuno. Quindi un Trie completo per ogni stringa di caratteri ASCII 2 richiederebbe un po 'più di 1 MB. Tre stringhe di caratteri? 256 MB solo per array nei nodi. E così via.

Ovviamente non ho intenzione di avere tutti i 16 milioni di tre stringhe di caratteri nel mio Trie, quindi molto spazio è solo sprecato. La maggior parte di questi array sono solo riferimenti null poiché la loro capacità supera di gran lunga il numero effettivo di chiavi inserite. E se aggiungo Unicode, gli array diventano ancora più grandi (char ha 64k valori invece di 256 in Java).

C'è qualche speranza di creare un trie efficiente per le corde? Ho preso in considerazione un paio di miglioramenti rispetto a questi tipi di implementazioni:

  • Invece di usare una matrice di riferimenti, potrei usare una matrice di tipo intero primitivo, che indicizza in una matrice di riferimenti a nodi la cui dimensione è vicina al numero di nodi effettivi.
  • Potrei spezzare le stringhe in parti a 4 bit che consentirebbero array di nodi di dimensione 16 al costo di un albero più profondo.

Risposte:


2

Per cosa stai usando questo trie? Qual è il numero totale di parole che prevedi di contenere e qual è la scarsità dei loro caratteri costitutivi? E, cosa più importante, un trie è persino appropriato (rispetto a una semplice mappa del prefisso all'elenco di parole)?

La tua idea di una tabella intermedia e la sostituzione di puntatori con indici funzionerà, a condizione che tu abbia un set relativamente piccolo di parole brevi e un set di caratteri sparsi. Altrimenti rischi di rimanere senza spazio nella tabella intermedia. E a meno che tu non stia guardando un set di parole estremamente piccolo, non risparmierai davvero tanto spazio: 2 byte per un breve periodo contro 4 byte per un riferimento su una macchina a 32 bit. Se stai utilizzando una JVM a 64 bit, i risparmi saranno maggiori.

La tua idea di suddividere i personaggi in blocchi di 4 bit probabilmente non ti farà risparmiare molto, a meno che tutti i tuoi personaggi previsti non siano in un intervallo estremamente limitato (forse OK per parole limitate a maiuscole US-ASCII, probabilmente con un corpus Unicode generale ).

Se hai un set di caratteri sparsi, allora HashMap<Character,Map<...>>potrebbe essere la tua migliore implementazione. Sì, ogni voce sarà molto più grande, ma se non hai molte voci otterrai una vittoria complessiva. (come nota a margine: ho sempre pensato che fosse divertente che l'articolo di Wikipedia su Tries mostrasse - forse lo fa ancora - un esempio basato su una struttura di dati con hash, ignorando completamente i compromessi spazio / tempo di quella scelta)

Infine, potresti voler evitare del tutto un trie. Se stai osservando un corpus di parole normali in un linguaggio umano (10.000 parole in uso attivo, con parole lunghe 4-8 caratteri), probabilmente starai MOLTO meglio con un HashMap<String,List<String>, dove la chiave è l'intero prefisso.


- I riferimenti sono 8 byte su 32 bit, 16 byte su macchine a 64 bit - È per la funzionalità di completamento automatico - La maggior parte dei caratteri nelle stringhe sono nell'intervallo ASCII, ma ci sono alcuni caratteri dell'Europa centrale inseriti. Ecco perché volevo ramificazioni più piccole di 256, perché taglierà un gran numero di caratteri. Non vedo HashMap <String, List <String>> migliore o più veloce o che consuma meno memoria, anche se davvero facile da scrivere e usare. Ma accetterò l'idea <Personaggio, Mappa> di HashMap. Andrebbe bene per i caratteri oltre 128 (raro nel mio caso - sarebbe male per il testo cinese).
RokL,

4

se si codificano le stringhe in UTF8 è possibile utilizzare il trie di ramificazione standard 256 ed essere ancora compatibili con Unicode

inoltre, dovresti notare che solo 70 o più caratteri su 128 caratteri ASCII possibili (che codificano tutti su 1 byte in UTF8) saranno trovati più pesantemente che puoi ottimizzare per quello (come includere i digraph comuni al posto dei caratteri di controllo non utilizzati )


So che UTF8 può essere rappresentato in questo modo. Tuttavia, ciò non risolve ancora il consumo di memoria, che è ancora piuttosto elevato. Scambiare i personaggi in un intervallo base di 256 richiederebbe un bel po 'di frasi di commutazione, ne dubito che ne varrebbe la pena. Per quanto riguarda UTF-8 ... questo è in realtà un problema su cui sto riflettendo proprio ora. Java String utilizza caratteri UTF-16, che posso facilmente ottenere, posso codificare questi byte per byte. Oppure posso convertire in UTF-8 e usarlo. A questo punto non mi è chiaro se il costo di conversione da UTF-16 a UTF-8 sia proibitivo o meno.
RokL

qual è la lingua che prevedi di utilizzarla nella maggior parte delle volte? cercare di ottimizzare per tutto è impossibile (o sarebbe già stato fatto), quindi ottimizzare per il caso comune
maniaco del cricchetto

1
Questo è uno dei pochissimi casi in cui CESU-8 sarebbe preferibile a UTF-8: è un grande vantaggio qui che è banale passare da un punto di codice UTF-8 al corrispondente punto di codice CESU-8 (mentre sarebbe necessario per decodificare 1-2 punti di codice UTF-16 per raggiungere i punti di codice UTF-8 corrispondenti).
Joachim Sauer,

1
@ratchetfreak Java. Anche se penso che la domanda possa essere generalizzata alla maggior parte delle lingue. Immagino che in C potresti semplicemente lanciare il puntatore byte*per codificare qualsiasi tipo in un trie bit a bit.
RokL

@UMad intendevo in quali lingue saranno le stringhe di input (inglese, francese, tedesco, ...)
maniaco del cricchetto
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.