21

I have been using the Counter() data structure in Python as a key-value store that allows me to have the objects sorted according to their value using the most_common method. More info here.

Is there any similar data structure for the Java language? For example, I have seen many related answers that focus on sorting HashMaps or TreeMaps by the data structure is not initially defined for that purpose. In my case I usually need to to keep counters of objects and then to select the most common or the ones with the highest score (Top-N queries). However, it is difficult for me since I need to insert to a HashMap and then sort or to use multiple data structures.

2

2 Answers 2

19

From here:

The Counter class is similar to bags or multisets in other languages.

Java does not have a Multiset class, or an analogue. Guava has a MultiSet collection, that does exactly what you want.

In pure Java, you can use a Map and the new merge method:

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

counts.merge("Test", 1, Integer::sum);
counts.merge("Test", 1, Integer::sum);
counts.merge("Other", 1, Integer::sum);
counts.merge("Other", 1, Integer::sum);
counts.merge("Other", 1, Integer::sum);

System.out.println(counts.getOrDefault("Test", 0));
System.out.println(counts.getOrDefault("Other", 0));
System.out.println(counts.getOrDefault("Another", 0));

Output:

2
3
0

You can wrap this behaviour in a class in a few lines of code:

public class Counter<T> {
    final Map<T, Integer> counts = new HashMap<>();

    public void add(T t) {
        counts.merge(t, 1, Integer::sum);
    }

    public int count(T t) {
        return counts.getOrDefault(t, 0);
    }
}

And use it like this:

final Counter<String> counts = new Counter<>();

counts.add("Test");
counts.add("Test");
counts.add("Other");
counts.add("Other");
counts.add("Other");

System.out.println(counts.count("Test"));
System.out.println(counts.count("Other"));
System.out.println(counts.count("Another"));

Output:

2
3
0
Sign up to request clarification or add additional context in comments.

1 Comment

At least you should give credit to the actual answer from where you have copied.
7

Here's a class that looks like it implements enough of Counter to do what you want.

static class Counter<T> {

    final ConcurrentMap<T, Integer> counts = new ConcurrentHashMap<>();

    public void put(T it) {
        add(it, 1);
    }

    public void add(T it, int v) {
        counts.merge(it, v, Integer::sum);
    }

    public List<T> mostCommon(int n) {
        return counts.entrySet().stream()
                // Sort by value.
                .sorted((e1, e2) -> Integer.compare(e2.getValue(), e1.getValue()))
                // Top n.
                .limit(n)
                // Keys only.
                .map(e -> e.getKey())
                // As a list.
                .collect(Collectors.toList());
    }
}

public void test() {
    Counter<String> c = new Counter<>();
    String[] numbers = {"Zero", "One", "Two", "Three", "Four", "Five", "Six"};
    for (int i = 0; i < numbers.length; i++) {
        c.add(numbers[i], i);
    }
    System.out.println(c.mostCommon(3));
}

It uses Java 8 functionality.

2 Comments

I logged in just to upvote this - thank you. I also noticed that the mostCommon method actually returns the least common elements. The edit got rejected and I'm not too sure why. But I at least want to leave a note for anyone who uses this - switch Integer.compare(e1.getValue(), e2.getValue()) with Integer.compare(e2.getValue(), e1.getValue()) in order to get the most common elements
@MylesHollowed - Good catch - thanks for the heads-up - fixed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.