我正在使用大ArrayList<HashMap<A,B>>
,我会反复需要从随机HashMap中选择一个随机密钥(并用它来做一些事情)。选择随机HashMap是微不足道的,但我该如何从这个HashMap中选择一个随机密钥?
速度很重要(因为我需要做10000次并且哈希图很大),所以只需在[0,9999]中选择一个随机数k,然后在迭代器上执行.next()
k次,真的不是一个选择。 同样,在每个随机选择中将HashMap转换为数组或ArrayList实际上都不是一个选项。请在回复之前阅读此内容。
从技术上讲,我认为这应该是可行的,因为HashMap在内部将其键存储在Entry[]
中,并且从数组中随机选择很容易,但我无法弄清楚如何访问它{{ 1}}。因此,访问内部Entry[]
的任何想法都非常受欢迎。其他解决方案(只要它们不占用散列图大小的线性时间)也是受欢迎的。
注意:启发式方法很好,所以如果有一种方法可以排除1%的元素(例如由于多个填充的桶),那就完全没问题了。
答案 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
。
如果是这种情况,为什么不将对象存储在Map
和ArrayList
中,以便随机或按键查找。
或者,您可以使用TreeMap
代替HashMap
吗?我不知道您的密钥是什么类型,但是您将TreeMap.floorKey()
与一些关键随机函数结合使用。
答案 6 :(得分:1)
花了一些时间后,我得出的结论是,您需要创建一个可以由List<Map<A, B>>
和List<A>
支持的模型来维护您的密钥。您需要保持List<Map<A, B>>
和List<A>
的访问权限,只需向调用者提供操作/方法即可。通过这种方式,您可以完全控制实现,实际对象将更安全地从外部更改。
values()
方法也会返回Set
。此示例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,但速度较慢)