Stringsからさまざまなオブジェクトへの多くのマッピングを保存するJavaプログラムがあります。
現在、私の選択肢は、ハッシュ(HashMap経由)またはバイナリ検索(TreeMap経由)に依存しています。人気のある高品質のコレクションライブラリに効率的で標準的なトライベースのマップ実装があるかどうか疑問に思っていますか?
過去に自分で書いたことがありますが、可能であれば標準的なものを使いたいと思います。
簡単な説明:私の質問は一般的なものですが、現在のプロジェクトでは、完全修飾クラス名またはメソッドシグネチャでインデックス付けされた多くのデータを扱っています。したがって、多くの共有プレフィックスがあります。
Limewireが貢献しているTrieの実装 をGoogle Guavaに見てください。
コアJavaライブラリにトライデータ構造はありません。
これは、通常、文字列を格納するように設計されているのに対し、Javaデータ構造がより一般的で、通常Object
(等式とハッシュ演算を定義)を保持しているが、 Comparable
オブジェクト(順序の定義)に制限されることがあります。CharSequence
は文字列に適していますが、「シンボルのシーケンス」には一般的な抽象化はありません。 Iterable
他のタイプのシンボル。
ここで考慮すべきもう1つの点があります。Javaで従来のトライを実装しようとすると、JavaはUnicodeをサポートしているという事実にすぐに直面します。シンボルのサブセットへのトライの文字列、またはシンボルでインデックス付けされた配列に子ノードを格納する従来のアプローチを放棄するこれは、コアライブラリに含めるのに十分な汎用とは見なされない別の理由かもしれません独自のライブラリを実装するか、サードパーティのライブラリを使用するかどうかに注意してください。
concurrent-trees も確認してください。 RadixおよびSuffixツリーの両方をサポートし、同時実行性の高い環境向けに設計されています。
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)はツリー内のすべてのアイテムの平均ビット数です。
シンプルで高速な実装を書いて公開しました here 。
Apacheの共通コレクション: org.Apache.commons.collections4.trie.PatriciaTrie
必要なのはorg.Apache.commons.collections.FastTreeMap
、 おもう。
完全 Javaライブラリ、 PatriciaTrie 実装を特徴としています。APIは小さくて使いやすく、利用可能です Maven中央リポジトリ で。
ソートされたマップが必要な場合、試行する価値があります。そうでない場合は、ハッシュマップの方が優れています。文字列キーを使用したハッシュマップは、標準のJava実装: 配列ハッシュマップ
以下は、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;
}
}
this TopCoder one(登録が必要です...)もご覧ください。
独自のコンカレント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;
}
}
ここに私の実装があります: 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++;
}
}
Scalaライブラリを引っ張ることを心配していないなら、私が burst trie について書いたこのスペース効率の良い実装を使用できます。