J'ai un programme Java qui stocke beaucoup de mappages de Strings à divers objets.
Pour le moment, mes options sont de miser sur le hachage (via HashMap) ou sur les recherches binaires (via TreeMap). Je me demande s’il existe une implémentation efficace et standard de cartes basée sur la trie dans une bibliothèque de collections populaire et de qualité.
J'ai écrit le mien dans le passé, mais je préférerais utiliser quelque chose de standard, si disponible.
Précision rapide: bien que ma question soit générale, dans le projet actuel, je traite beaucoup de données indexées par nom de classe pleinement qualifié ou signature de méthode. Ainsi, il existe de nombreux préfixes partagés.
Vous voudrez peut-être examiner l'implémentation Trie que Limewire contribue à Google Guava.
Il n’existe aucune structure de données dans les bibliothèques Java principales.
Cela est peut-être dû au fait que les tentatives sont généralement conçues pour stocker des chaînes de caractères, alors que les structures de données Java sont plus générales et contiennent généralement une variable Object
(définition de l'égalité et une opération de hachage), bien qu'elles soient parfois limitées à des objets Comparable
(définition d'un ordre). Il n’existe pas d’abstraction commune pour "une séquence de symboles", bien que CharSequence
convienne aux chaînes de caractères et je suppose que vous pouvez utiliser quelque chose avec Iterable
pour d’autres types de symboles.
Voici un autre point à prendre en compte: lorsque vous essayez d'implémenter un test classique en Java, vous êtes rapidement confronté au fait que Java prend en charge le format Unicode. Pour obtenir un gain d’espace, vous devez limiter les chaînes de votre test à un sous-ensemble de symboles ou abandonner l’approche classique consistant à stocker des nœuds enfants dans un tableau indexé par un symbole. Cela peut être une autre raison pour laquelle les tentatives ne sont pas considérées comme suffisamment générales pour être incluses dans la bibliothèque principale, et une chose à surveiller si vous implémentez votre propre bibliothèque ou si vous utilisez une bibliothèque tierce.
Consultez également concurrents-trees . Ils prennent en charge les arbres Radix et Suffix et sont conçus pour les environnements à haute concurrence.
Apache Commons Collections v4.0 prend désormais en charge trois structures.
Voir le org.Apache.commons.collections4.trie
informations sur le paquet pour plus d’informations. En particulier, vérifiez la PatriciaTrie
class:
Implémentation d'un PATRICIA Trie (algorithme pratique pour récupérer des informations codées en alphanumérique).
PATRICIA Trie est une Trie compressée. Au lieu de stocker toutes les données sur les bords du Trie (et d'avoir des nœuds internes vides), PATRICIA stocke les données dans chaque nœud. Cela permet des opérations très efficaces de parcours, insertion, suppression, prédécesseur, successeur, préfixe, plage et sélection (Object). Toutes les opérations sont exécutées au pire en O(K), où K est le nombre de bits du plus grand élément de l’arbre. En pratique, les opérations prennent en réalité O(A(K)), où A(K) est le nombre moyen de bits de tous les éléments de l'arborescence.
J'ai écrit et publié une implémentation simple et rapide ici .
Collections Apache commons: org.Apache.commons.collections4.trie.Patricia Trie
Ce dont vous avez besoin, c'est org.Apache.commons.collections.FastTreeMap
, je pense.
Vous pouvez essayer la bibliothèque Completely Java, elle comporte une implémentation PatriciaTrie . L'API est petite et facile à utiliser. Elle est disponible dans le répertoire Maven .
Si vous avez besoin d'une carte triée, alors les essais en valent la peine ... Si ce n'est pas le cas, hashmap est préférable .. Vous pouvez améliorer le hashmap avec des clés de chaîne par rapport à l'implémentation Java standard: Array hash map
Vous trouverez ci-dessous une implémentation HashMap de base d’un Trie. Certaines personnes pourraient trouver cela utile ...
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;
}
}
Vous pouvez aussi regarder ce TopCoder one (inscription requise ...).
Je viens d’essayer ma propre implémentation TRIE simultanée, mais non basée sur des caractères, elle est basée sur HashCode. Nous pouvons toujours utiliser cette carte ayant la carte pour chaque code de code CHAR.
Vous pouvez le tester en utilisant le code @ 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;
}
}
voici ma mise en oeuvre, profitez-en via: 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++;
}
}
Si vous n’êtes pas inquiet à propos de l’utilisation de la bibliothèque Scala, vous pouvez utiliser cette implémentation peu encombrante que j’ai écrite à propos de burst trie .