2009-03-08 33 views
70

Tôi có một chương trình Java lưu trữ rất nhiều ánh xạ từ các chuỗi đến các đối tượng khác nhau.Tôi làm cách nào để tìm một bản đồ thực hiện dựa trên bản đồ Trie chuẩn trong Java?

Hiện tại, các tùy chọn của tôi là dựa vào băm (thông qua HashMap) hoặc trên các tìm kiếm nhị phân (thông qua TreeMap). Tôi tự hỏi nếu có một bản đồ hiệu quả và chuẩn trên bản đồ thực hiện trong một thư viện sưu tập phổ biến và chất lượng?

Tôi đã viết của riêng mình trong quá khứ, nhưng tôi muốn đi với một cái gì đó tiêu chuẩn, nếu có.

Làm rõ nhanh: Mặc dù câu hỏi của tôi là chung, trong dự án hiện tại tôi đang xử lý nhiều dữ liệu được lập chỉ mục bằng tên lớp hoặc chữ ký phương thức đủ điều kiện. Vì vậy, có rất nhiều tiền tố được chia sẻ.

+0

là các chuỗi đã biết trước? Họ có cần truy cập chỉ bằng chuỗi không? – sfossen

Trả lời

29

Bạn có thể muốn xem Trie implementation that Limewire is contributing với Google ổi.

+8

Có vẻ như Google-Bộ sưu tập đã được superceded bởi Guava https://code.google.com/p/guava-libraries/, và tiếc là tôi không thể nhìn thấy lớp Trie ở đó ở bất cứ đâu. Patricia Trie dường như có trang dự án riêng của mình ngay bây giờ: https://code.google.com/p/patricia-trie/ –

+1

Các liên kết Limewire/Google cũng có một chút lộn xộn. Trong khi tôi đã tìm thấy https://code.google.com/archive/p/google-collections/issues/5 với các tệp thực tế, hãy lưu ý rằng [Bộ sưu tập của Apache Commons] (https://commons.apache.org/ bộ sưu tập thích hợp/commons /) đi kèm với [một số lần thử] (https://commons.apache.org/proper/commons-collections/javadocs/api-release/index.html) (bao gồm cả một bộ ba patricia). Đó là điều tôi muốn giới thiệu ngay bây giờ. –

+0

Ngoài ra việc thực hiện Apache Commons có vẻ là từ cùng một vị trí như đóng góp của Limewire, vì các nhận xét tóm tắt trong tài liệu của Commons cho PatriciaTrie giống hệt với các bình luận tóm tắt trong việc thực hiện đóng góp của Limewire. –

1

Điều bạn cần là org.apache.commons.collections.FastTreeMap, tôi nghĩ vậy.

+0

Điều này dường như không phải là triển khai trie. –

0

Bạn cũng có thể xem số this TopCoder (yêu cầu đăng ký ...).

+0

tôi đã đăng ký nhưng thành phần đó là nghi thức không thể tránh khỏi bây giờ. – Deepak

9

Không có cấu trúc dữ liệu trie trong các thư viện Java lõi. Điều này có thể là do cố gắng thường được thiết kế để lưu trữ chuỗi ký tự, trong khi cấu trúc dữ liệu Java tổng quát hơn, thường giữ bất kỳ Object (xác định bình đẳng và hoạt động băm), mặc dù đôi khi chúng được giới hạn ở các đối tượng Comparable (xác định đơn đặt hàng)). Không có trừu tượng chung cho "một chuỗi các ký hiệu", mặc dù CharSequence phù hợp với các chuỗi ký tự và tôi cho rằng bạn có thể làm điều gì đó với Iterable cho các loại biểu tượng khác.

Đây là một điểm cần xem xét: khi cố gắng triển khai một trie thông thường trong Java, bạn nhanh chóng đối mặt với thực tế là Java hỗ trợ Unicode. Để có bất kỳ loại hiệu quả không gian nào, bạn phải hạn chế các chuỗi trong trie của bạn thành một số tập con biểu tượng, hoặc từ bỏ cách tiếp cận thông thường để lưu trữ các nút con trong một mảng được lập chỉ mục bằng ký hiệu. Đây có thể là một lý do khác khiến các cố gắng không được coi là đủ mục đích chung để đưa vào thư viện cốt lõi và một cái gì đó cần lưu ý nếu bạn triển khai hoặc sử dụng thư viện của bên thứ ba.

+0

Câu trả lời này giả sử tôi muốn thực hiện một trie cho chuỗi. Một trie là một cấu trúc dữ liệu * chung *, có khả năng giữ các chuỗi tùy ý và cung cấp tra cứu tiền tố nhanh. –

+1

@PaulDraper Câu trả lời này không giả định bất cứ điều gì về những gì bạn muốn, vì bạn đã xuất hiện nhiều năm sau khi câu hỏi được hỏi. Và vì câu hỏi là đặc biệt về chuỗi ký tự, đó là trọng tâm của câu trả lời này. Mặc dù tôi dành rất nhiều thời gian để chỉ ra rằng một bộ ba Java cần phải được tổng quát hóa thành bất kỳ loại 'Comparable' nào. – erickson

0

Nếu bạn yêu cầu bản đồ đã sắp xếp, thì các lần thử là đáng giá. Nếu bạn không băm thì tốt hơn. Hashmap với các phím chuỗi có thể được cải tiến so với việc thực hiện Java chuẩn: Array hash map

3

Tôi đã viết và xuất bản triển khai đơn giản và nhanh chóng here.

7

Ngoài ra, hãy kiểm tra concurrent-trees. Chúng hỗ trợ cả cây Radix và Suffix và được thiết kế cho các môi trường tương tranh cao.

+2

Tính đến năm 2014, đây phải là câu trả lời được chấp nhận. Có vẻ như đã được duy trì tốt, được thử nghiệm tốt, đồng thời thực hiện các lần thử. – knub

0

ở đây là thực hiện của tôi, thưởng thức nó qua: 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

Liên kết này đã chết. (Đã gửi bản chỉnh sửa với url hiện tại.) – Nate

4

Apache Commons Collections v4.0 bây giờ hỗ trợ cấu trúc Trie.

Xem org.apache.commons.collections4.trie package info để biết thêm thông tin. Cụ thể, hãy kiểm tra lớp PatriciaTrie:

Thực hiện một PATRICIA Trie (Thuật toán thực tế để lấy thông tin được mã hóa bằng chữ và số).

Một PATRICIA Trie là một Trie được nén. Thay vì lưu trữ tất cả dữ liệu ở các cạnh của Trie (và có các nút bên trong trống), PATRICIA lưu trữ dữ liệu trong mỗi nút. Điều này cho phép các hoạt động traversal, chèn, xóa, tiền nhiệm, kế thừa, tiền tố, phạm vi và chọn (Object) rất hiệu quả. Tất cả các hoạt động được thực hiện ở mức thấp nhất trong thời gian O (K), trong đó K là số bit trong mục lớn nhất trong cây. Trong thực tế, các phép toán thực sự mất thời gian O (A (K)), trong đó A (K) là số bit trung bình của tất cả các mục trong cây.

0

Dưới đây là triển khai HashMap cơ bản của Trie. Một số người có thể thấy điều này hữu ích ...

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

Tôi vừa thử triển khai TRIE đồng thời của riêng mình nhưng không dựa trên các ký tự, dựa trên HashCode. Tuy nhiên Chúng tôi có thể sử dụng bản đồ này có Bản đồ của bản đồ cho mỗi mã số CHAR.
Bạn có thể kiểm tra điều này sử dụng mã @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; 
    } 
} 
Các vấn đề liên quan