1

So I have this loop

for (Map.Entry<String, Integer> val : map.entrySet()) {
        if (val.getValue() >= 10 && !Objects.equals(val.getKey(), " ") && val.getKey().length() > 3) {
            stats = stats + val.getKey().toLowerCase() + " - " + val.getValue() + "\n";
        }
    }

Now I want to get rid of for loop and if condition using streams. How can I do this?

cigien
  • 57,834
  • 11
  • 73
  • 112
czauto
  • 29
  • 3
  • 1
    If you want a more equivalent stream, try `map.entrySet().stream()` to stream over the map entries. You would want to use `Stream#filter` to include only results that satisfy your `if` statement, and then finally use `Stream#forEach` to iterate the results. Using `Map#forEach` directly will really be no different than the for loop. Also, you can use `Stream#filter` multiple times, to specify the conditions separately. – Rogue Jun 28 '22 at 16:04
  • I would `map` the entries to their respective `String`, and then use `Collectors.joining`. – Johannes Kuhn Jun 28 '22 at 16:49

4 Answers4

3

Here could be an approach:

String stats = map.entrySet().stream()
        // keep only the entries you're interested in:
        .filter(entry -> entry.getKey().length() > 3 && entry.getValue() >= 10)
        // serialise entries one by one:
        .map(entry -> entry.getKey().toLowerCase() + " - " + entry.getValue())
        // join:
        .collect(Collectors.joining("\n"));
sp00m
  • 47,968
  • 31
  • 142
  • 252
2

There are many approaches, here is one.

  • first some test data.
Map<String, Integer> map = Map.of(" ", 18, "foo", 14, "abcd",
        20, "ab", 10, "efgh", 3, "rstuv", 14);
  • Then I would define a Predicate<Entry<String, Integer>> to reduce the clutter of the stream construct. Note that since you are checking for length < 3 the condition for a single space can be omitted. If you meant check for a blank String, you can use !val.getKey().isBlank() as the condition.

  • reduce takes an initial argument followed by a BinaryOperator as an accumulator. In this case the accumulation is a concatenation of the mapped strings. String::concat meets the requirements and is used here to fulfill the operation.

Predicate<Entry<String, Integer>> check =
        val -> val.getValue() >= 10
                && val.getKey().length() > 3;

Then it is just a matter of streaming the EntrySet.

String result = map.entrySet().stream().filter(check)
         .map(e -> e.getKey().toLowerCase() + " - " + e.getValue() + "\n")
         .reduce("", String::concat);

Systemm.out.print(result);

Prints the following.

abcd - 20
rstuv - 14
WJS
  • 36,363
  • 4
  • 24
  • 39
  • Thanks for the explanation a lot )) I just saw your response and although I had solved this problem long time ago,, I discovered some new things for me :) – czauto Nov 10 '22 at 19:21
  • @czauto. Thanks! BTW, not certain if you knew this but you can't accept two answers. No problem though. – WJS Nov 10 '22 at 19:34
0

To have a full use of stream functionalities You need to use stream, filter and reduce functions as follow:

// Get the stream from entrySet
String result = map.entrySet().stream()    
      // Filter entries based on your condition
      .filter(val ->  val.getValue() >= 10 
                           && !Objects.equals(val.getKey(), " ") 
                           && val.getKey().length() > 3)  
       // Apply the reduce to combine filtered entries in a single string     
      .reduce("", (stats, val) -> stats 
                                  + val.getKey().toLowerCase() 
                                  + " - " 
                                  + val.getValue() 
                                  + "\n");

The stream method applied to a Collection give a new Stream of the item present in the set:

Returns a sequential Stream with this collection as its source.

The filter method appplied on the stream:

Returns a stream consisting of the elements of this stream that match the given predicate.

The reduce function on the stream object:

Performs a reduction on the elements of this stream, using the provided identity value and an associative accumulation function, and returns the reduced value

Davide Lorenzo MARINO
  • 26,420
  • 4
  • 39
  • 56
  • now I'm getting Bad return type in lambda expression: String cannot be converted to Entry error – czauto Jun 28 '22 at 16:28
  • @czauto The ternary expression (a > b ? 1 : 0) is not a statement but a value. It uses a conditional expression to determine a value (1 or 0). So you might be putting a value where you should be putting a statement, or vice versa. – ktm5124 Jun 28 '22 at 16:33
  • The 2-arg `reduce` function won't work here, as a `BinaryOperator>` is expected while you're passing a `BiFunction, String>`. You need [the 3-arg overload](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#reduce-U-java.util.function.BiFunction-java.util.function.BinaryOperator-). – sp00m Jun 28 '22 at 16:47
  • What about [`Collectors.joining`](https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/util/stream/Collectors.html#joining%28%29)? – Johannes Kuhn Jun 28 '22 at 16:50
0

Good job using the forEach method. It's good practice to use streams and the stream API.

Here's a very simple program that does something similar to what you want to do.

import java.util.Map;
import java.util.HashMap;
public class Test {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("aaaa", 1);
        map.put("bbbb", 2);
        map.put("cccc", 3);
        map.forEach((k,v)->System.out.println(k + ": " + v));
    }
}

Now we can compile and run the code.

% javac Test.java
% java Test
aaaa: 1
bbbb: 2
cccc: 3

Now, what's the error in your code?

The error is this. The Map.forEach method takes a BiConsumer. The BiConsumer operation takes two input arguments (k and v) and returns no result. You are using a BiConsumer operation to return a result in a ternary expression. The BiConsumer operation has a return type of void. So what you can do is... you can execute code inside of the BiConsumer operation (like I did in my example) but you cannot return a result in the BiConsumer operation (which you are trying to do).

You can read about the Map.forEach method and the BiConsumer functional operation in the Java docs.

  1. Java docs for the Map.forEach method
  2. Java docs for the BiConsumer operation
ktm5124
  • 11,861
  • 21
  • 74
  • 119