32

给定一组可能重复的对象,我希望最终得到每个对象的出现次数。我通过初始化一个空的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 语言特定功能持开放态度。

4

12 回答 12

20

查看Guava 的 Multiset。几乎正是您正在寻找的东西。

不幸的是,它没有 addAll(Iterable iterable) 函数,但是在您的集合上调用 add(E e) 的简单循环就足够简单了。

编辑

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

于 2013-01-10T14:36:10.207 回答
20

现在让我们尝试一些 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()));
}

我认为这段代码更优雅。

于 2014-08-26T18:00:45.187 回答
13

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

Map<String, Long> map = a.getSomeStringList()
            .stream()
            .collect(Collectors.groupingBy(
                    Function.identity(),
                    Collectors.counting())
            );

任何错误,只是评论。

于 2017-01-23T12:57:46.923 回答
7

查看这篇文章如何计算 List 中元素的出现次数。要计算出现次数,您可以使用int occurrences = Collections.frequency(list, obj);.

于 2013-01-10T14:39:12.180 回答
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]++;
        }
    }
}
于 2014-04-08T21:53:01.630 回答
2

对于 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;
}
于 2013-01-10T14:36:33.377 回答
1

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

对于整数值:

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
于 2018-05-13T12:51:21.550 回答
1

我很惊讶没有人提供这种简单易读的解决方案。您可以只使用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.

于 2021-03-30T21:11:02.737 回答
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%,再次 - 取决于集合中元素的数量)。

于 2013-01-10T15:27:00.390 回答
0

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

于 2018-08-10T10:59:27.107 回答
0

您可以使用Eclipse Collections中的一个Bag

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作为参数,并返回一个MutableBagoccurrencesOfon方法MutableBag返回 an int,它是元素出现的次数。与 a 不同Map,如果 a 不包含元素,Bag则不会返回。null相反,该occurrencesOf方法将返回0.

MutableBag是 a Collection,所以它有一个addAll将 aCollection作为参数的方法。

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"));

HashBagEclipse Collections 中的实现由 支持ObjectIntHashMap,因此int计数没有装箱。此处Bag有一个博客,其中包含有关Eclipse Collections 中的类型的更多信息。

注意:我是 Eclipse Collections 的提交者

于 2021-07-31T09:29:38.557 回答
-2

Java 是一种冗长的语言,我认为没有更简单的方法可以实现这一点,除非使用 3rd-party 库或等待 Java 8 的 Lambda 表达式。

于 2013-01-10T14:37:51.787 回答