【问题标题】:Counter HashMap to remove less counter valueCounter HashMap 删除较少的计数器值
【发布时间】:2018-04-23 11:02:25
【问题描述】:

是否有任何 HashMap 实现有界并在达到限制时删除最小映射值?

例如,

    int size = 3;
    Map<Object, Integer> map = new HashMap<>(size);
    Object[] objects = {'a', 'a', 'a', 'b', 'c', 'c', 'd'};
    for (Object o : objects) {
        if (map.containsKey(o)) {
            map.put(o, map.get(o) + 1);
        } else {
            map.put(o, 1);
        }
    }

在此操作之后,我将拥有地图:

{{a,3},{c,2},{d,1}}

顺序不一定要保留,注意 b 被删除了,因为当 d 被插入时,映射将是满的,并且具有最小值的条目被删除。

【问题讨论】:

  • 你能给我们举个例子并澄清一下吗?请注意,地图没有排序,所以到底想要什么?
  • 刚刚更新问题
  • 所以它也可以是{{a,3},{b,1},{d,1}} 注意{b,1} 否?
  • 如果添加{e,0} 会被删除{d,1}{e,0} 吗?
  • 这基本上是一个带有边界的发生计数器。因此,较新的条目将始终具有 1 的值,这始终使最后一个条目可能会被删除。 如果您想在插入前删除地图中的最小值,请说明。我看到的问题是,一旦每个键的值 > 1。keySet 永远不会改变,因为最小值始终是最新添加的。

标签: java collections hashmap


【解决方案1】:

您可以使用LinkedHashMap 来实现此目的。通常你会将它用于 LRU 缓存,并且实现不是很有效,但这是一个 Map,它允许你限制里面的条目数量。

new LinkedHashMap<Object, Integer>() {
    @Override
    protected boolean removeEldestEntry(Map.Entry<Object, Integer> eldest) {
        // Remove one of smallest values when going over 100
        if(size() > 100) {
            Optional<Map.Entry<Object, Integer>> min = this.entrySet().stream()
                        .min(Comparator.comparing(Map.Entry::getValue));

            min.ifPresent((k) -> remove(k.getKey()));
        }
        return false; // Always return false, so map doesn't remove entries
    }
};

【讨论】:

  • @AxelH 如果您返回true,它将删除最旧的条目(即头部)。这就是我们返回false的原因。
  • 对不起,我混淆了我的条件。我的意思是您可以删除最后添加的条目,因为这将在插入后执行。这可能需要将最后插入的项目存储在变量中(覆盖putputAll)以确保不选择它。 (如果行为不是预期的)
  • @AxelH 的想法是删除具有最小值的键。不是添加的最后一个或第一个条目。好吧,在上面的代码中,只有在插入新条目后,大小才会溢出,因此它的最大值为1,但这不是我在这里提出的优化(出于可读性目的)。跨度>
  • 解决方案运行良好,但任何 put 操作都会花费我们 O(n),这是无法承受的。
  • @Aladdin 您是否声称您的其他代码非常优化以至于这会显示为性能热点?考虑到您显示的代码,我很难相信这一点。
【解决方案2】:

这听起来很不常见,我认为您找不到这样的实现。根据您的用例,您可能会使用队列。

【讨论】:

    【解决方案3】:

    我不知道仅包含最近添加的频率的频率表。 MRU 表 将是关键字。你应该看看 guava 和其他库。

    自己动手:

    for (Object o : objects) {
        map.merge(o, 1, Integer::sum); // Update frequency
        if (map.size() > maxSize) {
           map.remove(map.entrySet().stream()
               .sorted(Comparators.compare(Map.Entry<Object, Integer>::getValue))
               .map(Map.Entry<Object, Integer>::getKey)
               .findFirst()
               .get());
        }
    }
    

    这接受 1 的溢出(在非并发上下文中),遍历所有条目(慢)并删除最小值的键。

    【讨论】:

    • Comparators.compare(Map.Entry&lt;Object, Integer&gt;::getValue) 无法编译,它应该是Comparator.comparing(Map.Entry&lt;Object, Integer&gt;::getValue),但您可以使用更简单的Map.Entry.comparingByValue() 来代替。通常,很少需要使用方法引用指定显式类型,例如.map(Map.Entry::getKey) 工作正常。
    • @Holger 当然,我把它留给别人回答要好得多
    【解决方案4】:

    您可以创建自己的实现地图

    class MyHashMap<K> extends HashMap<K, Integer> {
    
    private int maxSize;
    
    public MyHashMap(int maxSize) {
        this.maxSize = maxSize;
    }
    
    @Override
    public Integer put(K key, Integer value) {
        Integer v = super.put(key, value);
    
        if (maxSize < size()) {
            entrySet().stream()
                      .filter(entry -> !entry.getKey().equals(key))
                      .min(Comparator.comparing(Map.Entry::getValue))
                      .ifPresent(entry -> remove(entry.getKey()));
        }
    
        return v;
    }
    

    【讨论】:

      【解决方案5】:

      HashMap 是一个 Map,其意图是 map 一个带有值的键。密钥是唯一的。

      例如,您可以将Integer roll 映射到Student student。为此,您可以拥有一个 HashMap

      Map &lt;Integer, Student&gt; studentMap = new HashMap &lt;&gt;()

      在您的情况下,您需要定义一个 maxSize 容器。根据您的首选参数(访问最少或最旧的对象),您可以删除该项目,以防当前大小等于定义的 maxSize 并且您需要插入一个新项目。这更像是一个队列。你可以通过封装一个数组来实现自己的队列,或者LinedList、ArrayList等(根据你的实际需求)。

      仅仅因为 HashMap 可以接受 2 个通用参数,所以强制它作为问题的解决方案并不是一个好主意。选择正确的数据结构,并可以使用它来创建您的自定义数据结构来解决问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-03-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多