如何避免流的结果重复?
How avoid duplicates in result from stream?
钱箱class:
public class CashBox {
private long cashBoxId;
private BigDecimal totalAmount;
private long merchantId;
// all-args constructor
}
商家class:
public class Merchant {
private long merchantId;
private BigDecimal totalAmount;
// all-args constructor
}
输入数据:
List<CashBox> cashBoxes = List.of(
new CashBox(1, new BigDecimal(1000), 1),
new CashBox(2, new BigDecimal(2000), 1),
new CashBox(3, new BigDecimal(3000), 2),
new CashBox(4, new BigDecimal(500), 2));
我的任务
Calculate the total amount for each merchant and return the merchant list
我正在尝试使用 Stream API 解决此任务。并编写了以下代码:
List<Merchant> merchant = cashBoxes.stream()
.map(merch -> new Merchant(
merch.getMerchantId(),
cashBoxes.stream()
.filter(cashBox -> cashBox.getMerchantId() == merch.getMerchantId())
.map(CashBox::getTotalAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add)))
.collect(Collectors.toList());
结果
[Merchant{merchantId=1, totalAmount=3000}, Merchant{merchantId=1, totalAmount=3000}, Merchant{merchantId=2, totalAmount=3500}, Merchant{merchantId=2, totalAmount=3500}]
但显然,流 returns 四个对象而不是所需的两个。
我意识到,地图(第二行)为每个 cashBoxId 创建了四个对象。而且我不知道如何按 merchantId
过滤或获得没有重复的结果。
一种方法是使用 groupingBy
。按商户 ID 分组,然后为每个组映射到总量,并使用相应的收集器进行归约。这将为您提供包含商家 ID 和总金额的 Map<Long, BigDecimal>
。然后您可以将此地图的每个条目映射到商家:
cashBoxes.stream().collect(Collectors.groupingBy(
CashBox::getMerchantId, // group by merchant Id
// for each group...
Collectors.mapping(// map to total amount
CashBox::getTotalAmount,
Collectors.reducing(BigDecimal.ZERO, BigDecimal::add) // sum
)
)).entrySet().stream()
.map(x -> new Merchant(x.getKey(), x.getValue())) // map to merchant
.collect(Collectors.toList());
有
这里是使用单个 Stream 的“单行”。这个想法是利用使用 Stream::toMap
分组的优势,使用 Function<CashBox, Merchant>
映射值并使用 BinaryOperator<Merchant>
:
将它们合并到一个对象中
Collection<Merchant> merchants = cashBoxes.stream()
.collect(Collectors.toMap(
CashBox::getMerchantId,
cashBox -> new Merchant(cashBox.getMerchantId(), cashBox.getTotalAmount()),
(l, r) -> {
l.setTotalAmount(l.getTotalAmount().add(r.getTotalAmount()));
return l;
}
))
.values();
钱箱class:
public class CashBox {
private long cashBoxId;
private BigDecimal totalAmount;
private long merchantId;
// all-args constructor
}
商家class:
public class Merchant {
private long merchantId;
private BigDecimal totalAmount;
// all-args constructor
}
输入数据:
List<CashBox> cashBoxes = List.of(
new CashBox(1, new BigDecimal(1000), 1),
new CashBox(2, new BigDecimal(2000), 1),
new CashBox(3, new BigDecimal(3000), 2),
new CashBox(4, new BigDecimal(500), 2));
我的任务
Calculate the total amount for each merchant and return the merchant list
我正在尝试使用 Stream API 解决此任务。并编写了以下代码:
List<Merchant> merchant = cashBoxes.stream()
.map(merch -> new Merchant(
merch.getMerchantId(),
cashBoxes.stream()
.filter(cashBox -> cashBox.getMerchantId() == merch.getMerchantId())
.map(CashBox::getTotalAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add)))
.collect(Collectors.toList());
结果
[Merchant{merchantId=1, totalAmount=3000}, Merchant{merchantId=1, totalAmount=3000}, Merchant{merchantId=2, totalAmount=3500}, Merchant{merchantId=2, totalAmount=3500}]
但显然,流 returns 四个对象而不是所需的两个。
我意识到,地图(第二行)为每个 cashBoxId 创建了四个对象。而且我不知道如何按 merchantId
过滤或获得没有重复的结果。
一种方法是使用 groupingBy
。按商户 ID 分组,然后为每个组映射到总量,并使用相应的收集器进行归约。这将为您提供包含商家 ID 和总金额的 Map<Long, BigDecimal>
。然后您可以将此地图的每个条目映射到商家:
cashBoxes.stream().collect(Collectors.groupingBy(
CashBox::getMerchantId, // group by merchant Id
// for each group...
Collectors.mapping(// map to total amount
CashBox::getTotalAmount,
Collectors.reducing(BigDecimal.ZERO, BigDecimal::add) // sum
)
)).entrySet().stream()
.map(x -> new Merchant(x.getKey(), x.getValue())) // map to merchant
.collect(Collectors.toList());
有
这里是使用单个 Stream 的“单行”。这个想法是利用使用 Stream::toMap
分组的优势,使用 Function<CashBox, Merchant>
映射值并使用 BinaryOperator<Merchant>
:
Collection<Merchant> merchants = cashBoxes.stream()
.collect(Collectors.toMap(
CashBox::getMerchantId,
cashBox -> new Merchant(cashBox.getMerchantId(), cashBox.getTotalAmount()),
(l, r) -> {
l.setTotalAmount(l.getTotalAmount().add(r.getTotalAmount()));
return l;
}
))
.values();