给定一个可能重复的对象集合,我想最终得到每个对象的出现次数。我这样做是通过初始化一个空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语言特定功能持开放态度,允许更短的代码。
答案 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
作为参数,并返回 MutableBag
。 occurrencesOf
上的方法 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。