计算java集合中出现次数的优雅方法

时间:2013-01-10 14:27:04

标签: java collections

给定一个可能重复的对象集合,我想最终得到每个对象的出现次数。我这样做是通过初始化一个空Map,然后遍历Collection并将对象映射到它的计数(每次地图已经包含对象时递增计数)。

 public Map<Object, Integer> countOccurrences(Collection<Object> list){
      Map<Object, Integer> occurrenceMap = new HashMap<Object, Integer>();

      for(Object obj: list){
           Integer numOccurrence = occurrenceMap.get(obj);
           if(numOccurrence == null){
                //first count
                occurrenceMap.put(obj, 1);
           } else{
                occurrenceMap.put(obj, numOccurrence++);
           }
      }
      return occurrenceMap;
 }

对于计算出现次数的简单逻辑,这看起来过于冗长。有更优雅/更短的方式吗?我对一种完全不同的算法或java语言特定功能持开放态度,允许更短的代码。

12 个答案:

答案 0 :(得分:20)

结帐Guava's Multiset。几乎就是你要找的东西。

不幸的是它没有addAll(Iterable iterable)函数,但是调用add(E e)的集合上的一个简单循环很容易。

修改

我的错误,它确实有一个addAll方法 - 因为它必须,因为它实现了Collection。

答案 1 :(得分:19)

现在让我们尝试一些Java 8代码:

static public Map<String,Integer> toMap(List<String> lst){

    return lst.stream()
        .collect(HashMap<String,Integer>::new,
                 (map,str) ->{
                     if(!map.containsKey(str)){
                         map.put(str,1);
                     }else{
                         map.put(str,map.get(str)+1);
                     }
                 },
                 HashMap<String,Integer>::putAll);
}
static public Map<String,Integer> toMap(List<String> lst){
    return lst.stream().collect(Collectors.groupingBy(s -> s, 
                                  Collectors.counting()));
}

我认为这段代码更优雅

答案 2 :(得分:7)

我知道这是一个老问题,但我在Java 8中找到了一种更优雅的方式来计算这些投票,希望你喜欢它。

{ error: { code: 'InvalidImageSize', message: 'Image size is too small or too big.' } }

任何错误,只需评论。

答案 3 :(得分:6)

查看此文章How to count the number of occurrences of an element in a List。要计算出现次数,您可以使用int occurrences = Collections.frequency(list, obj);

答案 4 :(得分:3)

这里有一篇关于java中的计数器的好文章:http://www.programcreek.com/2013/10/efficient-counter-in-java/它更注重效率而不是优雅。

获胜者是:

HashMap<String, int[]> intCounter = new HashMap<String, int[]>();
for (int i = 0; i < NUM_ITERATIONS; i++) {
    for (String a : sArr) {
        int[] valueWrapper = intCounter.get(a);

        if (valueWrapper == null) {
            intCounter.put(a, new int[] { 1 });
        } else {
            valueWrapper[0]++;
        }
    }
}

答案 5 :(得分:1)

对于Java来说,这并不简单;)您可以使用TObjectIntHashMap

public <T> TObjectIntHashMap<T> countOccurrences(Iterable<T> list){
    TObjectIntHashMap<T> counts = new TObjectIntHashMap<T>();
    for(T obj: list) counts.adjustOrPut(obj, 1, 1);
    return counts;
 }

答案 6 :(得分:0)

作为对@NimChimpsky讨论的回应,这里有一个替代方案,更快 - 我试图证明 - 计数方法使用排序集合。根据元素的数量和“sortFactor”(参见代码),速度差异会有所不同,但对于运行环境(而不是调试)中的大量对象,我的方法相对于默认方法的速度提高了20-30%。 这是两种方法的简单测试类。

public class EltCountTest {

    final static int N_ELTS = 10000;

    static final class SampleCountedObject implements Comparable<SampleCountedObject>
    {
        int value = 0;

        public SampleCountedObject(int value) {
            super();
            this.value = value;
        }

        @Override
        public int compareTo(SampleCountedObject o) {
            return (value == o.value)? 0:(value > o.value)?1:-1; // just *a* sort
        }

        @Override
        public int hashCode() {
            return value;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof SampleCountedObject) {
                return value == ((SampleCountedObject)obj).value;
            }
            return false;
        }

        @Override
        public String toString() {
            return "SampleCountedObject("+value+")";
        }
    }

    /**
     * * @param args
     */
    public static void main(String[] args) {
        int tries = 10000;
        int sortFactor = 10;
        Map<SampleCountedObject, Integer> map1 = null;
        Map<SampleCountedObject, Integer> map2 = null;

        ArrayList<SampleCountedObject> objList = new ArrayList<EltCountTest.SampleCountedObject>(N_ELTS);

        for (int i =0, max=N_ELTS/sortFactor; i<max; i++){
            for (int j = 0; j<sortFactor; j++) {
                objList.add(new SampleCountedObject(i));
            }
        }

        long timestart = System.nanoTime();
        for (int a=0; a< tries; a++) {
            map1 = method1(objList);
        }
        System.out.println();
        long timeend1 = System.nanoTime();
        System.out.println();

        for (int a=0; a< tries; a++) {
            map2 = metod2(objList);
        }
        long timeend2 = System.nanoTime();
        System.out.println();


        long t1 = timeend1-timestart;
        long t2 = timeend2-timeend1;
        System.out.println("\n        org count method=["+t1+"]\nsorted collection method=["+t2+"]"+
                 "\ndiff=["+Math.abs(t1-t2)+"] percent=["+(100d*t2/t1)+"]");

        for (SampleCountedObject obj: objList) {
            int val1 = map1.get(obj);
            int val2 = map2.get(obj);
            if (val1 != val2) {
                throw new RuntimeException("val1 != val2 for obj "+obj);
            }
        }
        System.out.println("veryfy OK");

    }

    private static Map<SampleCountedObject, Integer> method1(ArrayList<SampleCountedObject> objList) {
        Map<SampleCountedObject, Integer> occurenceMap = new HashMap<SampleCountedObject, Integer>();

        for(SampleCountedObject obj: objList){
             Integer numOccurrence = occurenceMap.get(obj);
             if(numOccurrence == null){
                 occurenceMap.put(obj, 1);
             } else {
                 occurenceMap.put(obj, ++numOccurrence);
             }
        }
        return occurenceMap;
    }

    private static Map<SampleCountedObject, Integer> metod2(ArrayList<SampleCountedObject> objList) {
        Map<SampleCountedObject, Integer> occurenceMap = new HashMap<SampleCountedObject, Integer>();
        int count = 0;
        Collections.sort(objList);
        SampleCountedObject prevObj = objList.get(0);

        for(SampleCountedObject obj: objList){
            if (!obj.equals(prevObj)) {
                occurenceMap.put(prevObj, count);
                count = 1;
            } else {
                count ++;
            }
            prevObj = obj;
        }
        occurenceMap.put(prevObj, count);
        return occurenceMap;
    }
}

请注意,我还会验证结果是否相同,并在打印测试结果后执行此操作。

我发现有趣的是,在Debug运行中,我的方法比原始方法慢得多(10-20%,再次 - 取决于集合中的元素数量)。

答案 7 :(得分:0)

请参考以下解决方案来计算集合中的每个元素。

对于整数值:

List<Integer> list = new ArrayList<Integer>();
    list.add(3);
    list.add(2);
    list.add(5);
    list.add(1);
    list.add(8);
    list.add(0);
    list.add(2);
    list.add(32);
    list.add(72);
    list.add(0);
    list.add(13);
    list.add(32);
    list.add(73);
    list.add(22);
    list.add(73);
    list.add(73);
    list.add(21);
    list.add(73);

    HashSet<Integer> set = new HashSet<>();

    for (int j = 0; j < list.size(); j++) {
        set.add(list.get(j));
    }       

    Iterator<Integer> itr =  set.iterator();
    while(itr.hasNext()){
        int a = itr.next();

        System.out.println(a+ " : "+Collections.frequency(list, a));
    }

输出:

0 : 2

32 : 2

1 : 1

2 : 2

3 : 1

5 : 1

21 : 1

22 : 1

8 : 1

72 : 1

73 : 4

13 : 1

对于字符串值:

List<String> stringList =  new ArrayList<>();
    stringList.add("ABC");
    stringList.add("GHI");
    stringList.add("ABC");
    stringList.add("DEF");
    stringList.add("ABC");
    stringList.add("GHI");

    HashSet<String> setString = new HashSet<>();

    for (int j = 0; j < stringList.size(); j++) {
        setString.add(stringList.get(j));
    }


    Iterator<String> itrString =  setString.iterator();
    while(itrString.hasNext()){
        String a = itrString.next();            

        System.out.println(a+ " :::  "+Collections.frequency(stringList, a));
    }

<强>输出:

ABC :::  3

DEF :::  1

GHI :::  2

答案 8 :(得分:0)

commons-collections中有一种方法:CollectionUtils.getCardinalityMap正是在这样做。

答案 9 :(得分:0)

我很惊讶没有人提供这种简单易读的解决方案。你可以只使用 Map#getOrDefault()

 public Map<Object, Integer> countOccurrences(Collection<Object> list){
      Map<Object, Integer> occurrenceMap = new HashMap<Object, Integer>();
      for(Object obj: list){
          occurrenceMap.put(obj, occurrenceMap.getOrDefault(obj, 0) + 1);
      }
      return occurrenceMap;
 }

它完全解决了您遇到的问题并消除了笨拙的 if..else

答案 10 :(得分:0)

您可以使用 Bag 中的 Eclipse Collections

Iterable<Object> iterable = Arrays.asList("1", "2", "2", "3", "3", "3");
MutableBag<Object> counts = Bags.mutable.withAll(iterable);

Assertions.assertEquals(1, counts.occurrencesOf("1"));
Assertions.assertEquals(2, counts.occurrencesOf("2"));
Assertions.assertEquals(3, counts.occurrencesOf("3"));
Assertions.assertEquals(0, counts.occurrencesOf("4"));

withAll 接口上的 MutableBagFactory 方法以 Iterable 作为参数,并返回 MutableBagoccurrencesOf 上的方法 MutableBag 返回一个 int,它是元素出现的次数。与 Map 不同,如果 Bag 不包含元素,则它不会返回 null。相反,occurrencesOf 方法将返回 0

MutableBag 是一个 Collection,因此它有一个 addAll 方法,该方法将 Collection 作为参数。

counts.addAll(Arrays.asList("4", "4", "4", "4"));
Assertions.assertEquals(4, counts.occurrencesOf("4"));

MutableBag 也有一个 addAllIterable 方法,它采用 Iterable

Stream<Object> stream = Stream.of("1", "2", "3", "4");
counts.addAllIterable(stream::iterator);

Assertions.assertEquals(2, counts.occurrencesOf("1"));
Assertions.assertEquals(3, counts.occurrencesOf("2"));
Assertions.assertEquals(4, counts.occurrencesOf("3"));
Assertions.assertEquals(5, counts.occurrencesOf("4"));

Eclipse Collections 中的 HashBag 实现由 ObjectIntHashMap 支持,因此 int 计数未装箱。在位于 here 的 Eclipse 集合中有一个包含 Bag 类型的更多信息的博客。

注意:我是 Eclipse Collections 的提交者

答案 11 :(得分:-2)

Java是一种冗长的语言,我不认为有更简单的方法可以实现,除非使用第三方库或等待Java 8的Lambda Expression。