Shorten and assign Stream api usage to multiple variables

I need to do the following in a lambda, but am unable to think about how a single stream and reduction (s) can help :(

I have an Employee ArrayList where the member variable name can be duplicated throughout the object in the list. I need to create a map, where the key is the name of the employee, and the value is the object, where we have the sum of cost1 and cost2 for this employee.

Employee class has -> Row name, Integer cost1, Integer cost2

Output class has -> Integer cost1, Integer cost2

List <Employee> employeeList = new ArrayList<>();
// data population in inputMap
Map<String, Output> outputMap = new HashMap<>();
for (Employee emp : employeeList)
{
    Output op = outputMap.get(emp.getName());
    if(op == null){
      Output newOp = new Output ();
      newOp.setCost1(emp.getCost1())
      newOp.setCost2(emp.getCost2())
      newOp.put(emp.getName(), newOp);
    }else{
       int cost1 = op.getCost1() + emp.getCost1();
       int cost2 = op.getCost2() + emp.getCost2();
       op.setCost1(cost1);
       op.setCost2(cost2);
       newOp.put(emp.getName(), op);
    }
}

      

+3


source to share


2 answers


I assume you have a structure like this:

static class Output {
    private final int cost1;

    private final int cost2;

    public Output(int cost1, int cost2) {
        this.cost1 = cost1;
        this.cost2 = cost2;
    } 

    @Override
    public String toString() {
        return "cost1 = " + cost1 + " cost2 = " + cost2;
    }
    // ... getter
}  

static class Employee {
    private final String name;

    private final int cost1;

    private final int cost2;

    public Employee(String name, int cost1, int cost2) {
        this.name = name;
        this.cost1 = cost1;
        this.cost2 = cost2;
    }
 // ...getters      
}

      



Then the solution would be the first group with help Employee::getName

and reduce to Output

throughCollectors.reducing

  Map<String, Output> map = Arrays.asList(
                       new Employee("e", 12, 12), 
                       new Employee("f", 13, 13), 
                       new Employee("e", 11, 11))
            .stream()
            .collect(Collectors.groupingBy(Employee::getName,
                    Collectors.reducing(
                            new Output(0, 0),
                            emp -> new Output(emp.getCost1(), emp.getCost2()),
                            (left, right) -> new Output(left.getCost1() + right.getCost1(), left.getCost2() + right.getCost2()))));
    System.out.println(map); // {e=cost1 = 23 cost2 = 23, f=cost1 = 13 cost2 = 13}

      

+4


source


Whenever you need to get a minified map from the elements of your stream, you can use the three-argument version Collectors.toMap

:

Map<String, Output> result = employeeList.stream()
    .collect(Collectors.toMap(
        Employee::getName,
        employee -> new Output(employee.getCost1(), employee.getCost2()),
        (left, right) -> {
            left.setCost1(left.getCost1() + right.getCost1());
            left.setCost2(left.getCost2() + right.getCost2());
            return left;
        }));

      

Collectors.toMap

, as its name suggests, collects flow elements onto a map. Its first argument is a function that converts stream elements to keys, the second argument is another function that converts stream elements to values, and the third argument is a merge function that is applied to values ​​when a key collision occurs.

In this case, I am using a mutable shorthand in the left operand of the merge function. This is done in order not to create too many instances of the class Output

. It is safe to shrink this path because in the value function (second argument Collectors.toMap

) I am creating a new instance of the class Output

.



This solution can be greatly improved if you can add the following constructor and method to the class Output

:

// creates an Output instance out of an Employee instance
public Output(Employee employee) {
    this.cost1 = employee.getCost1();
    this.cost2 = employee.getCost2();
}

// merges another Output instance into this Output instance
public Output merge(Output another) {
    this.cost1 += another.cost1;
    this.cost2 += another.cost2;
    return this;
}

      

Now collecting to the map will be much easier:

Map<String, Output> result = employeeList.stream()
    .collect(Collectors.toMap(Employee::getName, Output::new, Output::merge));

      

+3


source







All Articles