Как выбрать случайный ключ из HashMap в Java?

Я работаю с большим ArrayList<HashMap<A,B>>, и мне неоднократно нужно было бы выбрать случайный ключ из случайного HashMap (и сделать с ним некоторые вещи). Выбор случайного HashMap тривиален, но как я должен выбрать случайный ключ из этого HashMap?

скорость важна (так как мне нужно сделать это 10000 раз, а хэш-карты большие), поэтому просто выберите случайное число k в [0,9999], а затем сделайте .next() на итераторе k раз, на самом деле не вариант. аналогично, преобразование HashMap в массив или ArrayList при каждом случайном выборе действительно не вариант. пожалуйста, прочитайте это перед тем, как ответить.

технически я чувствую, что это должно быть возможно, так как HashMap хранит свои ключи в Entry[] внутренне, и выбор наугад из массива легко, но я не могу понять, как получить доступ к этому Entry[]. Так что любые идеи для доступа к внутреннему Entry[] более чем приветствуется. Другие решения (если они не потребляют линейного времени в размер hashmap) также приветствуются, конечно.

Примечание: эвристические методы в порядке, так что если есть метод, который исключает 1% элементов (например, из-за мульти-наполняли ведра) это не проблема вообще.

9 ответов


с макушки моей головы

List<A> keysAsArray = new ArrayList<A>(map.keySet())
Random r = new Random()

потом просто

map.get(keysAsArray.get(r.nextInt(keysAsArray.size()))

вам нужен доступ к базовой таблице записей.

// defined staticly
Field table = HashMap.class.getDeclaredField("table");
table.setAccessible(true);
Random rand = new Random();

public Entry randomEntry(HashMap map) {
    Entry[] entries = (Entry[]) table.get(map);
    int start = rand.nextInt(entries.length);
    for(int i=0;i<entries.length;i++) {
       int idx = (start + i) % entries.length;
       Entry entry = entries[idx];
       if (entry != null) return entry;
    }
    return null;
}

Это все еще должно пройти записи, чтобы найти тот, который есть, поэтому худший случай-O(n), но типичное поведение-O(1).


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


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

что вам нужно, это второй заказ Set - как структура данных для хранения ключей - не список, как некоторые предложили здесь. Списки-подобные структуры данных являются дорогостоящими для удаления элементов. Необходимые операции-добавление / удаление элементов в постоянное время (чтобы сохранить его обновлено с помощью HashMap) и процедуры выбора случайного элемента. Следующий класс MySet именно это

class MySet<A> {
     ArrayList<A> contents = new ArrayList();
     HashMap<A,Integer> indices = new HashMap<A,Integer>();
     Random R = new Random();

     //selects random element in constant time
     A randomKey() {
         return contents.get(R.nextInt(contents.size()));
     }

     //adds new element in constant time
     void add(A a) {
         indices.put(a,contents.size());
         contents.add(a);
     }

     //removes element in constant time
     void remove(A a) {
         int index = indices.get(a);
         contents.set(index,contents.get(contents.size()-1));
         contents.remove(contents.size()-1);
         indices.set(contents.get(contents.size()-1),index);
         indices.remove(a);
     }
}

Я предполагаю, что вы используете HashMap Как вам нужно выглядеть на более поздний срок?

если не так, то просто измените ваш HashMap до Array/ArrayList.

если это так, почему бы не хранить свои объекты в Map и ArrayList Так что вы можете посмотреть случайно или по ключу.

кроме того, не могли бы вы использовать TreeMap вместо HashMap? Я не знаю, какой тип Вашего ключа, но вы используете TreeMap.floorKey() в сочетании с некоторым ключом рандомизатор.


потратив некоторое время, я пришел к выводу, что вам нужно создать модель, которая может быть подкреплена List<Map<A, B>> и List<A> сохранить ключи. Вам нужно сохранить доступ к вашему List<Map<A, B>> и List<A>, просто предоставьте операции / методы вызывающему. Таким образом, вы будете иметь полный контроль над выполнением, а сами объекты будут безопаснее от внешних изменений.

кстати, ваши вопросы приводят меня к,

в этом примере, IndexedSet, может дать вам представление о том, как-для.

[отредактировано]

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

List<A> list = new ArrayList(map.keySet());
SetUniqueList unikList = new SetUniqueList(list, map.keySet);
// Now unikList should reflect all the changes to the map keys
...
// Then you can do
unikList.get(i);

Примечание: я сам не пробовал. Сделаю это позже (спеша домой).


получить от вашей карты keyset с map.keySet() и выберите случайный ключ, как вы делали это с ArrayList. Затем вы можете получить значение с map.get(randomKey).


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

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

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


Как насчет упаковки HashMap в другую реализацию Map? Другая карта поддерживает список, и на put () он делает:

if (inner.put(key, value) == null) listOfKeys.add(key);

(Я предполагаю, что нули для значений не разрешены, если они используют containsKey, но это медленнее)