英文:
Java 8 Grouping with Multiple Keys
问题
我正在寻求帮助,以便对具有多个键的列表中的对象进行分组。
基本上,我有一个用户列表,其中包含他们订单的列表,我希望能够使用用户名和地址作为键将它们分组在一起。
示例数据:
<UserList>
<User>
<Username>user123</Username>
<Address>London</Address>
<TransactionList>
<Transaction>
<OrderNumber>1</OrderNumber>
<Cost>3683446.6600</Cost>
</Transaction>
</TransactionList>
</User>
<!-- 其他用户数据 -->
</UserList>
我希望按照以下方式对其进行排序,以便每个用户只有一个实例,并且其相关交易根据用户名和地址分组在一个列表中:
<UserList>
<User>
<Username>user123</Username>
<Address>London</Address>
<TransactionList>
<Transaction>
<OrderNumber>1</OrderNumber>
<Cost>3683446.6600</Cost>
</Transaction>
<!-- 其他交易数据 -->
</TransactionList>
</User>
<!-- 其他用户数据 -->
</UserList>
我尝试使用Map来实现:
Map<String, Map<String, List<User>>> map;
map = userLists.getUserList().stream()
.collect(Collectors.groupingBy(User::getUserName, Collectors.groupingBy(User::getAddress)));
这大致是我想要做的,但我想知道是否有更好的方法,例如使用MultiKey Map,然后通过迭代来匹配键并放入交易列表。
提前感谢您的帮助。
英文:
I am looking for some help in grouping a list of objects from a list with multiple keys.
Basically, I have a list of users which contains a list of their orders and I want to be able to group them together using UserName and Address as the keys.
Example Data:
<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>
I want to order it like this so that each user just has one instance and its related transactions are grouped in a list based off Username and Address:
<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>
I have tried to do it using a map:
Map<String, Map<String,List<User>>> map;
map = userLists.getUserList().stream()
.collect(Collectors.groupingBy(User::getUserName, Collectors.groupingBy(User::getAddress)));
This is slightly what I am looking to do but I was wondering if there was a better way using MultiKey Map or something like that and then iterate through and put a list of transactions if the key matched.
Thanks in advance for the help.
答案1
得分: 3
你的方法并没有错,第一步是正确的:按userName
和address
对用户进行分组。个人而言,我可能会反过来做,因为很可能有更多具有相同地址的用户,但从实现正确结果的角度来看,顺序并不重要。
我注意到预期的输出类似于带有共同特征(userName
和address
)的List<User>
,以及连接的transactionList
列表。虽然使用具有复合键的映射(例如${userName}_${address}
)可能会很有帮助,但我更愿意选择List<User>
,顺便说一句,这与预期的输出相符。
因此,第二步是遍历所有这些条目,并将Map
中的List<User>
减少为单个用户。每个内部条目都将与单个用户关联(因为同时有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<>(); // 存储输出
map.forEach((userName, groupedByAddress) -> { // 对于每个 'userName'
groupedByAddress.forEach((address, users) -> { // ...以及每个 'address'
User userToAdd = new User(); // ......创建一个 'User'
userToAdd.setUserName(userName); // ......设置 'userName'
userToAdd.setAddress(address); // ......设置 'address'
users.forEach(user -> { // ......并对每个用户
userToAdd.getTransactionList() // ..........连接
.addAll(user.getTransactionList()); // ..........所有交易
});
});
});
流API非常适合分组以获得中间结果,但是对于这种进一步的减少,它会非常笨拙。
英文:
Your approach is not wrong, the first step is correct: to group the users by the userName
and then address
. Personally, I'd invert it since more likely there are more users with a same address, but the order is not important in terms of achieving a corect result.
I notice the expected output looks like List<User>
with reduced users with common characteristic (userName
and address
) and concatenated transactionList
lists. Although a map with a composite key (ex. ${userName}_${address}
) might seem helpful, I'd rather choose the List<User>
, which is in comppliant with, by the way, what the expected output is like.
So, the second step is to iterate all these entries and reduce List<User>
within the Map
into a single user. Each inner entry will be associated with a single user (because of both userName
and address
). For-each is more than suitable for this task. There you go:
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
});
});
});
The Stream API is a good for grouping to get an intermediate result, however, for such futher reduction it would be very clumsy.
答案2
得分: 1
你要寻找的是基于用户姓名和地址相同的合并能力。如果你查看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();
英文:
What you are looking for is really a merging capability based on the name and address of the users being common. If you look into the Collectors.toMap
API, it provides you a similar capability along with the key and value selection for the Map
.
Collectors.toMap(
u -> Arrays.asList(u.getUserName(), u.getAddress()),
Function.identity(),
User::mergeUsers
));
where the merge is it looks like you would expect it to be
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);
}
and since you are only looking for the values of this Map
, your output with a complete solution would be:
Collection<User> users = userLists.getUserList().stream()
.collect(Collectors.toMap(
u -> Arrays.asList(u.getUserName(), u.getAddress()),
Function.identity(),
User::mergeUsers
)).values();
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论