Найти позицию элемента в Java TreeMap - PullRequest
17 голосов
/ 14 декабря 2011

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

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

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

  • вектор должен иметь тот же размер, что и словарь
  • для каждого слова , содержащегося в файле , вектор должен иметь 1 в позиции, соответствующей позиции слова в словаре
  • для каждого слова , не содержащегося в файле, вектор должен иметь -1 в позиции, соответствующей позиции слова в словаре

Так что моя идея - использовать Vector<Boolean> для реализации этих векторов. (Этот способ представления документов в коллекции называется Boolean Model - 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 ]

17 голосов
/ 14 декабря 2011

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

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

String[] mapKeys = new String[treeMap.size()];
int pos = 0;
for (String key : treeMap.keySet()) {
    mapKeys[pos++] = key;
}
4 голосов
/ 21 декабря 2011

Альтернативным решением будет использование метода TreeMap 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 голосов
/ 09 февраля 2013

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

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

    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 голосов
/ 24 декабря 2011

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


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

2) Нет итератора, определенного в TreeMaps как @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).

и как я нашел в этом SO-ответе Как перебирать 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) и после гарантии получить возможность получить индекс слова, содержащегося с постоянным временем O (1)в поиске.

Я определил словарь как HashMap следующим образом:

public HashMap<String, WordStruct> dictionary = new HashMap<String, WordStruct>();
  • ключ -> String, представляющий слово, содержащееся в словаре
  • значение -> 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.


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

2 голосов
/ 20 декабря 2011

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

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

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

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

1 голос
/ 21 декабря 2011

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

Добавлена ​​

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

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

NB. Предполагается, что после вызова getPosition() словарь не изменяется.

1 голос
/ 20 декабря 2011

Я согласен с 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() );
0 голосов
/ 23 декабря 2011

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

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

...