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.

Community
  • 1
  • 1
nikosdi
  • 2,138
  • 5
  • 26
  • 35
  • Maybe what you're looking for is in the answer of [This](http://stackoverflow.com/questions/2864840/treemap-sort-by-value?lq=1) question. – Ferdinand Neman Sep 02 '15 at 08:47
  • Does this answer your question? [Is there a scala/java equivalent of Python 3's collections.Counter](https://stackoverflow.com/questions/28254447/is-there-a-scala-java-equivalent-of-python-3s-collections-counter) – eshizhan Jun 22 '22 at 12:44

2 Answers2

17

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
arodriguezdonaire
  • 5,396
  • 1
  • 26
  • 50
6

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.

OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • 3
    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 – Myles Hollowed Oct 09 '19 at 18:17
  • @MylesHollowed - Good catch - thanks for the heads-up - fixed. – OldCurmudgeon Oct 10 '19 at 07:47