1

I defined a map and filled it with 5 objects of the type Rectangle. Each Rectangle-object has the attributes rectangleId, aLength, bLength and color.

I want to use the java stream api to stream the given map into the new map. While streaming, the value of bLength shall be increased by 100 for all rectangles that are red. Below is what I got so far. I can't seem to figure out how to change the value of bLength.

public class Main {
    public static void main(String[] args){

        Rectangle r1 = new Rectangle(800, 100,200,"green");
        Rectangle r2 = new Rectangle(900, 200,300,"red");
        Rectangle r3 = new Rectangle(1000, 300,400,"yellow");
        Rectangle r4 = new Rectangle(1100, 400,500,"blue");
        Rectangle r5 = new Rectangle(1200, 500,600,"orange");

        TreeMap<Integer, Rectangle> myMap = new TreeMap<>();
        myMap.put(r1.rectangleId, r1);
        myMap.put(r2.rectangleId, r2);
        myMap.put(r3.rectangleId, r3);
        myMap.put(r4.rectangleId, r4);
        myMap.put(r5.rectangleId, r5);

        Map<Integer, Rectangle> myMapNew = myMap.entrySet().stream()
                .filter(r -> r.getValue().color == "red")
                .collect(Collectors.toMap(r -> r.getKey(), r -> r.getValue()));

    }

    public class Rectangle {
        public int rectangleId;
        public int aLength;
        public int bLength;
        public String color;

        public Rectangle(int rectangleId, int aLength, int bLength, String color){
            this.rectangleId = rectangleId;
            this.aLength = aLength;
            this.bLength = bLength;
            this.color = color;
    }
}
Tobias
  • 2,547
  • 3
  • 14
  • 29
Jan
  • 73
  • 1
  • 7
  • Why do you want to use the stream api? Seems straightforward to do in a for loop. – daniu Apr 16 '21 at 18:32
  • Must the result be a new map? By *modifying* the rectangles, there is only one instance of each, so the new map would be identical to the old map after your proposed operation. For the two maps to be different you would need to create new instances of the rectangles for the new map. Please elaborate. – Bohemian Apr 16 '21 at 18:38

3 Answers3

4

By modifying the rectangles, there is still only one instance of each, so the rectangles in both maps are the same rectangles and so new map would be identical to the old map after your proposed operation. Thus a new map is not required - modifying the old one is sufficient:

myMap.values().stream()
  .filter(r -> r.color.equals("red"))
  .forEach(r -> r.length += 100);

For the new map to be different, you would need to create new instance of the rectangles for the néw map, but that is not what you proposed.

Also note that comparing Strings using == is not guaranteed to work the way you expect in the general case; always use .equals().

Bohemian
  • 412,405
  • 93
  • 575
  • 722
2

So you have 2 steps in your streaming then:

  1. incoming Rectangle to outgoing Rectangle (= identity)
  2. convert one or many attributes of Rectangle based on a predicate (= conditional attribute-modification)

Solve it in functions

  1. identity (actually obsoletes this step in streaming), anyway:
Function<Rectangle, Rectangle> toIdentical = Function.identity();
  1. modification rule:

bLength shall be increased by 100 for all Rectangles that have property color with value "red"

Function<Rectangle, Rectangle> toConditialModified = (rectangle) -> {
  if (rectangle.color.equals("red")) { // conditional
    rectangle.bLength += 100; // modified
  }
  return rectangle;
};

Then combine in a stream

Build a pipeline using these functional steps:

Map<Integer, Rectangle> newRectanglesMap = oldRectanglesMap.values().stream()
  .map( toIdentical )  // can leave this out
  .map( toConditionalModified )
  .collect(Collectors.toMap(r -> r.rectangleId, r -> r));

Alternatively: update partly

Just work on the values, modify if needed in-place:

oldRectanglesMap.values().stream()
  .filter(r -> r.color.equals("red")) // get only the red ones
  .forEach(r -> { r.bLength += 100; }) // modify the filtered

Over-readable & functionally decomposed

This would be better readable if decomposing Function<Rectangle, Rectangle> toConditialModified of previous step 2:

Predicate<Rectangle> isRed = r -> r.color.equals("red");
Consumer<Rectangle> updateBLengthPlus100 = r -> r.bLength += 100;

oldRectanglesMap.values().stream()
  .filter( isRed )
  .forEach( updateBLengthPlus100 );

I just played with your code and requirement:

  • looked on different aspects of the problem
  • decomposed it differently
  • solved these parts differently
  • but: always functional using streams
hc_dev
  • 8,389
  • 1
  • 26
  • 38
1

If you want to have all of the rectangles from the original map in the new map (and modify some of them) you can't use the filter method, because that will restrict the result to only the red ones.

You need to use the map method to change the rectangles while streaming:

Map<Integer, Rectangle> myMapNew = myMap.entrySet().stream()
                .map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), entry.getValue().increaseBLengthOrReturnIdentity()))
                .collect(Collectors.toMap(r -> r.getKey(), r -> r.getValue()));

And you need to define the method increaseBLengthOrReturnIdentity in you Rectangle class:

public class Rectangle {
    public int rectangleId;
    public int aLength;
    public int bLength;
    public String color;

    public Rectangle(int rectangleId, int aLength, int bLength, String color){
        this.rectangleId = rectangleId;
        this.aLength = aLength;
        this.bLength = bLength;
        this.color = color;
    }

    public Rectangle increaseBLengthOrReturnIdentity() {
        if (color.equals("red")) {//btw.: better use equals to compare strings instead of ==
            return new Rectangle(rectangleId, aLength, bLength + 100, color);
        }

        // you could also return 'this' here; depends on whether you need a new object or not
        return new Rectangle(rectangleId, aLength, bLength, color);
    }
}
Tobias
  • 2,547
  • 3
  • 14
  • 29
  • Thanks, but I get the following error: Required type: Function super java.util.Map.Entry, ? extends R> Provided: reason: no instance(s) of type variable(s) exist so that Entry conforms to Rectangle – Jan Apr 16 '21 at 18:10
  • Sorry, I missed that you stream over `Map.Entry` objects instead of only `Rectangle` object. I updated the answer and tested it. Now it should be working. – Tobias Apr 16 '21 at 18:33