Де я можу знайти стандартну реалізацію карт на основі Trie в Java? [зачинено]


76

У мене є програма Java, яка зберігає багато зіставлення з рядків на різні об’єкти.

Зараз я маю можливість покластися на хешування (через HashMap) або на двійкові пошуки (через TreeMap). Цікаво, чи існує ефективна та стандартна реалізація карт на основі trie у популярній та якісній бібліотеці колекцій?

Раніше я писав свій власний, але волію скористатися чимось стандартним, якщо такий є.

Швидке роз'яснення: Хоча моє запитання загальне, у поточному проекті я маю справу з великою кількістю даних, які індексуються повнокваліфікованою назвою класу або підписом методу. Таким чином, існує багато спільних префіксів.


струни відомі заздалегідь? Чи потрібен доступ до них лише за допомогою рядка?
sfossen

Відповіді:


32

Можливо, ви захочете поглянути на реалізацію Trie, яку Limewire вносить у Google Guava.


8
Схоже, Google-Collections замінено Guava code.google.com/p/guava-libraries , і, на жаль, я ніде не бачу там класу Trie. Здається, у Patricia Trie зараз є своя сторінка проекту: code.google.com/p/patricia-trie
Dan J

1
Посилання Limewire / Google зараз теж безлад. Хоча мені вдалося знайти code.google.com/archive/p/google-collections/issues/5 з фактичними файлами, зауважте, що колекції Apache Commons пропонуються з кількома спробами (включаючи патріцію). Саме цього я б порекомендував зараз.
Jason C

Також реалізація Apache Commons, схоже, з того самого місця, що і внесок Limewire, оскільки зведені коментарі в документах Commons для PatriciaTrie ідентичні зведеним коментарям у реалізації, внесеній Limewire.
Jason C

10

У основних бібліотеках Java немає структури даних trie.

Це може бути тому, що спроби, як правило, призначені для зберігання рядків символів, тоді як структури даних Java є більш загальними, зазвичай містять будь-які Object(визначення рівності та хеш-операції), хоча іноді вони обмежуються Comparableоб'єктами (визначення порядку). Не існує загальної абстракції для "послідовності символів", хоча CharSequenceвона підходить для рядків символів, і, я гадаю, ви можете зробити щось Iterableдля інших типів символів.

Ось ще один момент, на який слід звернути увагу: намагаючись реалізувати звичайну версію Java, ви швидко стикаєтесь з тим, що Java підтримує Unicode. Щоб мати будь-яку космічну ефективність, вам потрібно обмежити рядки у вашому тріє до якогось підмножини символів або відмовитись від традиційного підходу до збереження дочірніх вузлів у масиві, індексованому символом. Це може бути ще однією причиною, чому спроби не вважаються достатньо загальними для включення в основну бібліотеку, і щось, на що слід звернути увагу, якщо ви реалізуєте власну або використовуєте сторонні бібліотеки.


Ця відповідь передбачає, що я хочу реалізувати трие для рядків. Трие - це загальна структура даних, здатна утримувати довільні послідовності та забезпечувати швидкі пошуки префіксів.
Пол Дрейпер,

1
@PaulDraper Ця відповідь не передбачає нічого про те, що ви хочете, оскільки ви з’явилися через роки після того, як було задано запитання. І оскільки питання стосується конкретно рядків символів, саме на це спрямовано цю відповідь. Хоча я витрачаю багато часу, вказуючи на те, що Java-трійку потрібно було б узагальнити до будь-якого типу Comparable.
erickson

7

Також перевірте паралельні дерева . Вони підтримують дерева Radix і Suffix і розроблені для середовищ з високою одночасністю.


3
Станом на 2014 рік, це має бути прийнятою відповіддю. Схоже на доглянуте, добре перевірене, одночасне впровадження спроб.
knub

5

Apache Commons Collections v4.0 тепер підтримує структури trie.

Для отримання додаткової інформації дивіться інформацію про org.apache.commons.collections4.trieпакет . Зокрема, перевірте PatriciaTrieклас:

Впровадження Trie PATRICIA (Практичний алгоритм отримання інформації, кодованої буквено-цифровою).

PATRICIA Trie - це стиснутий Trie. Замість того, щоб зберігати всі дані по краях Trie (і мати порожні внутрішні вузли), PATRICIA зберігає дані у кожному вузлі. Це дозволяє виконувати дуже ефективні операції обходу, вставки, видалення, попередника, наступника, префікса, діапазону та вибору (Об'єкт). Усі операції виконуються в найгірший час за O (K), де K - кількість бітів у найбільшому елементі дерева. На практиці операції фактично займають час O (A (K)), де A (K) - середня кількість бітів усіх елементів у дереві.


3

Я написав і опублікував просте і швидке впровадження тут .


Я хотів би це сподобатися, але кожен з ваших вузлів вимагає 1024 байта і представляє лише один символ. Також вставка зараз займає час O (n ^ 2) через змінену Java семантику підрядка (). Це реалізація насправді не дуже практична.
Стефан Рейх,

@Stefan Reich, Цей простір масиву призначений лише для внутрішніх вузлів, що зникає мало, враховуючи те, наскільки швидко дерева Trie роздуваються.
Мелінда Грін,

Дякую за вашу відповідь, але я не переконаний. Спроби можуть не завжди швидко розгалужуватися, насправді вони не будуть мати реальних даних. Ваші масиви також повільно шукають вміст. Нам слід справді використовувати Патрісію Трієс, щоб мати речі компактними та ефективними. Я зробив власну реалізацію, яку, можливо, незабаром розміщу тут. Ніяких важких почуттів, просто намагаюся оптимізувати :) Багато привітань
Стефан Рейх

Мої спроби можуть швидко розійштися, оскільки надмірності віднімаються і зберігаються в елементі "префікс". Тут є місце для безлічі різних реалізацій на основі того, що ви намагаєтеся оптимізувати. У моєму випадку я прагну до простого, але практичного.
Мелінда Грін

1
Ах, я неправильно зрозумів цю частину коду. Існує стільки "Об'єкта" та кастингу, що я його не бачив. Тож це Патрісія Трі. Моє ліжко.
Штефан Рейх



1

Нижче наведено базову реалізацію Trie HashMap. Деякі люди можуть знайти це корисним ...

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

Ви також можете переглянути цей TopCoder (потрібна реєстрація ...).


я зареєструвався, але цей компонент є невідступним обрядом зараз.
Діпак

0

Якщо вам потрібна відсортована карта, тоді спроби варті. Якщо ви цього не зробите, тоді хеш-карта краще. Геш-карту з рядковими ключами можна покращити порівняно зі стандартною реалізацією Java: хеш-карта масиву



0

ось моя реалізація, насолоджуйтесь цим через: 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

Я щойно спробував власну реалізацію одночасного TRIE, але не на основі символів, а на основі HashCode. Проте ми можемо використовувати цю мапу Map Map для кожного коду CHAR.
Ви можете перевірити це за допомогою коду @ 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;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.