web-dev-qa-db-ja.com

Javaでの標準のTrieベースのマップ実装はどこにありますか?

Stringsからさまざまなオブジェクトへの多くのマッピングを保存するJavaプログラムがあります。

現在、私の選択肢は、ハッシュ(HashMap経由)またはバイナリ検索(TreeMap経由)に依存しています。人気のある高品質のコレクションライブラリに効率的で標準的なトライベースのマップ実装があるかどうか疑問に思っていますか?

過去に自分で書いたことがありますが、可能であれば標準的なものを使いたいと思います。

簡単な説明:私の質問は一般的なものですが、現在のプロジェクトでは、完全修飾クラス名またはメソッドシグネチャでインデックス付けされた多くのデータを扱っています。したがって、多くの共有プレフィックスがあります。

71
Uri

Limewireが貢献しているTrieの実装 をGoogle Guavaに見てください。

30

コアJavaライブラリにトライデータ構造はありません。

これは、通常、文字列を格納するように設計されているのに対し、Javaデータ構造がより一般的で、通常Object(等式とハッシュ演算を定義)を保持しているが、 Comparableオブジェクト(順序の定義)に制限されることがあります。CharSequenceは文字列に適していますが、「シンボルのシーケンス」には一般的な抽象化はありません。 Iterable他のタイプのシンボル。

ここで考慮すべきもう1つの点があります。Javaで従来のトライを実装しようとすると、JavaはUnicodeをサポートしているという事実にすぐに直面します。シンボルのサブセットへのトライの文字列、またはシンボルでインデックス付けされた配列に子ノードを格納する従来のアプローチを放棄するこれは、コアライブラリに含めるのに十分な汎用とは見なされない別の理由かもしれません独自のライブラリを実装するか、サードパーティのライブラリを使用するかどうかに注意してください。

9
erickson

concurrent-trees も確認してください。 RadixおよびSuffixツリーの両方をサポートし、同時実行性の高い環境向けに設計されています。

7
Alex Beardsley

Apache Commons Collections v4.0はトライ構造をサポートするようになりました。

org.Apache.commons.collections4.trieパッケージ情報 詳細については。特に、 PatriciaTrie クラスを確認してください:

PATRICIA Trie(英数字でコード化された情報を取得するための実用的なアルゴリズム)の実装。

パトリシアトライは圧縮されたトライです。 Trieの端にすべてのデータを保存する(および空の内部ノードを持つ)代わりに、PATRICIAはすべてのノードにデータを保存します。これにより、非常に効率的なトラバース、挿入、削除、先行操作、後続操作、プレフィックス、範囲、およびselect(Object)操作が可能になります。すべての操作は、最悪の場合O(K)時間で実行されます。ここで、Kはツリー内の最大項目のビット数です。実際には、操作は実際にO(A(K)) time、ここでA(K)はツリー内のすべてのアイテムの平均ビット数です。

4
Duncan Jones

シンプルで高速な実装を書いて公開しました here

3
Melinda Green

Apacheの共通コレクション: org.Apache.commons.collections4.trie.PatriciaTrie

2
Ilya Kharlamov

必要なのはorg.Apache.commons.collections.FastTreeMap 、 おもう。

1
andrii

完全 Javaライブラリ、 PatriciaTrie 実装を特徴としています。APIは小さくて使いやすく、利用可能です Maven中央リポジトリ で。

ソートされたマップが必要な場合、試行する価値があります。そうでない場合は、ハッシュマップの方が優れています。文字列キーを使用したハッシュマップは、標準のJava実装: 配列ハッシュマップ

0
U Mad

以下は、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
martynas

this TopCoder one(登録が必要です...)もご覧ください。

0
TofuBeer

独自のコンカレントTRIE実装を試しましたが、文字に基づくのではなく、HashCodeに基づいています。それでも、CHARの各hascodeに対してMap of Mapを持つこれを使用できます。
コード@ https://github.com/skanagavelu/TrieHashMap/blob/master/src/TrieMapPerformanceTest.Javahttps:// 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;
    }
}
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
coderz

Scalaライブラリを引っ張ることを心配していないなら、私が burst trie について書いたこのスペース効率の良い実装を使用できます。

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

0
Nate