如何从Java中的HashMap中选择一个随机密钥?

时间:2012-09-12 09:38:43

标签: java random hashmap

我正在使用大ArrayList<HashMap<A,B>>,我会反复需要从随机HashMap中选择一个随机密钥(并用它来做一些事情)。选择随机HashMap是微不足道的,但我该如何从这个HashMap中选择一个随机密钥?

速度很重要(因为我需要做10000次并且哈希图很大),所以只需在[0,9999]中选择一个随机数k,然后在迭代器上执行.next() k次,真的不是一个选择。 同样,在每个随机选择中将HashMap转换为数组或ArrayList实际上都不是一个选项。请在回复之前阅读此内容。

从技术上讲,我认为这应该是可行的,因为HashMap在内部将其键存储在Entry[]中,并且从数组中随机选择很容易,但我无法弄清楚如何访问它{{ 1}}。因此,访问内部Entry[]的任何想法都非常受欢迎。其他解决方案(只要它们不占用散列图大小的线性时间)也是受欢迎的。

注意:启发式方法很好,所以如果有一种方法可以排除1%的元素(例如由于多个填充的桶),那就完全没问题了。

10 个答案:

答案 0 :(得分:12)

从我的头顶

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

然后只是

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

答案 1 :(得分:5)

您需要访问基础条目表。

// 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)。

答案 2 :(得分:3)

听起来你应该考虑一个辅助的密钥列表或一个真实的对象,而不是一个地图,存储在你的列表中。

答案 3 :(得分:3)

我设法找到了没有性能损失的解决方案。我会在这里发布,因为它可以帮助其他人 - 并且可能回答关于这个主题的几个开放式问题(稍后我会搜索这些)。

您需要的是第二个自定义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);
     }
}

答案 4 :(得分:2)

正如@Alberto Di Gioacchino 指出的那样,已接受的解决方案中存在一个带有删除操作的错误。我就是这样解决的。

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 item) {
         indices.put(item,contents.size());
         contents.add(item);
     }

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

答案 5 :(得分:1)

我假设您正在使用HashMap,因为您需要在以后查看某些内容?

如果不是这样,那么只需将HashMap更改为Array / ArrayList

如果是这种情况,为什么不将对象存储在MapArrayList中,以便随机或按键查找。

或者,您可以使用TreeMap代替HashMap吗?我不知道您的密钥是什么类型,但是您将TreeMap.floorKey()与一些关键随机函数结合使用。

答案 6 :(得分:1)

花了一些时间后,我得出的结论是,您需要创建一个可以由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);

注意: 我自己没试过。稍后会这样做(赶回家)。

答案 7 :(得分:1)

自Java 8起,就采用了O(log(N))方法,并增加了O(log(N)):通过Spliterator创建map.entrySet().spliterator(),使log(map.size( ))trySplit()进行呼叫,并随机选择前半部分或后半部分。如果Spliterator中剩余的元素少于10个,请将其转储到列表中并随机选择。

答案 8 :(得分:0)

如果您绝对需要在HashMap中访问Entry数组,则可以使用反射。但是那时你的程序将依赖于HashMap的具体实现。

根据建议,您可以为每个地图保留一个单独的键列表。你不会保留密钥的深层副本,因此实际的内存非规范化不会那么大。

第三种方法是实现自己的Map实现,即将密钥保存在列表而不是集合中的实现。

答案 9 :(得分:0)

如何在另一个Map实现中包装HashMap?另一个地图维护一个List,而在put()上它确实:

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

(我假设不允许值为null,如果它们使用containsKey,但速度较慢)