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


76

Tengo un programa Java que almacena muchas asignaciones de cadenas a varios objetos.

En este momento, mis opciones son confiar en hash (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.

He escrito el mío en el pasado, pero prefiero ir con algo estándar, si está disponible.

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


¿Se conocen las cadenas de antemano? ¿Es necesario acceder a ellos solo mediante una cadena?
sfossen

Respuestas:


32

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


8
Parece que las colecciones de Google han sido reemplazadas por Guava code.google.com/p/guava-libraries , y desafortunadamente no puedo ver una clase de Trie en ninguna parte. Patricia Trie parece tener su propia página de proyecto ahora: code.google.com/p/patricia-trie
Dan J

1
Los enlaces de Limewire / Google ahora también están un poco desordenados. Si bien logré encontrar code.google.com/archive/p/google-collections/issues/5 con los archivos reales, tenga en cuenta que Apache Commons Collections viene con una serie de intentos (incluido un patricia trie). Ese es el que recomendaría ahora mismo.
Jason C

Además, la implementación de Apache Commons parece provenir del mismo lugar que la contribución de Limewire, ya que los comentarios de resumen en los documentos de Commons para PatriciaTrie son idénticos a los comentarios de resumen en la implementación contribuida por Limewire.
Jason C

10

No hay una estructura de datos trie en las bibliotecas principales de Java.

Esto puede deberse a que los intentos generalmente están diseñados para almacenar cadenas de caracteres, mientras que las estructuras de datos de Java son más generales, por lo general contienen cualquiera Object(que define la igualdad y una operación hash), aunque a veces se limitan a Comparableobjetos (que definen un orden). No existe una abstracción común para "una secuencia de símbolos", aunque CharSequencees adecuada para cadenas de caracteres, y supongo que podría hacer algo con Iterableotros tipos de símbolos.

Aquí hay otro punto a considerar: al intentar implementar un trie convencional en Java, rápidamente se enfrenta al hecho de que Java es compatible con Unicode. Para tener algún tipo de eficiencia espacial, debe restringir las cadenas de 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 que los intentos no se consideran lo suficientemente de propósito general para su inclusión en la biblioteca principal, y algo a tener en cuenta si implementa la suya propia o utiliza una biblioteca de terceros.


Esta respuesta asume que quiero implementar un trie para cadenas. Un trie es una estructura de datos general , capaz de contener secuencias arbitrarias y proporcionar búsquedas rápidas de prefijos.
Paul Draper

1
@PaulDraper Esta respuesta no asume nada sobre lo que quieres, ya que apareciste años después de que se hizo la pregunta. Y dado que la pregunta es específicamente sobre cadenas de caracteres, ese es el enfoque de esta respuesta. Aunque dedico mucho tiempo a señalar que un trie de Java debería generalizarse a cualquier tipo de Comparable.
erickson

7

También echa un vistazo a los árboles concurrentes . Admiten árboles Radix y Suffix y están diseñados para entornos de alta concurrencia.


3
A partir de 2014, esta debería ser la respuesta aceptada. Parece una implementación simultánea de intentos bien mantenida y probada.
knub

5

Apache Commons Collections v4.0 ahora admite estructuras trie.

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

Implementación de un PATRICIA Trie (Algoritmo Práctico para Recuperar Información Codificada en Alfanumérico).

Una 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 recorrido, inserción, eliminación, predecesor, sucesor, 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 el número de bits en el elemento más grande del árbol. En la práctica, las operaciones en realidad toman O (A (K)) tiempo, donde A (K) es el número promedio de bits de todos los elementos del árbol.


3

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


Me gustaría que me gustara esto, pero cada uno de sus nodos requiere 1024 bytes y representa solo un carácter. Además, la inserción ahora toma O (n ^ 2) tiempo debido a la semántica cambiada de Java de subcadena (). Esta implementación realmente no es muy práctica.
Stefan Reich

@Stefan Reich, Ese espacio de matriz es solo para nodos internos, que es extremadamente pequeño dada la rapidez con que los árboles Trie se abren.
Melinda Green

Gracias por tu respuesta, pero no estoy convencido. Es posible que los intentos no siempre se ramifiquen rápidamente, de hecho, probablemente no lo harán con datos reales. Sus matrices también son lentas para buscar contenido. Realmente deberíamos usar Patricia Tries para tener cosas compactas y eficientes. He hecho mi propia implementación que probablemente publicaré aquí en breve. Sin resentimientos, solo tratando de optimizar :) Muchos saludos
Stefan Reich

Mis intentos solo pueden desplegarse rápidamente ya que las redundancias se descartan y almacenan en el miembro "prefijo". Hay espacio para muchas implementaciones diferentes según lo que intente optimizar. En mi caso, apunto a lo simple pero práctico.
Melinda Green

1
Ah, entendí mal esa parte del código. Hay tanto "Objeto" y casting que no lo vi. Entonces es Patricia Trie. Culpa mía.
Stefan Reich


1

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


Esta no parece ser una implementación trie.
Duncan Jones

1

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

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

Si necesita un mapa ordenado, vale la pena intentarlo. Si no lo hace, entonces hashmap es mejor. El mapa de hash con claves de cadena se puede mejorar con respecto a la implementación estándar de Java: mapa de hash de matriz



0

aquí está mi implementación, disfrútala 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<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

Acabo de probar mi propia implementación de TRIE concurrente pero no basada en caracteres, está basada en HashCode. Aún así, podemos usar este mapa de mapa para cada código de has 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<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;
    }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.