Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Counting occurrences of a key in a Map in Java

I'm writing a project that captures Java keywords from a .java file and keeps track of the occurrences with a map. I've used a similar method in the past successfully, but I can't seem to adopt this method for my intended use here.

    Map<String,Integer> map = new TreeMap<String,Integer>();
    Set<String> keywordSet = new HashSet<String>(Arrays.asList(keywords));
    Scanner input = new Scanner(file);
    int counter = 0;
    while (input.hasNext())
    {
        String key = input.next();
        if (key.length() > 0)
        {
            if (keywordSet.contains(key))
            {
                map.put(key, 1);
                counter++;
            }

                if(map.containsKey(key)) <--tried inner loop here, failed
                {
                    int value = map.get(key);
                    value++;
                    map.put(key, value);
                }

        }

This block of code is supposed to add the keyword to the key, and increment the value each time the same key occurs. So far, it adds the keywords, but fails to properly increment the value. here is a sample output:

{assert=2, class=2, continue=2, default=2, else=2, ...} 

Basically it increments every value in the map instead of the ones it's supposed to. I'm not sure if I'm over-thinking this or what. I've tried an inner loop and it gave me insane results. I really hope I'm just over-thinking this. Any help is greatly appreciated!

like image 836
Justin Ertley Avatar asked Mar 05 '13 06:03

Justin Ertley


3 Answers

There's a much more concise (and easier to reason about) way to achieve what you want:

final ConcurrentMap<String, AtomicInteger> map = new ConcurrentHashMap<>();
final Scanner input = new Scanner(file);
while (input.hasNext()) {
  final String key = input.next();
  if (key.length() > 0) {
    map.putIfAbsent(key, new AtomicInteger(0));
    map.get(key).incrementAndGet();
  }
}

Let's analyze why does this work.

Whenever the Scanner encounters a keyword, there are 2 possible cases: you either have encountered it before (ie, it is a known keyword), or it is an yet unseen keyword.

  • If it is an unseen keyword: putIfAbsent will put an AtomicInteger with value 0 in the map, and incrementAndGet() will set it to 1 right after, and, from now on, it becomes a known keyword;
  • If it is a known keyword: putIfAbsent will do nothing, and incrementAndGet() will increment the value that is already present in the map.

Then, if you want the key set, you do:

final Set<String> keys = map.keySet();

To print all the values, you could do something like:

for (final String k : map.keySet()) {
  System.out.println(k + ": " + map.get(k).get());
}

You are not forced to use the two "different" classes I used above, ConcurrentMap and AtomicInteger. It is just easier to use them because they encapsulate much of the logic that you tried to write by yourself (and failed). The logic that they encapsulate is exactly all the other answers describe (ie, test if the value is present, if not set it to 0, then get whatever value is present, increment it and put it back into the map).

To maintain the keys of the map (our words being counted) in alphabetical order, use a ConcurrentNavigableMap such as ConcurrentSkipListMap .

like image 149
Bruno Reis Avatar answered Oct 21 '22 03:10

Bruno Reis


For every key you scan you create a new entry in the map (overriding the existing one). Then, the next condition holds so you increment the count by 1, reaching the value 2.

The inner part should be something like:

        if (keywordSet.contains(key))
        {
            Integer value = map.get(key);
            if (value == null)
                value = 0;
            value++;
            map.put(key, value);
        }

Anyway, consider using some kind of a mutable integer to make this more efficient. You won't have to override entries in the map, and you won't be doing too much Integer boxing operations.

like image 21
Eyal Schneider Avatar answered Oct 21 '22 01:10

Eyal Schneider


Even more concise using Map.merge (since Java 8):

if (keywordSet.contains(key)) {
    map.merge(key, 1, (currentCount, notUsed) -> ++currentCount);
}

Here is a generic implementation of a counting map - a map with values representing the count of their keys:

public static <K> void count(K key, Map<K, Integer> map) {
    map.merge(key, 1, (currentCount, notUsed) -> ++currentCount);
}

public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<>();
    count("A", map);
    count("B", map);
    count("A", map);
    count("Z", map);
    count("A", map);
    System.out.println(map); // {A=3, B=1, Z=1}
}
like image 6
David Soroko Avatar answered Oct 21 '22 03:10

David Soroko