我正在尝试创建一个方法,该方法将获取具有设置权重的项目列表,并随机选择1。我的解决方案是使用一个Hashmap,它将使用Integer作为权重从Hashmap中随机选择一个Keys。 HashMap的键可以是Object类型的混合,我想返回一个选定的键。
但是,我希望避免在避免突变的基础上返回null
值。是的,我知道这是Java,但是有更优雅的方式来编写Java并希望解决这个问题。
public <T> T getRandomValue(HashMap<?, Integer> VALUES) {
final int SIZE = VALUES.values().stream().reduce(0, (a, b) -> a + b);
final int RAND_SELECTION = ThreadLocalRandom.current().nextInt(SIZE) + 1;
int currentWeightSum = 0;
for (Map.Entry<?, Integer> entry : VALUES.entrySet()) {
if (RAND_SELECTION > currentWeightSum && RAND_SELECTION <= (currentWeightSum + entry.getValue())) {
return (T) entry.getKey();
} else {
currentWeightSum += entry.getValue();
}
}
return null;
}
答案 0 :(得分:1)
由于在正常情况下永远不会达到循环之后的代码,所以此时你应该不会写return null
之类的东西,而是抛出异常,这样就可以在这一点上发现不规则的条件而不是强迫调用者最终调试NullPointerException
,可能发生在完全不同的地方。
public static <T> T getRandomValue(Map<T, Integer> values) {
if(values.isEmpty())
throw new NoSuchElementException();
final int totalSize = values.values().stream().mapToInt(Integer::intValue).sum();
if(totalSize<=0)
throw new IllegalArgumentException("sum of weights is "+totalSize);
final int threshold = ThreadLocalRandom.current().nextInt(totalSize) + 1;
int currentWeightSum = 0;
for (Map.Entry<T, Integer> entry : values.entrySet()) {
currentWeightSum += entry.getValue();
if(threshold <= currentWeightSum) {
return entry.getKey();
}
}
// if we reach this point, the map's content must have been changed in-between
throw new ConcurrentModificationException();
}
请注意,该代码修复了代码的其他一些问题。您不应该承诺在不知道地图的实际类型的情况下返回任意T
。如果地图包含不同类型的对象作为密钥,即是Map<Object,Integer>
,则调用者不能期望得到比Object
更具体的内容。除此之外,当任何HashMap
足够时,您不应该坚持参数为Map
。此外,我更改了变量名称以遵循Java的命名约定并简化了循环的主体。
如果要支持空映射作为合法输入,将返回类型更改为Optional<T>
将是最佳解决方案,为空映射返回空可选项,否则返回包含值的可选项(这将禁止null
1}}键)。但是,循环之后应该是无法访问的代码点应该被标记为异常。