Подтвердить что ты не робот

Элегантный способ подсчета вхождений в коллекцию java

Учитывая коллекцию объектов с возможными дубликатами, я бы хотел получить число вхождений на объект. Я делаю это, инициализируя пустой 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 функции, которая позволяет использовать более короткий код.

4b9b3361

Ответ 1

Отъезд Guava Multiset. В значительной степени именно то, что вы ищете.

К сожалению, у него нет функции addAll (Iterable iterable), но простой цикл над вашим вызовом add (E e) коллекции достаточно прост.

ИЗМЕНИТЬ

Моя ошибка, у нее действительно есть метод addAll - как и должно быть, поскольку он реализует Collection.

Ответ 2

Теперь попробуйте код 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()));
}

Мне кажется, этот код более элегантный

Ответ 3

Я знаю, что это старый вопрос, но я нашел более элегантный способ подсчета этих голосов в Java 8, надеюсь, вам понравится.

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

Любая ошибка, просто комментарий.

Ответ 5

В 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]++;
        }
    }
}

Ответ 6

Это не так много для 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;
 }

Ответ 7

В качестве ответа на обсуждение с @NimChimpsky вот альтернатива и быстрее - которую я пытаюсь доказать - метод подсчета, который использует отсортированную коллекцию. В зависимости от количества элементов и "sortFactor" (см. Код) разница в скорости изменяется, но для больших объемов объектов в среде Run (не отладки) мой метод имеет увеличение скорости на 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 run мой метод довольно медленный, чем исходный (10-20%, опять же - в зависимости от количества элементов в коллекции).

Ответ 8

Пожалуйста, обратитесь к приведенному ниже решению, чтобы подсчитать каждый элемент в коллекциях.

Значение Integer:

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

Ответ 9

Существует метод в commons-collections: CollectionUtils.getCardinalityMap который делает именно это.

Ответ 10

Java - это подробный язык, я не думаю, что есть более простой способ достичь этого, если не использовать стороннюю библиотеку или ждать выражения Java Lambda Expression.