Найти позицию элемента в Java TreeMap

Я работаю с TreeMap строк TreeMap<String, String>, и используя его для реализации набора слов.

затем у меня есть коллекция файлов, и я хотел бы создать представление каждого файла в векторном пространстве (пространстве слов), определенном словарем.

каждый файл должен иметь вектор, представляющий его следующие свойства:

  • вектор должен иметь тот же размер, что и словарь
  • для каждого слова содержится в файле вектор должен иметь 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, выглядит отлично, но имеет проблему сложности (линейную с размерностью словаря из-за копирования ключей в массив), и идея сделать это для каждого файла неприемлема.

любые другие идеи на мои вопросы?

8 ответов


после того, как вы построили карту дерева, скопируйте ее отсортированные ключи в массив и используйте Arrays.binarySearch для поиска индекса в O(logN) времени. Если вам нужно значение, выполните поиск на исходной карте тоже.

Edit: вот как вы копируете ключи в массив

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

в самом JDK такой реализации нет. Хотя TreeMap итерации в естественном порядке ключей, его внутренние структуры данных основаны на деревьях, а не массивах (помните, что Maps Не заказывайте ключи по определению, несмотря на то, что это очень распространенный вариант использования).

тем не менее, вы должны сделать выбор, поскольку невозможно иметь время вычисления O(1) для ваших критериев сравнения как для вставки в Map и indexOf(key) расчет. Это связано с тот факт, что лексикографический порядок нестабилен в изменяемой структуре данных (в отличие от порядка вставки, например). Пример: как только вы вставляете первую пару ключ-значение (запись) в карту, ее позиция всегда будет одной. Однако в зависимости от второго вставленного ключа эта позиция может измениться, поскольку новый ключ может быть "больше" или "ниже", чем в Map. Вы можете реализовать это, поддерживая и обновляя индексированный список ключей во время операции вставки, но тогда у вас будет O(N log(n)) для ваших операций вставки (так как вам нужно будет переупорядочить массив). Это может быть желательно или нет, в зависимости от ваших шаблонов доступа к данным.

ListOrderedMap и LinkedMap в Apache Commons оба приближаются к тому, что вам нужно, но полагаются на порядок вставки. Вы можете проверить их реализацию и разработать свое собственное решение проблемы с небольшими и умеренными усилиями, я считаю (это должно быть просто вопросом замены ListOrderedMaps внутренний массив поддержки с сортированный список -TreeList в Apache Commons, например).

вы также можете вычислить индекс самостоятельно, вычитая количество элементов, которые ниже, чем заданный ключ (который должен быть быстрее, чем итерация по списку поиска вашего элемента, в наиболее частом случае - поскольку вы ничего не сравниваете).


альтернативным решением было бы использовать TreeMap ' s headMap метод. Если слово существует в TreeMap, потом size() его карта головы равна индексу слова в словаре. Это может быть немного расточительно по сравнению с моим другим ответом, через.

вот как вы кодируете его на 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

я хотел бы поблагодарить всех вас за усилия, которые вы приложили, отвечая на мой вопрос, все они были очень полезны и принимая лучшее от каждого из них заставил меня прийти к решению, которое я фактически реализовал в своем проекте.


я считаю, что лучшие ответы на мои отдельные вопросы:

2) на картах деревьев нет итератора, определенного как @Isoliveira sais:

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).

и как я нашел в этом так ответа как перебрать TreeMap?, единственный способ итерации по элементам в Map использовать map.entrySet() и использовать итераторы, определенные на Set (или какой-то другой класс с итераторами).


3) можно использовать TreeMap реализовать словарь, но это гарантирует сложность O (logN) в поиске индекса содержащегося слова (стоимость поиска в структуре данных дерева).

С помощью HashMap С той же процедурой вместо этого будет иметь сложность O (1).


1) такого метода не существует. Единственное решение-реализовать его полностью.

как заявил @Paul

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

предположение решения заключается в том, что как только этот словарь будет создан, он не будет изменен впоследствии: таким образом, положение слова всегда будет одинаковым.

давая это предположение, я нашел решение, которое позволяет построить словарь со сложностью O (N) и после гарантии возможности получить индекс a слово, содержащееся с константой времени O (1) в поиске.

я определил словарь как HashMap такой:

public HashMap<String, WordStruct> dictionary = new HashMap<String, WordStruct>();
  • ключ --> на String представляя слово, содержащееся в словаре
  • значение --> an Object созданного класса WordStruct

здесь 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;
    }

}

и позволяет мне хранить память о любом атрибуте, который мне нравится соединять со словом Словарь.

теперь я заполняю словарь, повторяя все слова, содержащиеся во всех файлах моей коллекции:

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);

    }

и отныне, чтобы иметь позицию индекса в алфавитном порядке слова в словаре, нужно только получить переменную DictionaryPosition:

так как слово знает, что вы просто нужно получить к нему доступ, и это имеет постоянную стоимость в HashMap.


еще раз спасибо и желаю вам всем счастливого Рождества!!


у меня была та же проблема. Поэтому я взял исходный код java.утиль.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);
}

реализация основана на обновлении Весов узлов в красно-черном дереве при его изменении. Вес-это количество дочерних узлов под данным узлом, плюс один-self. Например, когда дерево поворачивается влево:

    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


Я согласен с Isolvieira. Пожалуй, лучшим подходом было бы использовать другую структуру, чем дерева.

однако, если вы все еще хотите пойти с вычислением индекса ключей, решение будет посчитать, сколько ключей ниже, чем ключ, который вы ищете.

вот фрагмент кода:

    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() );

вы думали, чтобы сделать значения в вашем 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 для каждого слова в файле.

если вы планируете использовать value в словарь TreeMap для чего-то еще вы всегда может составить его с Integer.

добавил

думая об этом далее, если


Я бы предложил вам написать SkipList для хранения вашего словаря, так как это по-прежнему будет предлагать поиск, вставку и удаление O(log N), а также возможность предоставлять индекс (реализации дерева обычно не могут возвращать индекс, так как узлы его не знают, и будет стоить их обновления). К сожалению, реализация Java ConcurrentSkipListMap не предоставляет индекс, поэтому вам нужно будет реализовать свою собственную версию.

получаете индекс элемента будет O (log N), если вы хотите как индекс, так и значение, не делая 2 поиска, вам нужно будет вернуть объект-оболочку, содержащий оба.