Элегантный способ подсчета вхождений в коллекцию 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 функции, которая позволяет использовать более короткий код.
Ответы
Ответ 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())
);
Любая ошибка, просто комментарий.
Ответ 4
Проверьте эту статью Как подсчитать количество вхождений элемента в списке. Для подсчета событий вы можете использовать int occurrences = Collections.frequency(list, obj);
.
Ответ 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.