【问题标题】:Java - Choose a random value from a HashMap but one with the highest integer assignedJava - 从 HashMap 中选择一个随机值,但分配的整数最大
【发布时间】:2015-06-09 16:13:36
【问题描述】:

我想从 HashMap 中选择一个随机值,但它必须具有分配给它的最高值。 例如,我想从这个列表中随机选择一个人,但前提是他们每个人的“票”最多。 示例:

  • 约翰 - 10
  • 亚历克斯 - 10
  • 大卫 - 5
  • 艾伦 - 10

从这个列表中,我想随机选择一个拥有最多“票”的人。所以在这种情况下,它将来自 John、Alex 或 Alan。

到目前为止,我有这个:

HashMap<UUID, Integer> tickets = new HashMap<UUID, Integer>();

在上面的例子中,UUID 是人名,而 Integer 是他们的门票金额。

【问题讨论】:

  • 您可能会从这里的这篇文章中受益,似乎非常相关。一旦所有的人都拥有最高的整数,你就可以随机选择一个。 stackoverflow.com/questions/7498751/….
  • @LaurentiuL。这个问题的措辞很糟糕。我认为OP的意思是:如果有超过一个人的票数最多,则随机选择其中一个并退还他。

标签: java


【解决方案1】:

您将需要Map<Integer, List<UUID>> 类型的地图。使用SortedMap<K, V>,默认情况下,您的密钥将按其自然顺序排序。由于您使用 Integer 作为键,因此不需要任何特殊逻辑来强制排序。

然后你只需要生成一个介于 0 和 list.size() - 1 之间的随机数,用于映射中的最高键。要获得最高密钥,您需要从地图的keySet 构造一个TreeSet<T> 实例。然后您可以使用last() 获取最高密钥,然后您可以检索该密钥的 UUID 列表。

您可以像这样轻松创建排序地图:

Map<Integer, List<UUID>> map = new SortedMap<>();

然后你只需要像这样从你的keySet 创建一个TreeSet&lt;Integer&gt; 实例:

TreeSet<Integer> sortedKeySet = new TreeSet<>(map.keySet());

之后,您可以获得最高的密钥,以及相关的 UUID 列表,如下所示:

Integer highestKey = sortedKeySet.last();
List<UUID> usersWithHigestKey = map.get(highestKey);

那么你只需要在0usersWithHighestKey.size()(不包括)之间生成一个随机数,就可以得到一个价值最高的随机用户。

【讨论】:

  • 要获取与最高键对应的项目,您可以使用 List&lt;UUID&gt; highestValueList = myMap.get(myMap.lastKey()); 之类的东西(尽管您应该首先确保地图不为空)
  • 感谢您的建议,但我没有那么先进,我还没有体验过 SortedMaps 或 NavigableSets。如果你能给我一个例子,将不胜感激。
  • @Ally 要使用排序地图,假设您已经将 tickets 地图填充了数据:SortedMap&lt;Integer, List&lt;UUID&gt;&gt; sortedTickets = new TreeMap&lt;&gt;(); for (Entry&lt;UUID, Integer&gt; entry : tickets.entrySet()) {List&lt;UUID&gt; list = sortedTickets.get(entry.getValue()); if (list == null) {list = new ArrayList&lt;&gt;(); sortedTickets.put(entry.getValue(), list);} list.add(entry.getKey());} List&lt;UUID&gt; highestValList = sortedTickets.get(sortedTickets.lastKey()); UUID random = highestValList.get((int)(Math.random() * highestValList.size()));
  • @Ally 看看我的编辑。你可以使用`TreeSet`(它同时实现了SortedSet&lt;T&gt;NavigableSet&lt;T&gt;
【解决方案2】:

一个简单的解决方案是构建一个新数组,该数组只包含等于一个人拥有的最大“票”数量的元素。然后使用随机数生成器从该数组中选择一个元素(通过随机生成一个介于 0 和 list.length-1 之间的数字)

【讨论】:

    【解决方案3】:

    如果您使用的是 Java 8,则可以使用流和 lambda 来解决问题:

    private static final Random R = new Random();
    
    public static String getRandomMax(HashMap<String, Integer> data)
    {
        if ((null == data) || data.isEmpty()) {
            return (null);
        }
    
        int max = data.values().stream().max(Integer::max).get();
    
        Predicate<String> reducer = name -> { return (data.get(name) == max); };
    
        List<String> names = data.keySet()
                                 .stream()
                                 .filter(reducer)
                                 .collect(Collectors.toList());
    
        if (1 == names.size()) {
            return (names.get(0));
        }
        int idx = R.nextInt(names.size());
        return (names.get(idx));
    }
    

    【讨论】:

      【解决方案4】:

      您的问题可以通过Reservoir Sampling algorithm 解决。您可以遍历具有最高票号的行并使用概率 1/n 选择当前项目,其中 n 是您之前迭代的项目数,因此您选择第一个,因为它是 1/1 = 100%,但您继续迭代,那么你有 1/2=50% 的概率选择第二个,覆盖你之前的那个(第一个或第二个),然后你有 1/3 的机会选择第三个,依此类推。

      但是您混合了一些行,一些具有最大值,另一些具有较低的值,一开始您不知道最大值是多少,但您可以在单个循环中计算它。

      伪代码:

      n = 0;
      max = -1;
      uuid = null;
      
      for (k, v: entries) {
        if (v > max) {
          // Reset to new max, we pick that one, prob = 1/1 = 100%
          // Previous work is useless as the value of max was not the real max
          max = v;
          n = 1;
          uuid = k;
        } else if (v == max) {
          // This item's value is equal to current max so there is a chance we pick it
          // We pick this with prob 1/n
          n = n + 1;
          if (randomFloatFrom0To1() < 1/n) {
            uuid = k;
          }
        } else {
          // This sample is not interesting
        }
      }
      //uuid at this point should be random among the ones with v = max
      

      我认为这就是您所需要的。单循环,O(n)

      输入可以是映射或任何其他键/值对序列。

      randomFloatFrom0To1() 可以在 java 中实现为:

      java.util.Random r = new java.util.Random();
      // ...
      r.nextFloat() // <-- This returns random float between 0 and 1
      

      你也可以使用随机整数:

      if (r.nextInteger(n) == 0) { // <-- Same probability, 1/n
        //...
      

      【讨论】:

      • 如果你先构造你的要排序的map,可以在O(log n)时间实现新key的插入,在常数时间内取到最大key;比O(n)好多了。
      • @VivinPaliath 你假设我正在使用地图。要构建地图,您需要 O(n),因为您需要处理 n 个元素(将它们放在地图上)加上地图处理(即每个元素的 O(log n),因此 O(n log n))。用我的方法你就完成了,你不需要地图,只需要任何元组输入和一个循环。
      • @usser270349。地图的创建将是nlogn,但之后最大键的检索是O(1)。您每次都建议O(n) 创建,然后O(n) 检索。
      • @VivinPaliath 这个算法不需要任何地图。如果输入是 HashMap,它可以迭代它的入口集,但如果它是任何其他输入,任何对序列,则不需要先构建映射。
      • 我同意,但即使您不将地图构造纳入最终复杂性,您仍然在执行O(n) 查找。如果你不知道你得到什么样的集合,如果你不知道它有什么样的排序,这可能很好。但这里的重点是,OP 可以使用一个集合来完全解决他的用例,并使用这些集合中的行为来完成他想要的。因此,没有必要创建一个专门的方法来简单地找到最大密钥; SortedSetTreeMap 将为您做到这一点。
      猜你喜欢
      • 1970-01-01
      • 2012-09-05
      • 1970-01-01
      • 2019-08-08
      • 2012-02-21
      • 2016-03-02
      • 1970-01-01
      • 2023-03-25
      • 1970-01-01
      相关资源
      最近更新 更多