2

The problem I am trying to solve is MapSum LeetCode Description. In nutshell, the task is to sum values over keys.

Iterating Map = 117 ms:

Iterator<Entry<String, Integer>> it = _map.entrySet().iterator();
        int count = 0;
        while(it.hasNext()) {
            Entry<String, Integer> entry = it.next();
             String key = entry.getKey();
             int val = entry.getValue();
             if(key.startsWith(prefix)) {
                 count += val; 
             }   
        }

Using Java 8 Streams - 134 ms

int count = _map.keySet().stream()
                .filter(key -> key.startsWith(prefix))
                .map(str -> _map.get(str))
                .mapToInt(Number::intValue)
                .sum();

Questions


  • Should you avoid Streams where for-loop is possible?

Or have I written the above code in non-streaming way

Incpetor
  • 1,293
  • 6
  • 25
  • 46
  • one of the few links you can go through https://stackoverflow.com/questions/44180101/in-java-what-are-the-advantages-of-streams-over-loops – Naman Sep 21 '17 at 12:42
  • 2
    "What is an ideal use case for Java Streams?" **too broad** "Should you avoid Streams where for-loop is possible?" **no** "How is Java 8 evaluation lazily or eagerly?" **what**? – Michael Sep 21 '17 at 12:46
  • 1
    I don't know how you benchmark it, but there is a difference in both snippets. In the stream one, you are iterating over the keyset only, which means that you have to call get for each key (e.g. implying recomputing the hash if you are using a hashmap for instance, which adds extra time). A more fair comparison would be to do `_map.entrySet().stream().filter(e -> e.getKey().startsWith(prefix)).map(Map.Entry::getValue).mapToInt(Number::intValue).sum();` – Alexis C. Sep 21 '17 at 12:47
  • @AlexisC. Agreed the comparison isn't completely fair. I am interested in knowing for a given task when should I go for Java 8, or I am using streams the wrong way etc. I would expect streams to be faster at least not slower than conventional for-loop if they evaluation is lazy sort of – Incpetor Sep 21 '17 at 12:53
  • @Alexis C.: Can do even shorter: `_map.entrySet().stream().filter(e -> e.getKey() .startsWith(prefix)) .mapToInt(Map.Entry::getValue).sum();` – Holger Sep 21 '17 at 14:55
  • I see you've changed your question to only the one about for loops. The answer - as I said before - is no. Streams are always possible to write as for loops. Streams may sometimes perform less well than a for-loop but the majority of the time the difference is negligible and there are plenty of reasons to prefer streams. See this question: https://stackoverflow.com/questions/44180101/in-java-what-are-the-advantages-of-streams-over-loops – Michael Sep 22 '17 at 13:59

1 Answers1

3

Your stream version has an extra get per item, which is not as fast as you probably think it is. Try it like this:

int count = _map.entrySet().stream()
                .filter(ent-> ent.getKey().startsWith(prefix))
                .mapToInt(ent -> ent.getValue().intValue())
                .sum();

Also, of course, it's not that easy to do microbenchmarks properly in Java, so your measurements may be off.

But, finally, streams are not usually the fastest way to write something. In most cases the performance penalty is quite small, and the syntax can be a lot clearer than the alternative.

EDIT: to answer your last question -- streams are generally evaluated lazily. The last step pulls items through all the previous steps.

Matt Timmermans
  • 53,709
  • 3
  • 46
  • 87
  • 1
    The explicit invocation of `.intValue()` is not necessary. You can simply say `.mapToInt(ent -> ent.getValue())` or even `.mapToInt(Map.Entry::getValue)`. – Holger Sep 21 '17 at 15:00