【问题标题】:Reduce and assign using Stream api for multiple variables使用 Stream api 减少和分配多个变量
【发布时间】:2017-11-10 06:09:13
【问题描述】:

我需要在 lambda 中执行以下操作,但无法想到单流和减少可以提供帮助的方式:(

我有 Employee ArrayList,其中 name 成员变量可以在列表中的对象之间重复。我需要创建地图,其中键是员工姓名,值是对象,我们有该员工的 cost1 和 cost2 sum。

员工类有 -> 字符串名称、整数 cost1、整数 cost2

输出类有 -> 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);
    }
}

【问题讨论】:

  • 我不明白你想做什么,你能再具体点吗?
  • @Eugene :是的,我可以使用。但是如果可能的解决方案使用 java 自己的类将更有助于理解它是否可以在单个流中完成
  • @SamedSivaslıoğlu:对于每个员工姓名,将 cost1 和 cost2 相加并放入地图。

标签: lambda java-8 java-stream


【解决方案1】:

我假设你的结构是这样的:

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      
}

那么解决方案是先通过Employee::getName 分组,然后通过Collectors.reducing 减少到Output

  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}

【讨论】:

  • 太棒了。谢谢。实际上,通过您的示例,我了解减少更多
  • 看起来像toMap 收集器的任务:Collectors.toMap(Employee::getName, emp -&gt; new Output(emp.getCost1(), emp.getCost2()), (left, right) -&gt; new Output(left.getCost1() + right.getCost1(), left.getCost2() + right.getCost2())),相同的功能,更少的代码,甚至创建更少的Output 实例......
  • @FedericoPeraltaSchaffner 请提供它,这将是一个很好的补充。
  • @Federico Peralta Schaffner:你假设一个可变的 Output 类,而这个答案没有。当然,你不能用具有标识值的reducing 收集器来做到这一点。如果你假设一个可变的Output 类,你可以通过groupingBy 进一步减少实例的数量,下游收集器只为每个组创建一个Output 实例(你仍然为每个员工创建一个)。跨度>
  • @Federico Peralta Schaffner:与您的变体相比,不多:Collectors.groupingBy(Employee::getName, Collector.of(Output::new, (o, e) -&gt; { o.setCost1(o.getCost1()+e.getCost1()); o.setCost2(o.getCost2()+e.getCost2()); }, (left, right) -&gt; { left.setCost1(left.getCost1() + right.getCost1()); left.setCost2(left.getCost2() + right.getCost2()); return left; } ))。如前所述,这可能会节省更多的实例。否则,不可变 Output 实例的 toMap 会更简单。可变减少将受益于 Output.addToCost1(int) 和 Output.addToCost2(int) 方法虽然......
【解决方案2】:

当您需要从流的元素中获取 reduced 映射时,您可以使用 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,顾名思义,将流的元素收集到一个映射中。它的第一个参数是将流的元素转换为键的函数,第二个参数是将流的元素转换为值的另一个函数,第三个参数是在键发生冲突时应用于值的合并函数.

在这种情况下,我在合并函数的左操作数上使用 可变 缩减。这是为了避免创建太多Output 类的实例。减少这种方式是安全的,因为在值函数(Collectors.toMap 的第二个参数)中,我正在创建 Output 类的新实例。

如果您可以将以下构造函数和方法添加到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;
}

现在,收集到地图会简单得多:

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

【讨论】:

  • 我得到了早期答案的要点,每次都会创建更多实例。感谢您提供更高效的解决方案。
猜你喜欢
  • 2017-02-12
  • 2012-04-07
  • 2020-12-02
  • 1970-01-01
  • 2021-03-19
  • 2021-09-13
  • 2015-05-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多