234

I have an ArrayList, a Collection class of Java, as follows:

ArrayList<String> animals = new ArrayList<String>();
animals.add("bat");
animals.add("owl");
animals.add("bat");
animals.add("bat");

As you can see, the animals ArrayList consists of 3 bat elements and one owl element. I was wondering if there is any API in the Collection framework that returns the number of bat occurrences or if there is another way to determine the number of occurrences.

I found that Google's Collection Multiset does have an API that returns the total number of occurrences of an element. But that is compatible only with JDK 1.5. Our product is currently in JDK 1.6, so I cannot use it.

Stefan van den Akker
  • 6,661
  • 7
  • 48
  • 63
MM.
  • 2,535
  • 2
  • 17
  • 13
  • That's one of the reasons why you should program to an interface rather than an implementation. If you happen to find the right collection you'll need to change the type to use that collection. I'll post an answer on this. – OscarRyz Feb 03 '09 at 22:20

25 Answers25

423

I'm pretty sure the static frequency-method in Collections would come in handy here:

int occurrences = Collections.frequency(animals, "bat");

That's how I'd do it anyway. I'm pretty sure this is jdk 1.6 straight up.

Lars Andren
  • 8,601
  • 7
  • 41
  • 56
  • 1
    Always prefer Api from JRE, that add another dependency to the project. And don't Reinventing the wheel !! – Fernando. Jun 04 '18 at 08:07
  • 5
    It was introduced in JDK 5 (although no one uses a version before that so it doesn't matter) https://docs.oracle.com/javase/8/docs/technotes/guides/collections/changes5.html – Minion Jim Mar 02 '19 at 15:49
158

In Java 8:

Map<String, Long> counts =
    list.stream().collect(Collectors.groupingBy(e -> e, Collectors.counting()));
Vitalii Fedorenko
  • 110,878
  • 29
  • 149
  • 111
  • 7
    Using Function.identity() (with static import) instead of e -> e makes it a little nicer to read. – Kuchi Oct 11 '15 at 23:31
  • 13
    Why is this better than `Collections.frequency()`? It seems less readable. – rozina Apr 12 '17 at 13:19
  • This is not what was asked for. It does more work than necessary. – Alex Worden Oct 04 '17 at 03:09
  • 15
    This may do more than what was asked for, but it does precisely what I wanted (get a map of distinct elements in a list to their counts). Furthermore, this question was the top result in Google when I searched. – KJP Oct 29 '17 at 03:30
  • 2
    @rozina You get all counts in one pass. – atoMerz Apr 22 '18 at 07:17
  • @rozina if you already have a Stream you don't have to collect and then apply Collections.frequency but get everything in one pass. – Andrea Bergonzo May 11 '18 at 18:21
28

Alternative Java 8 solution using Streams:

long count = animals.stream().filter(animal -> "bat".equals(animal)).count();
Cristina_eGold
  • 1,463
  • 2
  • 22
  • 41
24

This shows, why it is important to "Refer to objects by their interfaces" as described in Effective Java book.

If you code to the implementation and use ArrayList in let's say, 50 places in your code, when you find a good "List" implementation that count the items, you will have to change all those 50 places, and probably you'll have to break your code ( if it is only used by you there is not a big deal, but if it is used by someone else uses, you'll break their code too)

By programming to the interface you can let those 50 places unchanged and replace the implementation from ArrayList to "CountItemsList" (for instance ) or some other class.

Below is a very basic sample on how this could be written. This is only a sample, a production ready List would be much more complicated.

import java.util.*;

public class CountItemsList<E> extends ArrayList<E> { 

    // This is private. It is not visible from outside.
    private Map<E,Integer> count = new HashMap<E,Integer>();

    // There are several entry points to this class
    // this is just to show one of them.
    public boolean add( E element  ) { 
        if( !count.containsKey( element ) ){
            count.put( element, 1 );
        } else { 
            count.put( element, count.get( element ) + 1 );
        }
        return super.add( element );
    }

    // This method belongs to CountItemList interface ( or class ) 
    // to used you have to cast.
    public int getCount( E element ) { 
        if( ! count.containsKey( element ) ) {
            return 0;
        }
        return count.get( element );
    }

    public static void main( String [] args ) { 
        List<String> animals = new CountItemsList<String>();
        animals.add("bat");
        animals.add("owl");
        animals.add("bat");
        animals.add("bat");

        System.out.println( (( CountItemsList<String> )animals).getCount( "bat" ));
    }
}

OO principles applied here: inheritance, polymorphism, abstraction, encapsulation.

OscarRyz
  • 196,001
  • 113
  • 385
  • 569
  • 13
    Well one should always try composition rather than inheritance. Your implementation is now stuck to ArrayList when there may be times you want a LinkedList or other. Your example should have taken another LIst in its constructor/factory and returned a wrapper. – mP. Feb 05 '09 at 02:59
  • I completely agree with you. The reason I used inheritance in the sample is because it is a lot more easier to show a running example using inheritance than composition ( having to implement the List interface ). Inheritance creates the highest coupling. – OscarRyz Feb 05 '09 at 18:15
  • 3
    But by naming it CountItemsList you imply that it does two things, it counts items and it is a list. I think just one single responsibility for that class, counting the occurrences, would be as simple and you wouldn't need to implement the List interface. – flob Feb 12 '15 at 10:23
14

Sorry there's no simple method call that can do it. All you'd need to do though is create a map and count frequency with it.

HashMap<String,int> frequencymap = new HashMap<String,int>();
foreach(String a in animals) {
  if(frequencymap.containsKey(a)) {
    frequencymap.put(a, frequencymap.get(a)+1);
  }
  else{ frequencymap.put(a, 1); }
}
Ray Hidayat
  • 16,055
  • 4
  • 37
  • 43
  • 2
    This is really not a scalable solution - imagine MM's data set had hundreds and thousands of entries and MM wanted to know the frequences for each and every entry. This could potentially be a very costly task - especially when there are much better ways to do it. – mP. Feb 03 '09 at 03:37
  • Yes, it might not be a good solution, doesn't mean its wrong. – Adeel Ansari Feb 03 '09 at 03:44
  • He just wants the number of 'bat' occurrences. Just iterate once over the original ArrayList and increment a counter whenever you see 'bat'. – Frank Feb 03 '09 at 04:29
  • 3
    @dehmann, I don't think he literally wants the number of bat occurrences in a 4-element collection, I think that was just sample data so we'd understand better :-). – paxdiablo Feb 03 '09 at 04:33
  • @Vinegar Just because it works doesnt mean its the proper way to do things. We could scan all the rows in a table and manually find the bat record but we dont we create the appropriate query. – mP. Feb 03 '09 at 06:01
  • 2
    @Vinegar 2/2. Programming is about doing things properly now, so we dont cause headaches or a bad experience for someone else be it a user or another coder in the future. PS: The more code your write the more chance there is something can go wrong. – mP. Feb 03 '09 at 06:02
  • I agree, thats why I don't upvote those clumsy solutions. But neither I downvote those because they give the right result. Since, we can't downvote twice to a wrong answer. And IMO, we must distinguish wrong from inefficient. – Adeel Ansari Feb 04 '09 at 02:08
  • 2
    @mP: Please explain why this is not a scalable solution. Ray Hidayat is building a frequency count for each token so that each token can then be looked up. What is a better solution? – stackoverflowuser2010 Jun 13 '13 at 04:35
  • 1
    This looks like C#, but the question is tagged [tag:java]. – MC Emperor Jun 16 '19 at 12:06
11

There is no native method in Java to do that for you. However, you can use IterableUtils#countMatches() from Apache Commons-Collections to do it for you.

Kyle
  • 893
  • 1
  • 9
  • 20
Kevin
  • 30,111
  • 9
  • 76
  • 83
  • 1
    Refer to my answer below - the correct answer is to use a structure that supports the counting idea from the begining rather than counting entries from start to end each time a query is made. – mP. Feb 03 '09 at 03:38
  • @mP So you just downvote everyone who has a different opinion than you? What if he can't use a Bag for some reason or is stuck with using one of the native Collections? – Kevin Feb 03 '09 at 03:41
  • -1 for being a sore loser :-) I think mP downvoted you because your solution costs time every time you want a result. A bag cost a little time only at insertion. Like databases, these sort of structures tend to be "more read than write" so it makes sense to use the low cost option. – paxdiablo Feb 03 '09 at 03:44
  • And it appears your answer also requires non-native stuff, so your comment seems a little strange. – paxdiablo Feb 03 '09 at 03:45
  • Thanks to both of you guys. I believe one of the two approaches or both of them might work. I will give it a try tomorrow. – MM. Feb 03 '09 at 03:45
  • @Pax I can imagine a use case where he is not the owner/creator of the Collection but merely a user. In such a case, he wouldn't be able to use a Bag. Anyways, the appropriate answer is: Use Commons Collections :) – Kevin Feb 03 '09 at 03:48
  • Do you guys think this is wrong. It might be inefficient doesn't mean its wrong. For inefficiency don't vote, simple. Downvote means, at least to me, something totally wrong. Its bloated, I agree, but it will yield a right result. – Adeel Ansari Feb 03 '09 at 03:48
  • What do you think of this algo of Fibonacci. Its in Groovy BTW.... "def fib(x) {x <= 1 ? x : fib(x-1) + fib(x-2)}"..... Its terribly inefficient, but it will yield the right result, and if what you want is less 20 it is good to go. Doesn't deserve a negative. – Adeel Ansari Feb 03 '09 at 03:49
  • @Vinegar, the actual help text is "not helpful" rather than wrong. Having said that, I didn't downvote this one (see the smiley on my comment) because it's not "not helpful". I didn't upvote it either since I believe mP's is more helpful and I restrict myself to upvoting the one best answer IMNSHO. – paxdiablo Feb 03 '09 at 03:50
  • @Vinegar, bubble sorts are also inefficient but yield the right result. Let's see how many rep points you'd get here on SO if you answered a sort question with "bubble sort" :-). Granted, it's fine for sorting small data sets but it also won't scale. – paxdiablo Feb 03 '09 at 03:55
  • My point being, bags are a better answer to the specific question. – paxdiablo Feb 03 '09 at 03:56
  • I tend to agree, Pax. Neither I am very inclined towards this solution. I voted it +1 to make it zero. And for bubble sort example, I wouldn't expect any point in that case, neither +ve nor -ve. :) – Adeel Ansari Feb 03 '09 at 04:15
  • And if one take a look of this answer from a slightly different angle, I assume Kevin also looked it in that way. Common Collection would work as substitute of Google Collection, since the original poster mentioned this himself. But still Bags are better. – Adeel Ansari Feb 03 '09 at 04:18
10

Simple Way to find the occurrence of string value in an array using Java 8 features.

public void checkDuplicateOccurance() {
        List<String> duplicateList = new ArrayList<String>();
        duplicateList.add("Cat");
        duplicateList.add("Dog");
        duplicateList.add("Cat");
        duplicateList.add("cow");
        duplicateList.add("Cow");
        duplicateList.add("Goat");          
        Map<String, Long> couterMap = duplicateList.stream().collect(Collectors.groupingBy(e -> e.toString(),Collectors.counting()));
        System.out.println(couterMap);
    }

Output : {Cat=2, Goat=1, Cow=1, cow=1, Dog=1}

You can notice "Cow" and cow are not considered as same string, in case you required it under same count, use .toLowerCase(). Please find the snippet below for the same.

Map<String, Long> couterMap = duplicateList.stream().collect(Collectors.groupingBy(e -> e.toString().toLowerCase(),Collectors.counting()));

Output : {cat=2, cow=2, goat=1, dog=1}

  • nit: because the list is a list of strings, `toString()` is unneccessary. You can just do: `duplicateList.stream().collect(Collectors.groupingBy(e -> e,Collectors.counting()));` – Tad Nov 14 '19 at 12:40
10

To achieve that one can do it in several ways, namely:

Methods that return the number of occurrence of a single element:

Collection Frequency

Collections.frequency(animals, "bat");

Java Stream:

Filter

animals.stream().filter("bat"::equals).count();

Just iteration thought the list

public static long manually(Collection<?> c, Object o){
    int count = 0;
    for(Object e : c)
        if(e.equals(o))
            count++;
    return count;
}

Methods that create a map of frequencies:

Collectors.groupingBy

Map<String, Long> counts = 
       animals.stream()
              .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

merge

Map<String, Long> map = new HashMap<>();
c.forEach(e -> map.merge(e, 1L, Long::sum));

Manually

Map<String, Integer> mp = new HashMap<>();
        animals.forEach(animal -> mp.compute(animal, (k, v) -> (v == null) ? 1 : v + 1));

A running example with all the methods:

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Frequency {

    public static int frequency(Collection<?> c, Object o){
        return Collections.frequency(c, o);
    }

    public static long filter(Collection<?> c, Object o){
        return c.stream().filter(o::equals).count();
    }

    public static long manually(Collection<?> c, Object o){
        int count = 0;
        for(Object e : c)
            if(e.equals(o))
                count++;
        return count;
    }

    public static Map<?, Long> mapGroupBy(Collection<?> c){
        return c.stream()
                .collect(Collectors.groupingBy(Function.identity() , Collectors.counting()));
    }

    public static Map<Object, Long> mapMerge(Collection<?> c){
        Map<Object, Long> map = new HashMap<>();
        c.forEach(e -> map.merge(e, 1L, Long::sum));
        return map;
    }

    public static Map<Object, Long> manualMap(Collection<?> c){
        Map<Object, Long> map = new HashMap<>();
        c.forEach(e -> map.compute(e, (k, v) -> (v == null) ? 1 : v + 1));
        return map;
    }


    public static void main(String[] args){
        List<String> animals = new ArrayList<>();
        animals.add("bat");
        animals.add("owl");
        animals.add("bat");
        animals.add("bat");

        System.out.println(frequency(animals, "bat"));
        System.out.println(filter(animals,"bat"));
        System.out.println(manually(animals,"bat"));
        mapGroupBy(animals).forEach((k, v) -> System.out.println(k + " -> "+v));
        mapMerge(animals).forEach((k, v) -> System.out.println(k + " -> "+v));
        manualMap(animals).forEach((k, v) -> System.out.println(k + " -> "+v));
    }
}

The methods name should have reflected what those methods are doing, however, I used the name to reflect the approach being used instead (given that in the current context it is okey).

dreamcrash
  • 47,137
  • 25
  • 94
  • 117
8

Actually, Collections class has a static method called : frequency(Collection c, Object o) which returns the number of occurrences of the element you are searching for, by the way, this will work perfectly for you:

ArrayList<String> animals = new ArrayList<String>();
animals.add("bat");
animals.add("owl");
animals.add("bat");
animals.add("bat");
System.out.println("Freq of bat: "+Collections.frequency(animals, "bat"));
Khafaga
  • 1,537
  • 4
  • 15
  • 24
8

I wonder, why you can't use that Google's Collection API with JDK 1.6. Does it say so? I think you can, there should not be any compatibility issues, as it is built for a lower version. The case would have been different if that were built for 1.6 and you are running 1.5.

Am I wrong somewhere?

Adeel Ansari
  • 39,541
  • 12
  • 93
  • 133
6

To get the occurrences of the object from the list directly:

int noOfOccurs = Collections.frequency(animals, "bat");

To get the occurrence of the Object collection inside list, override the equals method in the Object class as:

@Override
public boolean equals(Object o){
    Animals e;
    if(!(o instanceof Animals)){
        return false;
    }else{
        e=(Animals)o;
        if(this.type==e.type()){
            return true;
        }
    }
    return false;
}

Animals(int type){
    this.type = type;
}

Call the Collections.frequency as:

int noOfOccurs = Collections.frequency(animals, new Animals(1));
atr
  • 684
  • 6
  • 10
6
List<String> list = Arrays.asList("as", "asda", "asd", "urff", "dfkjds", "hfad", "asd", "qadasd", "as", "asda",
        "asd", "urff", "dfkjds", "hfad", "asd", "qadasd" + "as", "asda", "asd", "urff", "dfkjds", "hfad", "asd",
        "qadasd", "as", "asda", "asd", "urff", "dfkjds", "hfad", "asd", "qadasd");

Method 1:

Set<String> set = new LinkedHashSet<>();
set.addAll(list);

for (String s : set) {

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

Method 2:

int count = 1;
Map<String, Integer> map = new HashMap<>();
Set<String> set1 = new LinkedHashSet<>();
for (String s : list) {
    if (!set1.add(s)) {
        count = map.get(s) + 1;
    }
    map.put(s, count);
    count = 1;

}
System.out.println(map);
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
sabm
  • 61
  • 1
  • 2
  • Welcome to Stack Overflow! Consider explaining your code to make it easier for others to understand your solution. – Antimony Aug 23 '17 at 17:14
6

A slightly more efficient approach might be

Map<String, AtomicInteger> instances = new HashMap<String, AtomicInteger>();

void add(String name) {
     AtomicInteger value = instances.get(name);
     if (value == null) 
        instances.put(name, new AtomicInteger(1));
     else
        value.incrementAndGet();
}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
5

What you want is a Bag - which is like a set but also counts the number of occurances. Unfortunately the java Collections framework - great as they are dont have a Bag impl. For that one must use the Apache Common Collection link text

mP.
  • 18,002
  • 10
  • 71
  • 105
  • 2
    Best scalable solution and, if you can't use third-party stuff, just write your own. Bags aren't rocket science to create. +1. – paxdiablo Feb 03 '09 at 03:47
  • Downvoted for giving some vague answer while others have provided implementations for frequency-counting data structures. The 'bag' data structure you linked to is also not an appropriate solution to the OP's question; that 'bag' structure is intended to hold a specific number of copies of a token, not to count up the number of occurrences of tokens. – stackoverflowuser2010 Jun 13 '13 at 04:38
2

​If you use Eclipse Collections, you can use a Bag. A MutableBag can be returned from any implementation of RichIterable by calling toBag().

MutableList<String> animals = Lists.mutable.with("bat", "owl", "bat", "bat");
MutableBag<String> bag = animals.toBag();
Assert.assertEquals(3, bag.occurrencesOf("bat"));
Assert.assertEquals(1, bag.occurrencesOf("owl"));

The HashBag implementation in Eclipse Collections is backed by a MutableObjectIntMap.

Note: I am a committer for Eclipse Collections.

Donald Raab
  • 6,458
  • 2
  • 36
  • 44
1

Java 8 - another method

String searched = "bat";
long n = IntStream.range(0, animals.size())
            .filter(i -> searched.equals(animals.get(i)))
            .count();
ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
1

So do it the old fashioned way and roll your own:

Map<String, Integer> instances = new HashMap<String, Integer>();

void add(String name) {
     Integer value = instances.get(name);
     if (value == null) {
        value = new Integer(0);
        instances.put(name, value);
     }
     instances.put(name, value++);
}
Mark Renouf
  • 30,697
  • 19
  • 94
  • 123
  • With the appropriate "synchronized", if needed, to avoid race conditions. But I'd still prefer to see this in its own class. – paxdiablo Feb 03 '09 at 03:57
  • You have a typo. Need HashMap instead, as you are taking it in Map. But the mistake to put 0 instead of 1 is a bit more serious. – Adeel Ansari Feb 03 '09 at 04:21
1

Put the elements of the arraylist in the hashMap to count the frequency.

Shamik
  • 6,938
  • 11
  • 55
  • 72
1
package traversal;

import java.util.ArrayList;
import java.util.List;

public class Occurrance {
    static int count;

    public static void main(String[] args) {
        List<String> ls = new ArrayList<String>();
        ls.add("aa");
        ls.add("aa");
        ls.add("bb");
        ls.add("cc");
        ls.add("dd");
        ls.add("ee");
        ls.add("ee");
        ls.add("aa");
        ls.add("aa");

        for (int i = 0; i < ls.size(); i++) {
            if (ls.get(i) == "aa") {
                count = count + 1;
            }
        }
        System.out.println(count);
    }
}

Output: 4

Cà phê đen
  • 1,883
  • 2
  • 21
  • 20
1
 Integer[] spam = new Integer[]  {1,2,2,3,4};
 List<Integer>   list=Arrays.asList(spam);

System.out.println(list.stream().collect(Collectors.groupingBy(Function.identity(),Collectors.counting())));
System.out.println(list.stream().collect(Collectors.groupingBy(Function.identity(),HashMap::new,Collectors.counting())));
    

output

{1=1, 2=2, 3=1, 4=1}

Govind Sharma
  • 127
  • 1
  • 4
0
List<String> lst = new ArrayList<String>();

lst.add("Ram");
lst.add("Ram");
lst.add("Shiv");
lst.add("Boss");

Map<String, Integer> mp = new HashMap<String, Integer>();

for (String string : lst) {

    if(mp.keySet().contains(string))
    {
        mp.put(string, mp.get(string)+1);

    }else
    {
        mp.put(string, 1);
    }
}

System.out.println("=mp="+mp);

Output:

=mp= {Ram=2, Boss=1, Shiv=1}
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
0

I didn't want to make this case more difficult and made it with two iterators I have a HashMap with LastName -> FirstName. And my method should delete items with dulicate FirstName.

public static void removeTheFirstNameDuplicates(HashMap<String, String> map)
{

    Iterator<Map.Entry<String, String>> iter = map.entrySet().iterator();
    Iterator<Map.Entry<String, String>> iter2 = map.entrySet().iterator();
    while(iter.hasNext())
    {
        Map.Entry<String, String> pair = iter.next();
        String name = pair.getValue();
        int i = 0;

        while(iter2.hasNext())
        {

            Map.Entry<String, String> nextPair = iter2.next();
            if (nextPair.getValue().equals(name))
                i++;
        }

        if (i > 1)
            iter.remove();

    }

}
Alexander Shapkin
  • 1,126
  • 1
  • 13
  • 11
0
Map<String,Integer> hm = new HashMap<String, Integer>();
for(String i : animals) {
    Integer j = hm.get(i);
    hm.put(i,(j==null ? 1 : j+1));
}
for(Map.Entry<String, Integer> val : hm.entrySet()) {
    System.out.println(val.getKey()+" occurs : "+val.getValue()+" times");
}
fcm45
  • 1
0

If you are a user of my ForEach DSL, it can be done with a Count query.

Count<String> query = Count.from(list);
for (Count<Foo> each: query) each.yield = "bat".equals(each.element);
int number = query.result();
akuhn
  • 27,477
  • 2
  • 76
  • 91
0

You can use groupingBy feature of Java 8 for your use case.

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Test {
    public static void main(String[] args) {
        List<String> animals = new ArrayList<>();

        animals.add("bat");
        animals.add("owl");
        animals.add("bat");
        animals.add("bat");

        Map<String,Long> occurrenceMap =
                animals.stream().collect(Collectors.groupingBy(Function.identity(),Collectors.counting()));
        System.out.println("occurrenceMap:: " + occurrenceMap);
    }
}

Output

occurrenceMap:: {bat=3, owl=1}

GD07
  • 1,117
  • 1
  • 7
  • 9