2009-03-08 8 views
70

Ho un programma Java che memorizza molti mapping dalle stringhe a vari oggetti.Dove trovo un'implementazione di mappe basata su Trie standard in Java?

In questo momento, le mie opzioni sono o fare affidamento su di hashing (via HashMap) o su ricerche binarie (via TreeMap). Mi chiedo se esista un'implementazione di mappa efficiente e standard basata su trie in una libreria di raccolte di qualità e popolare?

Ho scritto il mio in passato, ma preferirei andare con qualcosa di standard, se disponibile.

chiarificazione rapida: Mentre la mia domanda è generale, nel progetto attuale ho a che fare con un sacco di dati che viene indicizzato da nome di classe completo o firma del metodo. Quindi, ci sono molti prefissi condivisi.

+0

le stringhe sono note in anticipo? Devono essere accessibili solo con una stringa? – sfossen

risposta

29

Si potrebbe desiderare di guardare il Trie implementation that Limewire is contributing a Google Guava.

+8

Sembra che Google-Collections sia stato sostituito da Guava https://code.google.com/p/guava-libraries/, e sfortunatamente non riesco a vedere una classe Trie lì da nessuna parte. La Patricia Trie sembra avere una propria pagina di progetto ora: https://code.google.com/p/patricia-trie/ –

+1

I link di Limewire/Google sono un po 'un casino anche adesso. Mentre sono riuscito a trovare https://code.google.com/archive/p/google-collections/issues/5 con i file effettivi, nota che [Apache Commons Collections] (https://commons.apache.org/ proper/commons-collections /) viene fornito con [un numero di tentativi] (https://commons.apache.org/proper/commons-collections/javadocs/api-release/index.html) (incluso un trie patricia). Questo è quello che raccomanderei in questo momento. –

+0

Anche l'implementazione di Apache Commons sembra provenire dallo stesso posto del contributo di Limewire, poiché i commenti di riepilogo nella documentazione di Commons per PatriciaTrie sono identici ai commenti di riepilogo nell'implementazione fornita da Limewire. –

1

Quello che vi serve è org.apache.commons.collections.FastTreeMap, credo.

+0

Questa non sembra un'implementazione trie. –

0

Si potrebbe guardare this TopCoder uno pure (da tutto ...).

+0

mi sono registrato ma quel componente è rito non prevedibile ora. – Deepak

9

Non esiste una struttura dati trie nelle librerie Java core.

Ciò può essere dovuto tentativi sono generalmente progettati per memorizzare stringhe di caratteri, mentre le strutture di dati Java sono più generali, di solito ospitare ogni Object (che definisce l'uguaglianza e un'operazione di hash), anche se a volte sono limitati a Comparable oggetti (la definizione di un ordine). Non esiste un'astrazione comune per "una sequenza di simboli", sebbene lo CharSequence sia adatto per le stringhe di caratteri e suppongo che potresti fare qualcosa con Iterable per altri tipi di simboli.

Ecco un altro punto da considerare: quando si cerca di implementare un trie convenzionali in Java, che si sta rapidamente di fronte al fatto che Java supporta Unicode. Per avere qualsiasi tipo di efficienza dello spazio, devi limitare le stringhe nel tuo trie a qualche sottoinsieme di simboli, o abbandonare l'approccio convenzionale di memorizzare i nodi figli in una matrice indicizzata dal simbolo. Questo potrebbe essere un altro motivo per cui i tentativi non sono considerati sufficientemente generici per l'inclusione nella libreria principale e qualcosa da tenere a mente se si implementa il proprio o si utilizza una libreria di terze parti.

+0

Questa risposta presuppone che voglio implementare un trie per le stringhe. Un trie è una struttura dati * generale *, in grado di contenere sequenze arbitrarie e di fornire rapide ricerche di prefissi. –

+1

@PaulDraper Questa risposta non presuppone nulla di ciò che desideri, dal momento che ti sei presentato anni dopo che la domanda è stata posta. E poiché la domanda riguarda specificamente le stringhe di caratteri, questo è il punto centrale di questa risposta. Anche se trascorro molto tempo sottolineando che un trie di Java avrebbe bisogno di essere generalizzato a qualsiasi tipo di "Comparable". – erickson

0

Se hai richiesto la mappa ordinata, allora vale la pena provare. Se non lo fai allora hashmap è migliore. HashMap con le chiavi di stringa può essere migliorata nel corso l'implementazione Java di serie: Array hash map

3

ho scritto e pubblicato una semplice e rapida implementazione here.

7

Controllare anche concurrent-trees. Supportano entrambi gli alberi Radix e Suffix e sono progettati per ambienti ad alta concorrenza.

+2

A partire dal 2014, questa dovrebbe essere la risposta accettata. Sembra un'implementazione concorrente ben collaudata, ben collaudata e concorrente. – knub

0

qui è la mia realizzazione, godere via: GitHub - MyTrie.java

/* usage: 
    MyTrie trie = new MyTrie(); 
    trie.insert("abcde"); 
    trie.insert("abc"); 
    trie.insert("sadas"); 
    trie.insert("abc"); 
    trie.insert("wqwqd"); 
    System.out.println(trie.contains("abc")); 
    System.out.println(trie.contains("abcd")); 
    System.out.println(trie.contains("abcdefg")); 
    System.out.println(trie.contains("ab")); 
    System.out.println(trie.getWordCount("abc")); 
    System.out.println(trie.getAllDistinctWords()); 
*/ 

import java.util.*; 

public class MyTrie { 
    private class Node { 
    public int[] next = new int[26]; 
    public int wordCount; 
    public Node() { 
     for(int i=0;i<26;i++) { 
     next[i] = NULL; 
     } 
     wordCount = 0; 
    } 
    } 

    private int curr; 
    private Node[] nodes; 
    private List<String> allDistinctWords; 
    public final static int NULL = -1; 

    public MyTrie() { 
    nodes = new Node[100000]; 
    nodes[0] = new Node(); 
    curr = 1; 
    } 

    private int getIndex(char c) { 
    return (int)(c - 'a'); 
    } 

    private void depthSearchWord(int x, String currWord) { 
    for(int i=0;i<26;i++) { 
     int p = nodes[x].next[i]; 
     if(p != NULL) { 
     String word = currWord + (char)(i + 'a'); 
     if(nodes[p].wordCount > 0) { 
      allDistinctWords.add(word); 
     } 
     depthSearchWord(p, word); 
     } 
    } 
    } 

    public List<String> getAllDistinctWords() { 
    allDistinctWords = new ArrayList<String>(); 
    depthSearchWord(0, ""); 
    return allDistinctWords; 
    } 

    public int getWordCount(String str) { 
    int len = str.length(); 
    int p = 0; 
    for(int i=0;i<len;i++) { 
     int j = getIndex(str.charAt(i)); 
     if(nodes[p].next[j] == NULL) { 
     return 0; 
     } 
     p = nodes[p].next[j]; 
    } 
    return nodes[p].wordCount; 
    } 

    public boolean contains(String str) { 
    int len = str.length(); 
    int p = 0; 
    for(int i=0;i<len;i++) { 
     int j = getIndex(str.charAt(i)); 
     if(nodes[p].next[j] == NULL) { 
     return false; 
     } 
     p = nodes[p].next[j]; 
    } 
    return nodes[p].wordCount > 0; 
    } 

    public void insert(String str) { 
    int len = str.length(); 
    int p = 0; 
    for(int i=0;i<len;i++) { 
     int j = getIndex(str.charAt(i)); 
     if(nodes[p].next[j] == NULL) { 
     nodes[curr] = new Node(); 
     nodes[p].next[j] = curr; 
     curr++; 
     } 
     p = nodes[p].next[j]; 
    } 
    nodes[p].wordCount++; 
    } 
} 
+0

Questo link è morto. (Ha inviato una modifica con l'url corrente.) – Nate

4

Apache Commons Collections v4.0 ora supporta strutture trie.

Vedere org.apache.commons.collections4.trie package info per ulteriori informazioni. In particolare, verificare la classe PatriciaTrie:

Realizzazione di un trie patricia (pratico algoritmo per recuperare informazioni codificate in alfanumerica).

A PATRICIA Trie è un Trie compresso. Invece di memorizzare tutti i dati ai bordi del Trie (e con nodi interni vuoti), PATRICIA memorizza i dati in ogni nodo. Ciò consente operazioni di attraversamento, inserimento, eliminazione, predecessore, successore, prefisso, intervallo e selezione (oggetto) molto efficienti. Tutte le operazioni vengono eseguite nel peggiore dei casi nel tempo O (K), dove K è il numero di bit nell'elemento più grande nell'albero. In pratica, le operazioni richiedono effettivamente il tempo O (A (K)), dove A (K) è il numero medio di bit di tutti gli elementi nell'albero.

0

Di seguito è una implementazione di base di HashMap di un Trie. Alcune persone potrebbero trovare questo utile ...

class Trie { 

    HashMap<Character, HashMap> root; 

    public Trie() { 
     root = new HashMap<Character, HashMap>(); 
    } 

    public void addWord(String word) { 
     HashMap<Character, HashMap> node = root; 
     for (int i = 0; i < word.length(); i++) { 
      Character currentLetter = word.charAt(i); 
      if (node.containsKey(currentLetter) == false) { 
       node.put(currentLetter, new HashMap<Character, HashMap>()); 
      } 
      node = node.get(currentLetter); 
     } 
    } 

    public boolean containsPrefix(String word) { 
     HashMap<Character, HashMap> node = root; 
     for (int i = 0; i < word.length(); i++) { 
      Character currentLetter = word.charAt(i); 
      if (node.containsKey(currentLetter)) { 
       node = node.get(currentLetter); 
      } else { 
       return false; 
      } 
     } 
     return true; 
    } 
} 
0

Ho appena provato la mia implementazione TRIE concorrente, ma non sulla base di caratteri, si basa su HashCode. Ancora Possiamo usare questo avendo Mappa della Mappa per ogni codice di identificazione CHAR.
È possibile verificare questo utilizzando il codice @https://github.com/skanagavelu/TrieHashMap/blob/master/src/TrieMapPerformanceTest.java https://github.com/skanagavelu/TrieHashMap/blob/master/src/TrieMapValidationTest.java

import java.util.concurrent.atomic.AtomicReferenceArray; 

public class TrieMap { 
    public static int SIZEOFEDGE = 4; 
    public static int OSIZE = 5000; 
} 

abstract class Node { 
    public Node getLink(String key, int hash, int level){ 
     throw new UnsupportedOperationException(); 
    } 
    public Node createLink(int hash, int level, String key, String val) { 
     throw new UnsupportedOperationException(); 
    } 
    public Node removeLink(String key, int hash, int level){ 
     throw new UnsupportedOperationException(); 
    } 
} 

class Vertex extends Node { 
    String key; 
    volatile String val; 
    volatile Vertex next; 

    public Vertex(String key, String val) { 
     this.key = key; 
     this.val = val; 
    } 

    @Override 
    public boolean equals(Object obj) { 
     Vertex v = (Vertex) obj; 
     return this.key.equals(v.key); 
    } 

    @Override 
    public int hashCode() { 
     return key.hashCode(); 
    } 

    @Override 
    public String toString() { 
     return key +"@"+key.hashCode(); 
    } 
} 


class Edge extends Node { 
    volatile AtomicReferenceArray<Node> array; //This is needed to ensure array elements are volatile 

    public Edge(int size) { 
     array = new AtomicReferenceArray<Node>(8); 
    } 


    @Override 
    public Node getLink(String key, int hash, int level){ 
     int index = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, hash, level); 
     Node returnVal = array.get(index); 
     for(;;) { 
      if(returnVal == null) { 
       return null; 
      } 
      else if((returnVal instanceof Vertex)) { 
       Vertex node = (Vertex) returnVal; 
       for(;node != null; node = node.next) { 
        if(node.key.equals(key)) { 
         return node; 
        } 
       } 
       return null; 
      } else { //instanceof Edge 
       level = level + 1; 
       index = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, hash, level); 
       Edge e = (Edge) returnVal; 
       returnVal = e.array.get(index); 
      } 
     } 
    } 

    @Override 
    public Node createLink(int hash, int level, String key, String val) { //Remove size 
     for(;;) { //Repeat the work on the current node, since some other thread modified this node 
      int index = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, hash, level); 
      Node nodeAtIndex = array.get(index); 
      if (nodeAtIndex == null) { 
       Vertex newV = new Vertex(key, val); 
       boolean result = array.compareAndSet(index, null, newV); 
       if(result == Boolean.TRUE) { 
        return newV; 
       } 
       //continue; since new node is inserted by other thread, hence repeat it. 
      } 
      else if(nodeAtIndex instanceof Vertex) { 
       Vertex vrtexAtIndex = (Vertex) nodeAtIndex; 
       int newIndex = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, vrtexAtIndex.hashCode(), level+1); 
       int newIndex1 = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, hash, level+1); 
       Edge edge = new Edge(Base10ToBaseX.Base.BASE8.getLevelZeroMask()+1); 
       if(newIndex != newIndex1) { 
        Vertex newV = new Vertex(key, val); 
        edge.array.set(newIndex, vrtexAtIndex); 
        edge.array.set(newIndex1, newV); 
        boolean result = array.compareAndSet(index, vrtexAtIndex, edge); //REPLACE vertex to edge 
        if(result == Boolean.TRUE) { 
         return newV; 
        } 
        //continue; since vrtexAtIndex may be removed or changed to Edge already. 
       } else if(vrtexAtIndex.key.hashCode() == hash) {//vrtex.hash == hash) {  HERE newIndex == newIndex1 
        synchronized (vrtexAtIndex) { 
         boolean result = array.compareAndSet(index, vrtexAtIndex, vrtexAtIndex); //Double check this vertex is not removed. 
         if(result == Boolean.TRUE) { 
          Vertex prevV = vrtexAtIndex; 
          for(;vrtexAtIndex != null; vrtexAtIndex = vrtexAtIndex.next) { 
           prevV = vrtexAtIndex; // prevV is used to handle when vrtexAtIndex reached NULL 
           if(vrtexAtIndex.key.equals(key)){ 
            vrtexAtIndex.val = val; 
            return vrtexAtIndex; 
           } 
          } 
          Vertex newV = new Vertex(key, val); 
          prevV.next = newV; // Within SYNCHRONIZATION since prevV.next may be added with some other. 
          return newV; 
         } 
         //Continue; vrtexAtIndex got changed 
        } 
       } else { //HERE newIndex == newIndex1 BUT vrtex.hash != hash 
        edge.array.set(newIndex, vrtexAtIndex); 
        boolean result = array.compareAndSet(index, vrtexAtIndex, edge); //REPLACE vertex to edge 
        if(result == Boolean.TRUE) { 
         return edge.createLink(hash, (level + 1), key, val); 
        } 
       } 
      }    
      else { //instanceof Edge 
       return nodeAtIndex.createLink(hash, (level + 1), key, val); 
      } 
     } 
    } 




    @Override 
    public Node removeLink(String key, int hash, int level){ 
     for(;;) { 
      int index = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, hash, level); 
      Node returnVal = array.get(index); 
      if(returnVal == null) { 
       return null; 
      } 
      else if((returnVal instanceof Vertex)) { 
       synchronized (returnVal) { 
        Vertex node = (Vertex) returnVal; 
        if(node.next == null) { 
         if(node.key.equals(key)) { 
          boolean result = array.compareAndSet(index, node, null); 
          if(result == Boolean.TRUE) { 
           return node; 
          } 
          continue; //Vertex may be changed to Edge 
         } 
         return null; //Nothing found; This is not the same vertex we are looking for. Here hashcode is same but key is different. 
        } else { 
         if(node.key.equals(key)) { //Removing the first node in the link 
          boolean result = array.compareAndSet(index, node, node.next); 
          if(result == Boolean.TRUE) { 
           return node; 
          } 
          continue; //Vertex(node) may be changed to Edge, so try again. 
         } 
         Vertex prevV = node; // prevV is used to handle when vrtexAtIndex is found and to be removed from its previous 
         node = node.next; 
         for(;node != null; prevV = node, node = node.next) { 
          if(node.key.equals(key)) { 
           prevV.next = node.next; //Removing other than first node in the link 
           return node; 
          } 
         } 
         return null; //Nothing found in the linked list. 
        } 
       } 
      } else { //instanceof Edge 
       return returnVal.removeLink(key, hash, (level + 1)); 
      } 
     } 
    } 

} 



class Base10ToBaseX { 
    public static enum Base { 
     /** 
     * Integer is represented in 32 bit in 32 bit machine. 
     * There we can split this integer no of bits into multiples of 1,2,4,8,16 bits 
     */ 
     BASE2(1,1,32), BASE4(3,2,16), BASE8(7,3,11)/* OCTAL*/, /*BASE10(3,2),*/ 
     BASE16(15, 4, 8){  
      public String getFormattedValue(int val){ 
       switch(val) { 
       case 10: 
        return "A"; 
       case 11: 
        return "B"; 
       case 12: 
        return "C"; 
       case 13: 
        return "D"; 
       case 14: 
        return "E"; 
       case 15: 
        return "F"; 
       default: 
        return "" + val; 
       } 

      } 
     }, /*BASE32(31,5,1),*/ BASE256(255, 8, 4), /*BASE512(511,9),*/ Base65536(65535, 16, 2); 

     private int LEVEL_0_MASK; 
     private int LEVEL_1_ROTATION; 
     private int MAX_ROTATION; 

     Base(int levelZeroMask, int levelOneRotation, int maxPossibleRotation) { 
      this.LEVEL_0_MASK = levelZeroMask; 
      this.LEVEL_1_ROTATION = levelOneRotation; 
      this.MAX_ROTATION = maxPossibleRotation; 
     } 

     int getLevelZeroMask(){ 
      return LEVEL_0_MASK; 
     } 
     int getLevelOneRotation(){ 
      return LEVEL_1_ROTATION; 
     } 
     int getMaxRotation(){ 
      return MAX_ROTATION; 
     } 
     String getFormattedValue(int val){ 
      return "" + val; 
     } 
    } 

    public static int getBaseXValueOnAtLevel(Base base, int on, int level) { 
     if(level > base.getMaxRotation() || level < 1) { 
      return 0; //INVALID Input 
     } 
     int rotation = base.getLevelOneRotation(); 
     int mask = base.getLevelZeroMask(); 

     if(level > 1) { 
      rotation = (level-1) * rotation; 
      mask = mask << rotation; 
     } else { 
      rotation = 0; 
     } 
     return (on & mask) >>> rotation; 
    } 
}