Java 8 多键分组
Java 8 Grouping with Multiple Keys
我正在寻求一些帮助,以对具有多个键的列表中的对象列表进行分组。
基本上,我有一个用户列表,其中包含他们的订单列表,我希望能够使用用户名和地址作为键将他们组合在一起。
示例数据:
<UserList>
<User>
<Username>user123</Username>
<Address>London</Address>
<TransactionList>
<TransactionList>
<OrderNumber>1</OrderNumber>
<Cost>3683446.6600</Cost>
</TransactionList>
</TransactionList>
</User>
<User>
<Username>user123</Username>
<Address>London</Address>
<TransactionList>
<TransactionList>
<OrderNumber>3</OrderNumber>
<Cost>500</Cost>
</TransactionList>
</TransactionList>
</User>
<User>
<Username>user12356</Username>
<Address>Manchester</Address>
<TransactionList>
<TransactionList>
<OrderNumber>6</OrderNumber>
<Cost>90000</Cost>
</TransactionList>
</TransactionList>
</User>
<User>
<Username>user12356</Username>
<Address>Manchester</Address>
<TransactionList>
<TransactionList>
<OrderNumber>10</OrderNumber>
<Cost>100</Cost>
</TransactionList>
</TransactionList>
</User>
</UserList>
我想这样排序,这样每个用户只有一个实例,它的相关交易根据用户名和地址分组在一个列表中:
<UserList>
<User>
<Username>user123</Username>
<Address>London</Address>
<TransactionList>
<TransactionList>
<OrderNumber>1</OrderNumber>
<Cost>3683446.6600</Cost>
</TransactionList>
<TransactionList>
<OrderNumber>3</OrderNumber>
<Cost>500</Cost>
</TransactionList>
</TransactionList>
</User>
<User>
<Username>user12356</Username>
<Address>Manchester</Address>
<TransactionList>
<TransactionList>
<OrderNumber>6</OrderNumber>
<Cost>90000</Cost>
</TransactionList>
<TransactionList>
<OrderNumber>10</OrderNumber>
<Cost>100</Cost>
</TransactionList>
</TransactionList>
</User>
</UserList>
我试过用地图来做:
Map<String, Map<String,List<User>>> map;
map = userLists.getUserList().stream()
.collect(Collectors.groupingBy(User::getUserName, Collectors.groupingBy(User::getAddress)));
这有点像我想要做的,但我想知道是否有更好的方法使用 MultiKey Map 或类似的东西然后迭代并在键匹配时放置一个事务列表。
在此先感谢您的帮助。
你的做法没有错,第一步是正确的:先按userName
分组,再按address
分组。就个人而言,我会反转它,因为更有可能有更多用户使用相同的地址,但顺序对于获得正确的结果并不重要。
我注意到预期的输出看起来像 List<User>
,减少了具有共同特征(userName
和 address
)的用户和串联的 transactionList
列表。尽管带有复合键的映射(例如 ${userName}_${address}
)可能看起来很有帮助,但我宁愿选择 List<User>
,顺便说一下,它符合预期的输出。
所以,第二步是迭代所有这些条目,并将Map
中的List<User>
减少为单个用户。每个 inner 条目将与一个用户相关联(因为 userName
和 address
)。 For-each 非常适合这项任务。给你:
Map<String, Map<String,List<User>>> map = userLists.stream()
.collect(Collectors.groupingBy(
User::getUserName,
Collectors.groupingBy(User::getAddress)));
List<User> list = new ArrayList<>(); // stored output
map.forEach((userName, groupedByAddress) -> { // for each 'userName'
groupedByAddress.forEach((address, users) -> { // ... and each 'address'
User userToAdd = new User(); // ...... create an 'User'
userToAdd.setUserName(userName); // ...... with the 'userName'
userToAdd.setAddress(address); // ...... with the 'address'
users.forEach(user -> { // ...... and of each the user's
userToAdd.getTransactionList() // .......... concatenate
.addAll(user.getTransactionList()); // .......... all the transactions
});
});
});
Stream API 很适合分组以获得中间结果,但是,对于这种进一步的缩减,它会非常笨拙。
您要找的其实是一种基于用户名和地址通用的合并功能。如果您查看 Collectors.toMap
API,它会为您提供类似的功能以及 Map
.
的键和值选择
Collectors.toMap(
u -> Arrays.asList(u.getUserName(), u.getAddress()),
Function.identity(),
User::mergeUsers
));
合并的位置看起来像您期望的那样
static User mergeUsers(User user1, User user2) {
List<Transaction> overall = new ArrayList<>(user1.getTransactionList());
overall.addAll(user2.getTransactionList());
return new User(user1.getUserName(), user1.getAddress(), overall);
}
并且因为您只是在寻找这个 Map
的值,您的完整解决方案的输出将是:
Collection<User> users = userLists.getUserList().stream()
.collect(Collectors.toMap(
u -> Arrays.asList(u.getUserName(), u.getAddress()),
Function.identity(),
User::mergeUsers
)).values();
我正在寻求一些帮助,以对具有多个键的列表中的对象列表进行分组。
基本上,我有一个用户列表,其中包含他们的订单列表,我希望能够使用用户名和地址作为键将他们组合在一起。
示例数据:
<UserList>
<User>
<Username>user123</Username>
<Address>London</Address>
<TransactionList>
<TransactionList>
<OrderNumber>1</OrderNumber>
<Cost>3683446.6600</Cost>
</TransactionList>
</TransactionList>
</User>
<User>
<Username>user123</Username>
<Address>London</Address>
<TransactionList>
<TransactionList>
<OrderNumber>3</OrderNumber>
<Cost>500</Cost>
</TransactionList>
</TransactionList>
</User>
<User>
<Username>user12356</Username>
<Address>Manchester</Address>
<TransactionList>
<TransactionList>
<OrderNumber>6</OrderNumber>
<Cost>90000</Cost>
</TransactionList>
</TransactionList>
</User>
<User>
<Username>user12356</Username>
<Address>Manchester</Address>
<TransactionList>
<TransactionList>
<OrderNumber>10</OrderNumber>
<Cost>100</Cost>
</TransactionList>
</TransactionList>
</User>
</UserList>
我想这样排序,这样每个用户只有一个实例,它的相关交易根据用户名和地址分组在一个列表中:
<UserList>
<User>
<Username>user123</Username>
<Address>London</Address>
<TransactionList>
<TransactionList>
<OrderNumber>1</OrderNumber>
<Cost>3683446.6600</Cost>
</TransactionList>
<TransactionList>
<OrderNumber>3</OrderNumber>
<Cost>500</Cost>
</TransactionList>
</TransactionList>
</User>
<User>
<Username>user12356</Username>
<Address>Manchester</Address>
<TransactionList>
<TransactionList>
<OrderNumber>6</OrderNumber>
<Cost>90000</Cost>
</TransactionList>
<TransactionList>
<OrderNumber>10</OrderNumber>
<Cost>100</Cost>
</TransactionList>
</TransactionList>
</User>
</UserList>
我试过用地图来做:
Map<String, Map<String,List<User>>> map;
map = userLists.getUserList().stream()
.collect(Collectors.groupingBy(User::getUserName, Collectors.groupingBy(User::getAddress)));
这有点像我想要做的,但我想知道是否有更好的方法使用 MultiKey Map 或类似的东西然后迭代并在键匹配时放置一个事务列表。
在此先感谢您的帮助。
你的做法没有错,第一步是正确的:先按userName
分组,再按address
分组。就个人而言,我会反转它,因为更有可能有更多用户使用相同的地址,但顺序对于获得正确的结果并不重要。
我注意到预期的输出看起来像 List<User>
,减少了具有共同特征(userName
和 address
)的用户和串联的 transactionList
列表。尽管带有复合键的映射(例如 ${userName}_${address}
)可能看起来很有帮助,但我宁愿选择 List<User>
,顺便说一下,它符合预期的输出。
所以,第二步是迭代所有这些条目,并将Map
中的List<User>
减少为单个用户。每个 inner 条目将与一个用户相关联(因为 userName
和 address
)。 For-each 非常适合这项任务。给你:
Map<String, Map<String,List<User>>> map = userLists.stream()
.collect(Collectors.groupingBy(
User::getUserName,
Collectors.groupingBy(User::getAddress)));
List<User> list = new ArrayList<>(); // stored output
map.forEach((userName, groupedByAddress) -> { // for each 'userName'
groupedByAddress.forEach((address, users) -> { // ... and each 'address'
User userToAdd = new User(); // ...... create an 'User'
userToAdd.setUserName(userName); // ...... with the 'userName'
userToAdd.setAddress(address); // ...... with the 'address'
users.forEach(user -> { // ...... and of each the user's
userToAdd.getTransactionList() // .......... concatenate
.addAll(user.getTransactionList()); // .......... all the transactions
});
});
});
Stream API 很适合分组以获得中间结果,但是,对于这种进一步的缩减,它会非常笨拙。
您要找的其实是一种基于用户名和地址通用的合并功能。如果您查看 Collectors.toMap
API,它会为您提供类似的功能以及 Map
.
Collectors.toMap(
u -> Arrays.asList(u.getUserName(), u.getAddress()),
Function.identity(),
User::mergeUsers
));
合并的位置看起来像您期望的那样
static User mergeUsers(User user1, User user2) {
List<Transaction> overall = new ArrayList<>(user1.getTransactionList());
overall.addAll(user2.getTransactionList());
return new User(user1.getUserName(), user1.getAddress(), overall);
}
并且因为您只是在寻找这个 Map
的值,您的完整解决方案的输出将是:
Collection<User> users = userLists.getUserList().stream()
.collect(Collectors.toMap(
u -> Arrays.asList(u.getUserName(), u.getAddress()),
Function.identity(),
User::mergeUsers
)).values();