¿Dónde encuentro una implementación de mapa basada en Trie estándar en Java?

Tengo un progtwig Java que almacena una gran cantidad de asignaciones de cadenas a varios objetos.

En este momento, mis opciones son confiar en hashing (a través de HashMap) o en búsquedas binarias (a través de TreeMap). Me pregunto si existe una implementación de mapas basada en trie eficiente y estándar en una biblioteca de colecciones popular y de calidad.

Ya escribí el mío, prefiero ir con algo estándar, si está disponible.

Aclaración rápida: si bien mi pregunta es general, en el proyecto actual estoy lidiando con una gran cantidad de datos que están indexados por nombre de clase totalmente calificado o firma de método. Por lo tanto, hay muchos prefijos compartidos.

    Es posible que desee ver la implementación de Trie que Limewire está contribuyendo a Google Guava.

    No existe una estructura de datos trie en las bibliotecas centrales de Java.

    Esto puede deberse a que los bashs generalmente están diseñados para almacenar cadenas de caracteres, mientras que las estructuras de datos de Java son más generales, generalmente contienen cualquier Object (definición de igualdad y operación de hash), aunque a veces se limitan a objetos Comparable (que definen un orden). No existe una abstracción común para “una secuencia de símbolos”, aunque CharSequence es adecuada para cadenas de caracteres, y supongo que podría hacer algo con Iterable para otros tipos de símbolos.

    Aquí hay otro punto a considerar: cuando intentamos implementar un trie convencional en Java, nos enfrentamos rápidamente con el hecho de que Java es compatible con Unicode. Para tener cualquier tipo de eficiencia de espacio, debe restringir las cadenas en su trie a algún subconjunto de símbolos, o abandonar el enfoque convencional de almacenar nodos secundarios en una matriz indexada por símbolo. Esta podría ser otra razón por la cual los bashs no se consideran de propósito general para su inclusión en la biblioteca central, y hay que tener cuidado con los que implementa si los implementa o usa una biblioteca de terceros.

    También mira árboles concurrentes . Admiten árboles Radix y Suffix y están diseñados para entornos de alta concurrencia.

    Apache Commons Collections v4.0 ahora es compatible con las estructuras.

    Consulte la información del paquete org.apache.commons.collections4.trie para obtener más información. En particular, consulte la clase PatriciaTrie :

    Implementación de una Trie PATRICIA (Algoritmo práctico para recuperar información codificada en alfanumérico).

    A PATRICIA Trie es una Trie comprimida. En lugar de almacenar todos los datos en los bordes del Trie (y tener nodos internos vacíos), PATRICIA almacena datos en cada nodo. Esto permite operaciones de cruce, inserción, eliminación, predecesora, sucesora, prefijo, rango y selección (Objeto) muy eficientes. Todas las operaciones se realizan en el peor de los casos en el tiempo O (K), donde K es la cantidad de bits en el elemento más grande del árbol. En la práctica, las operaciones realmente toman el tiempo O (A (K)), donde A (K) es el número promedio de bits de todos los elementos en el árbol.

    Escribí y publiqué una implementación simple y rápida aquí .

    Lo que necesitas es org.apache.commons.collections.FastTreeMap , creo.

    Colecciones de comunidades de Apache: org.apache.commons.collections4.trie.PatriciaTrie

    También puede ver este TopCoder (se requiere registro …).

    Si requirió un mapa ordenado, entonces los bashs valen la pena. Si no lo hace, entonces hashmap es mejor. Hashmap con claves de cadena se puede mejorar con respecto a la implementación estándar de Java: Array hash map

    Si no está preocupado por ingresar en la biblioteca de Scala, puede usar esta implementación eficiente del espacio que escribí sobre un trie de explosión .

    https://github.com/nbauernfeind/scala-burst-trie

    A continuación se muestra una implementación básica de HashMap de un Trie. Algunas personas pueden encontrar esto útil …

     class Trie { HashMap root; public Trie() { root = new HashMap(); } public void addWord(String word) { 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()); } node = node.get(currentLetter); } } public boolean containsPrefix(String word) { 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; } } 

    aquí está mi implementación, disfrútalo a través de: 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 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 getAllDistinctWords() { allDistinctWords = new ArrayList(); depthSearchWord(0, ""); return allDistinctWords; } public int getWordCount(String str) { int len = str.length(); int p = 0; for(int i=0;i 0; } public void insert(String str) { int len = str.length(); int p = 0; for(int i=0;i 

    Acabo de probar mi propia implementación de TRIE concurrente pero no basada en caracteres, está basada en HashCode. Todavía podemos usar esto teniendo Mapa de Mapa para cada código de CHAR.
    Puede probar esto usando el código @ 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 array; //This is needed to ensure array elements are volatile public Edge(int size) { array = new AtomicReferenceArray(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; } } 

    Puede probar la biblioteca Completamente Java, presenta una implementación PatriciaTrie . La API es pequeña y fácil de comenzar, y está disponible en el repository central de Maven .