【问题标题】:how can i get the number of same-value-more-than-once occurrences for a HashMap in Java?如何在 Java 中获取 HashMap 的多次相同值多次出现的次数?
【发布时间】:2012-11-07 12:14:50
【问题描述】:

是否有任何简单的方法来获取存在相同值的键? 或者更重要的是,我怎样才能得到相同值多次出现的次数?

考虑哈希图:

1->A
2->A
3->A
4->B
5->C
6->D
7->D

这里相同值多次发生 3 次(A 两次,D 一次)。那(3)是我想要的回报。

我可以通过 keyset/map.values() 列表遍历 hashmap,但这样做似乎很麻烦。有什么建议或解决方案吗?

编辑: 我的背景是,我正在开发一个时间表生成器。时隙的数据结构是

{String day-hour, HashMap<String,Event> Rooms}

在一天的时间里,一些 Event-s 被分配在房间地图上。在检查解决方案的适用性时,我需要知道是否在同一小时内为一名员工分配了多个事件。因此,我想通过值 Event.getStaff() 检查房间地图中有多少违规行为。

编辑: 值在这里是对象,我不想计算相同对象的出现次数,而是对象的字段。 EVENT 对象有一个字段 staff,我需要计算人员的多次出现。

【问题讨论】:

    标签: java data-structures map hashmap


    【解决方案1】:

    我可以通过 keyset/map.values() 列表遍历 hashmap,但这样做似乎很麻烦。

    嗯,它效率低下,但你无能为力,因为没有某种多映射来存储值到键的反向映射。

    如果你使用Guava

    Multiset<String> counts = HashMultiSet.create(map.values());
    for (Multiset.Entry<String> entry : counts.entrySet) {
      if (entry.getCount() > 1) {
        System.out.println(entry.getElement() + ": " + entry.getCount());
      }
    }
    

    【讨论】:

    • 上述代码的问题是丢失了key/value的关系,所以OP不会得到重复值的key。
    • 我会看到对 count > 1 的多重集进行过滤,然后在过滤集中执行Maps.filterKeys
    • @JohnB:好吧,OP 的“重要”要求是获取相同值出现的次数。尽管第一句话,关键部分似乎并不那么重要。
    • 在我看来,对于简单的任务最好避免使用外部库,否则您最终将在项目中跟踪一百万个 jar。当然,除非这个 lib 让您的生活更轻松,而不仅仅是一种方式
    • @MånsRolandiDanielsson:根据我的经验,Guava 在几乎每个 Java 项目中都很有用。这些天,我无法想象没有它在 Java 中写任何重要的东西。
    【解决方案2】:

    这是我认为的好方法:

    int freq = Collections.frequency(map.values(), "A");
    

    为您的示例返回“3”。干杯!

    编辑:对不起,我在第一次尝试时误解了这个问题,这应该可以解决问题:

    int k = 0;
    Set<String> set = new HashSet<String>(map.values());
    for (String s : set) {
       int i = Collections.frequency(map.values(), s);
       k += i > 1 ? i - 1 : 0;
    }
    

    尽管如此,您仍然无法检索实际密钥。但这不是最重要的,对吧?

    【讨论】:

    • OP 必须对值列表进行硬编码,这不会给出具有重复值的键。
    • 这只是获取重复值的计数,它既不提供键也不提供值。
    • 你又是对的,但根据原始问题,这不是这个问题中最重要的事情。
    • 我的错,没有看到第二行。
    • 请注意,这基本上是一个 O(N^2) 解决方案。对于重复很少的大型地图,它可能会很耗时。当然,这可能完全没问题 - OP 只需要意识到这一点。
    【解决方案3】:

    怎么样(扩展乔恩的回答)

    Multiset<V> counts = HashMultiSet.create(map.values());
    Predicate<Map.Entry<K,V>> pred = new Predicate<Map.Entry<K,V>>(){
       public boolean apply(Map.Entry<K,V> entry){
           return counts.count(entry.getValue()) > 1;
       }
    }
    Map<K,V> result = Maps.filterEntries(map, pred);
    

    这将产生一个映射,其中每个键都映射到一个重复的值。

    这个答案只需要解决问题的第一部分(“不太重要的部分”),以获取具有重复值的键。

    【讨论】:

    • 谢谢!但我从未使用过 Guava 或 MultiSet,因为我是 java 的初学者。如果其他方式失败/打扰,那么我会尝试番石榴。顺便说一句,这同样重要;我被困在计数上,并认为在某些时候我可能也需要钥匙。现在我真的做到了。
    【解决方案4】:

    我不知道上下文,但是如果您使用多图会怎样:

    Map<String, List<Integer>>
    

    这样你的地图就应该是这样的:

    A->1, 2, 3
    B->4
    C->5
    D->6, 7
    

    【讨论】:

    【解决方案5】:

    你可以围绕 (Hash)Map 创建一个包装类,装饰 put()-remove() 方法来维护另一个 map,其中原始 Map 的值是键,值是发生。然后你只需要实现方法来查询...

    但是,这相当棘手!您必须小心不要再链接到不在地图中的对象...这可能会导致内存泄漏!

    此外,还必须考虑空值容差...

    public static class MyCountingMap<K,V> implements Map<K,V> {
      private final Map<K,V> internalMap;
      //hashmap tolerates null as a key!
      private final Map<V,Long> counterMap = new HashMap<V, Long>();
    
      public MyCountingMap(Map<K, V> internalMap) {
        super();
        this.internalMap = internalMap;
      }
    
    
      @Override
      public V put(K key, V value) {
        boolean containedOriginally = internalMap.containsKey(key);
    
        V origValue = internalMap.put(key, value);
    
        updateCounterPut(containedOriginally, origValue, value);
    
        return origValue;
      }
    
      @Override
      public void putAll(Map<? extends K, ? extends V> m) {
        //now this is the awkward part...
        //this whole thing could be done through a loop and the put() method, 
        //but I'd prefer to use the original implementation...
        for(Map.Entry<? extends K, ? extends V> entry :m.entrySet()) {
          boolean containedOriginally = internalMap.containsKey(entry.getKey());
          V origValue = internalMap.get(entry.getKey());
          updateCounterPut(containedOriginally, origValue, entry.getValue());
        }
    
    
        internalMap.putAll(m);
      }
    
      // this method updates the counter
      private void updateCounterPut(boolean containedOriginally, V origValue, V newValue) {
        //if it was in the map, and it is different than the original, decrement 
        if(containedOriginally && isDifferent(origValue, newValue)) 
        {
          decrement(origValue);
        }
    
        //if it was NOT in the map, or the value differs
        if(!containedOriginally || isDifferent(origValue, newValue)) {
          increment(newValue);
        }
      }
    
      // nothing special, just nicer to extract this to a method. Checks if the two values are the same or not.
      private static boolean isDifferent(Object origValue, Object newValue) {
        return ((origValue==null && newValue!=null) || !(origValue!=null && origValue.equals(newValue)));
      }
    
      //this method returns the counter value for the map value
      public Long getValueCount(V value) {
        return counterMap.get(value);
      }
    
      @Override
      public V remove(Object key) {
        V toReturn = internalMap.remove(key);
        if(toReturn!=null) {
          decrement(toReturn);
        }
        return toReturn;
      }
    
      private void increment(V value) {
        Long count = counterMap.get(value);
        if(count == null) {
          count = 0L;
        }
        counterMap.put(value, count+1);
      }
    
      private void decrement(V value) {
        Long count = counterMap.get(value);
        if(count == null) {
          count = 0L;
        }
    
        //last! Have to remove reference to prevent memory leak!!
        if(count == 1L) {
          counterMap.remove(value);
        } else {
          counterMap.put(value, count-1);
        }
    
      }
      //... boring wrapper methods ...
      public void clear() { internalMap.clear(); }
      public boolean containsKey(Object key) { return internalMap.containsKey(key); }
      public boolean containsValue(Object value) { return internalMap.containsValue(value);   }
      public Set<Entry<K, V>> entrySet() { return internalMap.entrySet();    }
      public V get(Object key) { return internalMap.get(key); }
      public boolean isEmpty() { return internalMap.isEmpty(); } 
      public Set<K> keySet() { return internalMap.keySet(); }
      public int size() { return internalMap.size(); }
      public Collection<V> values() { return internalMap.values(); }
    }
    

    【讨论】:

      猜你喜欢
      • 2013-12-09
      • 1970-01-01
      • 1970-01-01
      • 2022-11-28
      • 1970-01-01
      • 2011-08-31
      • 2019-01-30
      • 1970-01-01
      • 2017-12-28
      相关资源
      最近更新 更多