[英]How to merge two groupingBy in java streams?
我有一个输入 object
@Getter
class Txn {
private String hash;
private String withdrawId;
private String depositId;
private Integer amount;
private String date;
}
output object 是
@Builder
@Getter
class UserTxn {
private String hash;
private String walletId;
private String txnType;
private Integer amount;
}
在Txn object 中从withdrawId -> depositId转移金额。
我正在做的是将所有交易(Txn 对象)添加到按 hash 分组的单个金额中。
但是为此,我必须创建两个流用于通过 withdrawId 进行分组,第二个或用于 depositId,然后是第三个 stream 用于合并它们
按 withdrawId 分组
var withdrawStream = txnList.stream().collect(Collectors.groupingBy(Txn::getHash, LinkedHashMap::new,
Collectors.groupingBy(Txn::getWithdrawId, LinkedHashMap::new, Collectors.toList())))
.entrySet().stream().flatMap(hashEntrySet -> hashEntrySet.getValue().entrySet().stream()
.map(withdrawEntrySet ->
UserTxn.builder()
.hash(hashEntrySet.getKey())
.walletId(withdrawEntrySet.getKey())
.txnType("WITHDRAW")
.amount(withdrawEntrySet.getValue().stream().map(Txn::getAmount).reduce(0, Integer::sum))
.build()
));
按 depositId 分组
var depositStream = txnList.stream().collect(Collectors.groupingBy(Txn::getHash, LinkedHashMap::new,
Collectors.groupingBy(Txn::getDepositId, LinkedHashMap::new, Collectors.toList())))
.entrySet().stream().flatMap(hashEntrySet -> hashEntrySet.getValue().entrySet().stream()
.map(withdrawEntrySet ->
UserTxn.builder()
.hash(hashEntrySet.getKey())
.walletId(withdrawEntrySet.getKey())
.txnType("DEPOSIT")
.amount(withdrawEntrySet.getValue().stream().map(Txn::getAmount).reduce(0, Integer::sum))
.build()
));
然后再次合并它们,使用存款 - 取款
var res = Stream.concat(withdrawStream, depositStream).collect(Collectors.groupingBy(UserTxn::getHash, LinkedHashMap::new,
Collectors.groupingBy(UserTxn::getWalletId, LinkedHashMap::new, Collectors.toList())))
.entrySet().stream().flatMap(hashEntrySet -> hashEntrySet.getValue().entrySet().stream()
.map(withdrawEntrySet -> {
var depositAmount = withdrawEntrySet.getValue().stream().filter(userTxn -> userTxn.txnType.equals("DEPOSIT")).map(UserTxn::getAmount).reduce(0, Integer::sum);
var withdrawAmount = withdrawEntrySet.getValue().stream().filter(userTxn -> userTxn.txnType.equals("WITHDRAW")).map(UserTxn::getAmount).reduce(0, Integer::sum);
var totalAmount = depositAmount-withdrawAmount;
return UserTxn.builder()
.hash(hashEntrySet.getKey())
.walletId(withdrawEntrySet.getKey())
.txnType(totalAmount > 0 ? "DEPOSIT": "WITHDRAW")
.amount(totalAmount)
.build();
}
));
我的问题是,如何在一个 stream 中执行此操作。就像以某种方式分组一样,withdrawId 和 depositId 是一个分组。
就像是
res = txnList.stream()
.collect(Collectors.groupingBy(Txn::getHash,
LinkedHashMap::new,
Collectors.groupingBy(Txn::getWithdrawId && Txn::getDepositId,
LinkedHashMap::new, Collectors.toList())))
.entrySet().stream().flatMap(hashEntrySet -> hashEntrySet.getValue().entrySet().stream()
.map(walletEntrySet ->
{
var totalAmount = walletEntrySet.getValue().stream().map(
txn -> Objects.equals(txn.getDepositId(), walletEntrySet.getKey())
? txn.getAmount() : (-txn.getAmount())).reduce(0, Integer::sum);
return UserTxn.builder()
.hash(hashEntrySet.getKey())
.walletId(walletEntrySet.getKey())
.txnType("WITHDRAW")
.amount(totalAmount)
.build();
}
));
我不会在我的代码中使用它,因为我认为它不可读并且将来很难更改和管理(SOLID)。 但如果你仍然想要这个 -
如果我的设计正确,每个用户 hash 是唯一的,交易将只有存款或取款,如果是这样,这将起作用-
您可以像在示例中那样通过收集器链接将 groupBy 三倍。 您可以通过简单的 map function 创建Txn
类型,只需检查哪个 ID 是 null。
Map<String, Map<String, Map<String, List<Txn>>>> groupBy =
txnList.stream()
.collect(Collectors.groupingBy(Txn::getHash, LinkedHashMap::new,
Collectors.groupingBy(Txn::getDepositId, LinkedHashMap::new,
Collectors.groupingBy(Txn::getWithdrawId, LinkedHashMap::new, Collectors.toList()))));
然后在这个 stream 上使用您示例中的逻辑。
对于那些没有理解问题的人,OP 想要从每个Txn
实例( Txn
可能代表交易)生成两个数据和平: hash
和withdrawId
+ 聚合金额,以及hash
和depositId
+ 聚合金额。
然后他们想将这两个部分合并在一起(因此他们创建了两个流,然后将它们连接起来)。
注意:原始代码中似乎有一个逻辑流程:与withdrawId
和depositId
相关联的金额相同。 这并不反映该金额已从一个帐户中提取并转移到另一个帐户。 因此,如果depositId
金额按原样使用,而withdrawId
- 否定(即-1 * amount
),这将是有意义的。
Collectors.teeing()
您可以使用 Java 12 Collector teeing teeing()
并将 stream 元素内部分组为两个不同的地图:
第一个通过withdrawId
和hash
对 stream 数据进行分组。
另一个通过对数据depositId
和hash
进行分组。
Teeing 期望三个 arguments:2 个下游收集器和一个 Function 组合收集器产生的结果。
作为 teeing( teeing()
的下游,我们可以使用 Collectors groupingBy()
和summingInt()
的组合,第二个需要累积 integer 笔交易amount
。
请注意,没有必要使用嵌套的 Collector groupingBy()
相反,我们可以创建一个自定义类型来保存id
和hash
(并且它的equals/hashCode
应该基于包装的 id 和 hash 来实现)。 Java 16 条record
非常适合这个角色:
public record HashWalletId(String hash, String walletId) {}
HashWalletId
的实例将用作两个中间映射中的密钥。
teeing teeing()
的终结者 function会将两个 Maps 的结果合并在一起。
唯一剩下的就是从 map 个条目中生成UserTxn
的实例。
List<Txn> txnList = // initializing the list
List<UserTxn> result = txnList.stream()
.collect(Collectors.teeing(
Collectors.groupingBy(
txn -> new HashWalletId(txn.getHash(), txn.getWithdrawId()),
Collectors.summingInt(txn -> -1 * txn.getAmount())), // because amount has been withdrawn
Collectors.groupingBy(
txn -> new HashWalletId(txn.getHash(), txn.getDepositId()),
Collectors.summingInt(Txn::getAmount)),
(map1, map2) -> {
map2.forEach((k, v) -> map1.merge(k, v, Integer::sum));
return map1;
}
))
.entrySet().stream()
.map(entry -> UserTxn.builder()
.hash(entry.getKey().hash())
.walletId(entry.getKey().walletId())
.txnType(entry.getValue() > 0 ? "DEPOSIT" : "WITHDRAW")
.amount(entry.getValue())
.build()
)
.toList(); // remove the terminal operation if your goal is to produce a Stream
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.