2009-03-08 20 views
70

Mam program Java, który przechowuje wiele mapowania z ciągów do różnych obiektów.Gdzie znajdę standardową implementację map opartą na Trie w Javie?

Obecnie moje opcje polegają albo na haszowaniu (poprzez HashMap), albo na wyszukiwaniu binarnym (poprzez TreeMap). Zastanawiam się, czy istnieje skuteczna i standardowa implementacja mapy opartej na trie w popularnej i wysokiej jakości bibliotece zbiorów?

W przeszłości pisałem własne, ale wolałbym iść z czymś standardowym, jeśli to możliwe.

Szybkie wyjaśnienie: Chociaż moje pytanie jest ogólne, w bieżącym projekcie mam do czynienia z dużą ilością danych, które są indeksowane przez w pełni kwalifikowany podpis nazwy klasy lub metody. Tak więc istnieje wiele wspólnych przedrostków.

+0

są znane z góry łańcuchy? Czy muszą być dostępne tylko przez ciąg? – sfossen

Odpowiedz

29

Możesz chcieć spojrzeć na Trie implementation that Limewire is contributing na Google Guava.

+8

Wygląda na to, że Google-Collections zostało zastąpione przez Guava https://code.google.com/p/guava-libraries/ i niestety nie widzę tam żadnej klasy Trie. Wydaje się, że Patricia Trie ma teraz swoją własną stronę projektu: https://code.google.com/p/patricia-trie/ –

+1

Łącza do Limewire/Google również są teraz trochę bałaganem. Chociaż udało mi się znaleźć https://code.google.com/archive/p/google-collections/issues/5 z rzeczywistymi plikami, zwróć uwagę, że [Kolekcje Apache Commons] (https://commons.apache.org/ proper/commons-collections /) zawiera wiele prób (https://commons.apache.org/proper/commons-collections/javadocs/api-release/index.html) (w tym patricia trie). To właśnie polecam teraz. –

+0

Również implementacja Apache Commons wydaje się pochodzić z tego samego miejsca, co wkład Limewire, ponieważ komentarze podsumowujące w dokumentach Commons na temat PatriciaTrie są identyczne z komentarzami podsumowującymi w implementacji wnoszonej przez Limewire. –

1

To, czego potrzebujesz, to org.apache.commons.collections.FastTreeMap, jak sądzę.

+0

To nie wydaje się być implementacja trie. –

0

Możesz również spojrzeć na numer this TopCoder (wymagana rejestracja ...).

+0

Zrobiłem rejestrację, ale ten składnik jest teraz obrzędem nie do przyjęcia. – Deepak

9

W podstawowych bibliotekach Java nie ma struktury danych Trieta.

Może to być spowodowane tym, że próby są zwykle zaprojektowane do przechowywania ciągów znaków, podczas gdy struktury danych Java są bardziej ogólne, zwykle posiadają dowolne Object (definiujące równość i operację mieszania), chociaż czasami są ograniczone do obiektów Comparable (definiujących zamówienie). Nie ma wspólnej abstrakcji dla "sekwencji symboli", chociaż CharSequence jest odpowiednia dla ciągów znaków i przypuszczam, że można zrobić coś z Iterable dla innych typów symboli.

Oto kolejny punkt do rozważenia: podczas próby zaimplementowania tradycyjnego trie w Javie, szybko stajesz wobec faktu, że Java obsługuje Unicode. Aby uzyskać jakąkolwiek efektywność przestrzenną, musisz ograniczyć łańcuchy w twoim trie do jakiegoś podzbioru symboli lub zrezygnować z konwencjonalnego podejścia do przechowywania węzłów potomnych w tablicy indeksowanej według symbolu. Może to być kolejny powód, dla którego próby nie są uważane za wystarczająco uniwersalne, aby można je było umieścić w głównej bibliotece, i na co zwrócić uwagę, jeśli wdrożysz własną bibliotekę lub skorzystasz z biblioteki innej firmy.

+0

Ta odpowiedź zakłada, że ​​chcę zaimplementować trie dla ciągów. Trie to * ogólna * struktura danych, zdolna do utrzymywania dowolnych sekwencji i szybkiego wyszukiwania prefiksów. –

+1

@PaulDraper Ta odpowiedź nie zakłada niczego, czego chcesz, ponieważ pojawiłeś się kilka lat po zadaniu pytania. A ponieważ pytanie dotyczy konkretnie ciągów znaków, to właśnie w tej odpowiedzi. Chociaż spędzam dużo czasu, wskazując, że Java Trie musiałaby zostać uogólniona na dowolny rodzaj "porównywalnych". – erickson

0

Jeśli potrzebna jest posortowana mapa, próby są warte zachodu. Jeśli nie, to hashmap jest lepsza. HashMap z kluczami tekstowymi można poprawić na standardowej implementacji Javy: Array hash map

3

pisałem i opublikowane prostą i szybką realizację here.

7

Sprawdź również concurrent-trees. Obsługują one drzewa Radix i Suffix i są zaprojektowane do środowisk o wysokiej współbieżności.

+2

Od 2014 r. Powinna to być zaakceptowana odpowiedź. Wygląda na dobrze utrzymaną, dobrze przetestowaną, współbieżną implementację prób. – knub

0

tutaj jest mój wdrażania, cieszyć się nim poprzez: 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

Ten link nie działa. (Przesłano zmianę z bieżącym adresem URL.) – Nate

4

Apache Commons Collections v4.0 obsługuje struktur TRIE.

Aby uzyskać więcej informacji, zobacz numer org.apache.commons.collections4.trie package info. W szczególności należy sprawdzić klasę PatriciaTrie:

realizacja PATRICIA Trie (Praktyczny algorytm do pobierania informacji zakodowanej w alfanumeryczny).

A PATRICIA Trie to skompresowany Trie. Zamiast przechowywania wszystkich danych na krawędziach Trie (i posiadających puste wewnętrzne węzły), PATRICIA przechowuje dane w każdym węźle. Pozwala to na bardzo efektywne operacje przejścia, wstawiania, usuwania, poprzedzania, następstwa, prefiksu, zakresu i wyboru (obiektu). Wszystkie operacje wykonywane są w najgorszym czasie w czasie O (K), gdzie K jest liczbą bitów największego elementu w drzewie. W praktyce operacja rzeczywiście zajmuje czas O (A (K)), gdzie A (K) jest średnią liczbą bitów wszystkich elementów w drzewie.

0

Poniżej znajduje się podstawowa implementacja HashMap dla Trie. Niektórzy ludzie mogą znaleźć to przydatne ...

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

Właśnie próbował własną Równoczesne wdrożenie trie, ale nie na podstawie znaków, jest ona oparta na hashcode. Nadal możemy użyć tego mając Mapę Mapy dla każdego hasaru CHAR.
Można to sprawdzić za pomocą kodu @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; 
    } 
} 
Powiązane problemy