1

I am learning how to use streams in java and I would like to know the most efficient way to copy the python count functionality into java.

For those unfamiliar with python count, see here.

I've already done a naive implementation but I doubt this would ever get added to a production level environment:

private List<String> countMessages(List<String> messages) {
        Map<String, Integer> messageOccurrences = new HashMap<>();
        List<String> stackedMessages = new LinkedList<String>();
        this.messages.stream().filter((message) -> (messageOccurrences.containsKey(message))).forEachOrdered((message) -> {
                    int new_occ = messageOccurrences.get(message) + 1;
                    messageOccurrences.put(message, new_occ);
        });
        messageOccurrences.keySet().forEach((key) -> {
            stackedMessages.add(key + "(" + messageOccurrences.get(key) + "times)" );
        });
        return stackedMessages;
    }

Any improvements or pointers would be appreciated.

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
Q.H.
  • 1,406
  • 2
  • 17
  • 33
  • this question is better suited for https://codereview.stackexchange.com/ – niceman Jun 04 '18 at 21:50
  • that looks a complicated way to do what pythons list `count()` method does. do you not just want `long count = this.messages.stream().filter((message) -> message.equals(messages)).count();` ? if not then please elaborate... – Ousmane D. Jun 04 '18 at 21:56
  • @Aominè I had made a mistake when I first submitted the question. The function should take the list of messages and return a hashmap with the message as the key and the number of times it appeared in the list as the value. I must do this using streams. – Q.H. Jun 04 '18 at 22:05
  • As a stylistic note, your code becomes more readable if you don’t pollute it with so many braces. Instead of `(name) -> (expression)`, just write `name -> expression`. Further, instead of `(name) -> { singleMethodInvocation(); }`, just write `name -> singleMethodInvocation()`. Then, don’t iterate over a `keySet()` to perform a `get()` on the map for each key, as that’s not only visual noise, it’s inefficient. You can iterate over the `entrySet()` or here, just use `messageOccurrences.forEach((key, value) -> stackedMessages.add(key + "(" + value + "times)" )`… – Holger Jun 05 '18 at 08:22
  • And consider [When to use LinkedList over ArrayList?](https://stackoverflow.com/q/322715/2711488) (spoiler: almost never). – Holger Jun 05 '18 at 08:23

1 Answers1

4

To answer the question "what is the best way to implement the python count function in java?".

Java already has Collections.frequency which will do exactly that.

However, if you want to do it with the streams API then I believe a generic solution would be:

public static <T> long count(Collection<T> source, T element) {
       return source.stream().filter(e -> Objects.equals(e, element)).count();
}

then the use case would be:

long countHellp = count(myStringList, "hello");
long countJohn = count(peopleList, new Person("John"));
long count101 = count(integerList, 101); 
...
...

or you can even pass a predicate if you wanted:

public static <T> long count(Collection<T> source, Predicate<? super T> predicate) {
       return source.stream().filter(predicate).count();
}

Then the use case would be for example:

long stringsGreaterThanTen = count(myStringList, s -> s.length() > 10);
long malesCount = count(peopleList, Person::isMale);
long evens = count(integerList, i -> i % 2 == 0); 
...
...

Given your comment on the post, it seems like you want to "group" then and get the count of each group.

public Map<String, Long> countMessages(List<String> messages) {
        return messages.stream()
                       .collect(groupingBy(Function.identity(), counting()));
}

This creates a stream from the messages list and then groups them, passing a counting() as the downstream collector meaning we will retrieve a Map<String, Long> where the keys are the elements and the values are the occurrences of that specific string.

Ensure you have the import:

import static java.util.stream.Collectors.*;

for the latter solution.

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • This might be a stupid question, but is there a way to cast the `Long` return value to an `Integer` within the function? Also marking this as the answer because it got me my desired result – Q.H. Jun 04 '18 at 22:13
  • @Q.H. it's not a stupid question at all. that's a good question. simply change `counting()` to `summingInt(e -> 1)` . – Ousmane D. Jun 04 '18 at 22:14
  • Don’t use `e -> Objects.equals(e, element)`, use `Predicate.isEqual(element)`. Besides having less noise, it’s implemented equivalent to `element == null? Object::isNull: element::equals`, which doesn’t repeat the `null` check for every stream element. – Holger Jun 05 '18 at 08:16
  • @Holger Right, thanks. will keep that in mind from now on. – Ousmane D. Jun 05 '18 at 09:07