web-dev-qa-db-ja.com

Java TreeMapで要素の位置を見つける

私は文字列のツリーマップTreeMap<String, String>を使用しており、それを使用して単語の辞書を実装しています。

次に、ファイルのコレクションがあり、辞書で定義されたベクトル空間(単語の空間)に各ファイルの表現を作成したいと思います。

各ファイルには、次のプロパティでファイルを表すベクトルが必要です。

  • ベクトルは辞書と同じサイズである必要があります
  • ファイル内のcontainedごとに、ベクトルには1が含まれている必要があります。辞書の単語の位置に対応する位置
  • ファイルに含まれていない単語ごとに、ベクトルには-1辞書の単語の位置に対応する位置

したがって、私の考えは、Vector<Boolean>を使用してこれらのベクトルを実装することです。 (コレクション内のドキュメントを表すこの方法は、ブールモデルと呼ばれます http://www.site.uottawa.ca/~diana/csi4107/L3.pdf

このベクトルを作成する手順で私が直面している問題は、次のような辞書内の単語の位置を見つける方法が必要なことです。

String key;
int i = get_position_of_key_in_Treemap(key); <--- purely invented method...

1)TreeMapで使用できるこのようなメソッドはありますか?そうでない場合は、自分で実装するのに役立つコードを提供できますか?

2)TreeMapに位置を取得できるイテレーター(キーのアルファベット順)はありますか?

3)最終的には別のクラスを使用して辞書を実装する必要がありますか?(TreeMapsでは必要なことを実行できないと思われる場合)はいの場合、どれですか?

前もって感謝します。

追加部分:

Dasblinkenlightによって提案されたソリューションは問題ないように見えますが、複雑さの問題があり(キーを配列にコピーするため、辞書の次元と線形)、ファイルごとにそれを行うという考えは受け入れられません。

私の質問に対する他のアイデアはありますか?

20
Matteo

私の質問に答えるために尽力してくれた皆さんに感謝したいと思います。それらはすべて非常に役に立ち、それぞれを最大限に活用して、実際にプロジェクトで実装したソリューションにたどり着きました。


私の単一の質問に対する最良の答えであると私が信じているのは次のとおりです。

2)TreeMapsで@Isoliveirasaisとして定義されているイテレータはありません。

_There's no such implementation in the JDK itself. 
Although TreeMap iterates in natural key ordering,
its internal data structures are all based on trees and not arrays
(remember that Maps do not order keys, by definition, 
in spite of that the very common use case).
_

そして私がこれで見つけたようにSO回答 TreeMapを反復する方法?Mapの要素を反復する唯一の方法は使用することですmap.entrySet()そして、Set(またはIteratorsを持つ他のクラス)で定義されたIteratorsを使用します。


3)TreeMapを使用して辞書を実装することは可能ですが、これにより、含まれているWordのインデックスを見つける際にO(logN)の複雑さが保証されます(でのルックアップのコスト)ツリーデータ構造)。

同じ手順でHashMapを使用すると、代わりに複雑さがO(1)になります。


1)そのような方法はありません。唯一の解決策は、それを完全に実装することです。

@Paulが述べたように

_Assumes that once getPosition() has been called, the dictionary is not changed.
_

解決策の前提は、その辞書が作成されると、その後は変更されないということです。このようにして、Wordの位置は常に同じになります。

この仮定を与えると、複雑な辞書を構築できるソリューションを見つけましたO(N)そして保証後、constat time O(1)ルックアップ。

私は辞書を次のようにHashMapとして定義しました。

_public HashMap<String, WordStruct> dictionary = new HashMap<String, WordStruct>();
_
  • キー->辞書に含まれる単語を表すString
  • 値->作成されたクラスのObjectWordStruct

ここで、WordStructクラスは次のように定義されています。

_public class WordStruct {

    private int DictionaryPosition;    // defines the position of Word in dictionary once it is alphabetically ordered

    public WordStruct(){

    }

    public SetWordPosition(int pos){
        this.DictionaryPosition = pos;
    }

}
_

そして、辞書のWordエントリと結合したいあらゆる種類の属性のメモリを保持することができます。

ここで、コレクションのすべてのファイルに含まれるすべての単語を繰り返し辞書に入力します。

_THE FOLLOWING IS PSEUDOCODE

for(int i = 0; i < number_of_files ; i++){

        get_file(i);

        while (file_contais_words){

            dictionary.put( Word(j) , new LemmaStruct());

        }

}   
_

HashMapが任意の順序で入力されたら、@ dasblinkenlightで示される手順を使用して、複雑さO(N)で一度だけ注文します。

_    Object[] dictionaryArray = dictionary.keySet().toArray();
    Arrays.sort(dictionaryArray);

    for(int i = 0; i < dictionaryArray.length; i++){

        String Word = (String) dictionaryArray[i];
        dictionary.get(Word).SetWordPosition(i);

    }
_

そして、これからは、辞書でWordのアルファベット順のインデックス位置を持つために必要なのは、その変数DictionaryPositionにアクセスすることだけです。

wordはそれにアクセスする必要があることを知っているので、これにはHashMapで一定のコストがかかります。


よろしくお願いします。メリークリスマスをお祈りします!

2
Matteo

ツリーマップを作成したら、並べ替えられたキーを配列にコピーし、 Arrays.binarySearch O(logN)時間でインデックスを検索します。値が必要な場合は、元のマップでも検索を実行します。

編集:これは、キーを配列にコピーする方法です

String[] mapKeys = new String[treeMap.size()];
int pos = 0;
for (String key : treeMap.keySet()) {
    mapKeys[pos++] = key;
}
17
dasblinkenlight

別の解決策は、TreeMapの-​​ headMap メソッドを使用することです。 WordがTreeMapに存在する場合、そのヘッドマップの size() は、辞書内のWordのインデックスと等しくなります。私の他の答えと比較して、それは少し無駄かもしれません。

Javaでコーディングする方法は次のとおりです。

import Java.util.*;

class Test {
    public static void main(String[] args) {
        TreeMap<String,String> tm = new TreeMap<String,String>();
        tm.put("quick", "one");
        tm.put("brown", "two");
        tm.put("fox", "three");
        tm.put("jumps", "four");
        tm.put("over", "five");
        tm.put("the", "six");
        tm.put("lazy", "seven");
        tm.put("dog", "eight");
        for (String s : new String[] {
            "quick", "brown", "fox", "jumps", "over",
            "the", "lazy", "dog", "before", "way_after"}
        ) {
            if (tm.containsKey(s)) {
                // Here is the operation you are looking for.
                // It does not work for items not in the dictionary.
                int pos = tm.headMap(s).size();
                System.out.println("Key '"+s+"' is at the position "+pos);
            } else {
                System.out.println("Key '"+s+"' is not found");
            }
        }
    }
}

プログラムによって生成される出力は次のとおりです。

Key 'quick' is at the position 6
Key 'brown' is at the position 0
Key 'fox' is at the position 2
Key 'jumps' is at the position 3
Key 'over' is at the position 5
Key 'the' is at the position 7
Key 'lazy' is at the position 4
Key 'dog' is at the position 1
Key 'before' is not found
Key 'way_after' is not found
4
dasblinkenlight

私も同じ問題を抱えていました。そこで、Java.util.TreeMapのソースコードを取得して、IndexedTreeMapと記述しました。それは私自身のIndexedNavigableMapを実装します:

public interface IndexedNavigableMap<K, V> extends NavigableMap<K, V> {
   K exactKey(int index);
   Entry<K, V> exactEntry(int index);
   int keyIndex(K k);
}

実装は、変更されたときに赤黒木でノードの重みを更新することに基づいています。重みは、特定のノードの下にある子ノードの数に1を加えたものです。たとえば、木が左に回転した場合:

    private void rotateLeft(Entry<K, V> p) {
    if (p != null) {
        Entry<K, V> r = p.right;

        int delta = getWeight(r.left) - getWeight(p.right);
        p.right = r.left;
        p.updateWeight(delta);

        if (r.left != null) {
            r.left.parent = p;
        }

        r.parent = p.parent;


        if (p.parent == null) {
            root = r;
        } else if (p.parent.left == p) {
            delta = getWeight(r) - getWeight(p.parent.left);
            p.parent.left = r;
            p.parent.updateWeight(delta);
        } else {
            delta = getWeight(r) - getWeight(p.parent.right);
            p.parent.right = r;
            p.parent.updateWeight(delta);
        }

        delta = getWeight(p) - getWeight(r.left);
        r.left = p;
        r.updateWeight(delta);

        p.parent = r;
    }
  }

updateWeightは、ルートまでの重みを更新するだけです。

   void updateWeight(int delta) {
        weight += delta;
        Entry<K, V> p = parent;
        while (p != null) {
            p.weight += delta;
            p = p.parent;
        }
    }

そして、ここでインデックスによって要素を見つける必要があるときは、重みを使用する実装です。

public K exactKey(int index) {
    if (index < 0 || index > size() - 1) {
        throw new ArrayIndexOutOfBoundsException();
    }
    return getExactKey(root, index);
}

private K getExactKey(Entry<K, V> e, int index) {
    if (e.left == null && index == 0) {
        return e.key;
    }
    if (e.left == null && e.right == null) {
        return e.key;
    }
    if (e.left != null && e.left.weight > index) {
        return getExactKey(e.left, index);
    }
    if (e.left != null && e.left.weight == index) {
        return e.key;
    }
    return getExactKey(e.right, index - (e.left == null ? 0 : e.left.weight) - 1);
}

また、キーのインデックスを見つけるのに非常に便利です。

    public int keyIndex(K key) {
    if (key == null) {
        throw new NullPointerException();
    }
    Entry<K, V> e = getEntry(key);
    if (e == null) {
        throw new NullPointerException();
    }
    if (e == root) {
        return getWeight(e) - getWeight(e.right) - 1;//index to return
    }
    int index = 0;
    int cmp;
    if (e.left != null) {
        index += getWeight(e.left);
    }
    Entry<K, V> p = e.parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        while (p != null) {
            cmp = cpr.compare(key, p.key);
            if (cmp > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    } else {
        Comparable<? super K> k = (Comparable<? super K>) key;
        while (p != null) {
            if (k.compareTo(p.key) > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    }
    return index;
}

間もなくIndexedTreeSetを実装しますが、その間、IndexedTreeMapのキーセットを使用できます。

更新:IndexedTreeSetが実装されました。

この作業の結果は https://github.com/geniot/indexed-tree-map で見つけることができます。

2

JDK自体にはそのような実装はありません。 TreeMapは自然キーの順序付けを繰り返しますが、その内部データ構造はすべてツリーに基づいており、配列ではありません(Mapsは、非常に一般的な使用法にもかかわらず、定義上、キーを順序付けしないことに注意してください)場合)。

とはいえ、MapindexOf(key)計算。これは、辞書式順序が可変データ構造で安定していないためです(たとえば、挿入順序とは対照的です)。例:最初のキーと値のペア(エントリ)を挿入すると、マップ内では、その位置は常に1になります。ただし、挿入された2番目のキーによっては、新しいキーがMapのキーよりも「大きい」または「小さい」ため、その位置が変わる場合があります。挿入操作中にキーのインデックス付きリストを維持および更新することでこれを確実に実装できますが、挿入操作にはO(n log(n))が必要になります(配列を並べ替える必要があるため)。データアクセスパターンに応じて、望ましいかどうか。

ApacheCommonsのListOrderedMapLinkedMapはどちらも必要なものに近づいていますが、挿入順序に依存しています。それらの実装をチェックして、少しから中程度の労力で問題に対する独自のソリューションを開発できると私は信じています(これは、ListOrderedMaps内部バッキング配列をソートされたリストに置き換えるだけの問題です-TreeListたとえば、Apache Commonsで)。

与えられたキーよりも少ない要素の数を引くことによって、インデックスを自分で計算することもできます(これは、最も頻繁なケースでは、何も比較していないため、リストを反復して要素を検索するよりも高速です) 。

2
lsoliveira

Isolvieiraに同意します。おそらく最善のアプローチは、TreeMapとは異なる構造を使用することです。

ただし、それでもキーのインデックスを計算したい場合、解決策は、探しているキーよりも低いキーの数を数えることです。

コードスニペットは次のとおりです。

    Java.util.SortedMap<String, String> treeMap = new Java.util.TreeMap<String, String>();
    treeMap.put("d", "content 4");
    treeMap.put("b", "content 2");
    treeMap.put("c", "content 3");
    treeMap.put("a", "content 1");

    String key = "d"; // key to get the index for
    System.out.println( treeMap.keySet() );

    final String firstKey = treeMap.firstKey(); // assuming treeMap structure doesn't change in the mean time
    System.out.format( "Index of %s is %d %n", key, treeMap.subMap(firstKey, key).size() );
1
user246645

TreeMapの値に辞書内の位置が含まれるようにすることを考えましたか?ここでは、ファイルの詳細にBitSetを使用しています。

これは、以下の他のアイデアとほとんど同じようには機能しません。

_Map<String,Integer> dictionary = new TreeMap<String,Integer> ();

private void test () {
  // Construct my dictionary.
  buildDictionary();
  // Make my file data.
  String [] file1 = new String[] {
    "1", "3", "5"
  };
  BitSet fileDetails = getFileDetails(file1, dictionary);
  printFileDetails("File1", fileDetails);
}

private void printFileDetails(String fileName, BitSet details) {
  System.out.println("File: "+fileName);
  for ( int i = 0; i < details.length(); i++ ) {
    System.out.print ( details.get(i) ? 1: -1 );
    if ( i < details.length() - 1 ) {
      System.out.print ( "," );
    }
  }
}

private BitSet getFileDetails(String [] file, Map<String, Integer> dictionary ) {
  BitSet details = new BitSet();
  for ( String Word : file ) {
    // The value in the dictionary is the index of the Word in the dictionary.
    details.set(dictionary.get(Word));
  }
  return details;
}

String [] dictionaryWords = new String[] {
  "1", "2", "3", "4", "5"
};

private void buildDictionary () {
  for ( String Word : dictionaryWords ) {
    // Initially make the value 0. We will change that later.
    dictionary.put(Word, 0);
  }
  // Make the indexes.
  int wordNum = 0;
  for ( String Word : dictionary.keySet() ) {
    dictionary.put(Word, wordNum++);
  }
}
_

ここで、ファイルの詳細の構築は、ファイル内の各単語のTreeMapでの単一のルックアップで構成されています。

辞書valueTreeMapを他の目的で使用することを計画している場合は、いつでもIntegerで構成できます。

追加

さらに考えてみると、valueMapフィールドが何かに割り当てられている場合は、Map内の独自の位置を計算し、比較のためにStringsのように動作する特別なキーをいつでも使用できます。

_private void test () {
  // Dictionary
  Map<PosKey, String> dictionary = new TreeMap<PosKey, String> ();
  // Fill it with words.
  String[] dictWords = new String[] {
                       "0", "1", "2", "3", "4", "5"};
  for ( String Word : dictWords ) {
    dictionary.put( new PosKey( dictionary, Word ), Word );
  }
  // File
  String[] fileWords = new String[] {
                       "0", "2", "3", "5"};
  int[] file = new int[dictionary.size()];
  // Initially all -1.
  for ( int i = 0; i < file.length; i++ ) {
    file[i] = -1;
  }
  // Temp file words set.
  Set fileSet = new HashSet( Arrays.asList( fileWords ) );
  for ( PosKey key : dictionary.keySet() ) {
    if ( fileSet.contains( key.getKey() ) ) {
      file[key.getPosiion()] = 1;
    }
  }

  // Print out.
  System.out.println( Arrays.toString( file ) );
  // Prints: [1, -1, 1, 1, -1, 1]

}

class PosKey
    implements Comparable {
  final String key;
  // Initially -1
  int position = -1;
  // The map I am keying on.
  Map<PosKey, ?> map;

  public PosKey ( Map<PosKey, ?> map, String Word ) {
    this.key = Word;
    this.map = map;
  }

  public int getPosiion () {
    if ( position == -1 ) {
      // First access to the key.
      int pos = 0;
      // Calculate all positions in one loop.
      for ( PosKey k : map.keySet() ) {
        k.position = pos++;
      }
    }
    return position;
  }

  public String getKey () {
    return key;
  }

  public int compareTo ( Object it ) {
    return key.compareTo( ( ( PosKey )it ).key );
  }

  public int hashCode () {
    return key.hashCode();
  }
}
_

注意:getPosition()が呼び出されると、辞書は変更されないと想定します。

1
OldCurmudgeon

辞書を保存するためにSkipListを作成することをお勧めします。これにより、O(log N)ルックアップ、挿入、および削除が提供されると同時に、インデックスも提供できるようになります(ツリーの実装では、ノードがインデックスを返さないため、通常、インデックスを返すことはできません。それを知っている、そしてそれらを最新の状態に保つにはコストがかかるだろう)。残念ながら、ConcurrentSkipListMapのJava実装はインデックスを提供しないため、独自のバージョンを実装する必要があります。

アイテムのインデックスの取得はO(log N)になります。2回のルックアップを行わずにインデックスと値の両方が必要な場合は、両方を保持するラッパーオブジェクトを返す必要があります。

0
Trevor Freeman