Java 8多键分组

huangapple go评论72阅读模式
英文:

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:

&lt;UserList&gt;
        &lt;User&gt;
            &lt;Username&gt;user123&lt;/Username&gt;
            &lt;Address&gt;London&lt;/Address&gt;
            &lt;TransactionList&gt;
                &lt;TransactionList&gt;
                    &lt;OrderNumber&gt;1&lt;/OrderNumber&gt;
                    &lt;Cost&gt;3683446.6600&lt;/Cost&gt;
                &lt;/TransactionList&gt;
            &lt;/TransactionList&gt;
        &lt;/User&gt;
              &lt;User&gt;
            &lt;Username&gt;user123&lt;/Username&gt;
            &lt;Address&gt;London&lt;/Address&gt;
            &lt;TransactionList&gt;
                &lt;TransactionList&gt;
                    &lt;OrderNumber&gt;3&lt;/OrderNumber&gt;
                    &lt;Cost&gt;500&lt;/Cost&gt;
                &lt;/TransactionList&gt;
            &lt;/TransactionList&gt;
        &lt;/User&gt;
               &lt;User&gt;
            &lt;Username&gt;user12356&lt;/Username&gt;
            &lt;Address&gt;Manchester&lt;/Address&gt;
            &lt;TransactionList&gt;
                &lt;TransactionList&gt;
                    &lt;OrderNumber&gt;6&lt;/OrderNumber&gt;
                    &lt;Cost&gt;90000&lt;/Cost&gt;
                &lt;/TransactionList&gt;
            &lt;/TransactionList&gt;
        &lt;/User&gt;
              &lt;User&gt;
            &lt;Username&gt;user12356&lt;/Username&gt;
            &lt;Address&gt;Manchester&lt;/Address&gt;
            &lt;TransactionList&gt;
                &lt;TransactionList&gt;
                    &lt;OrderNumber&gt;10&lt;/OrderNumber&gt;
                    &lt;Cost&gt;100&lt;/Cost&gt;
                &lt;/TransactionList&gt;
            &lt;/TransactionList&gt;
        &lt;/User&gt;
	&lt;/UserList&gt;

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:

&lt;UserList&gt;
        &lt;User&gt;
            &lt;Username&gt;user123&lt;/Username&gt;
            &lt;Address&gt;London&lt;/Address&gt;
            &lt;TransactionList&gt;
                &lt;TransactionList&gt;
                    &lt;OrderNumber&gt;1&lt;/OrderNumber&gt;
                    &lt;Cost&gt;3683446.6600&lt;/Cost&gt;
                &lt;/TransactionList&gt;
				&lt;TransactionList&gt;
                    &lt;OrderNumber&gt;3&lt;/OrderNumber&gt;
                    &lt;Cost&gt;500&lt;/Cost&gt;
                &lt;/TransactionList&gt;
            &lt;/TransactionList&gt;
        &lt;/User&gt;
         &lt;User&gt;
            &lt;Username&gt;user12356&lt;/Username&gt;
            &lt;Address&gt;Manchester&lt;/Address&gt;
            &lt;TransactionList&gt;
                &lt;TransactionList&gt;
                    &lt;OrderNumber&gt;6&lt;/OrderNumber&gt;
                    &lt;Cost&gt;90000&lt;/Cost&gt;
                &lt;/TransactionList&gt;
				 &lt;TransactionList&gt;
                    &lt;OrderNumber&gt;10&lt;/OrderNumber&gt;
                    &lt;Cost&gt;100&lt;/Cost&gt;
                &lt;/TransactionList&gt;
            &lt;/TransactionList&gt;
        &lt;/User&gt;
	&lt;/UserList&gt;	

I have tried to do it using a map:

Map&lt;String, Map&lt;String,List&lt;User&gt;&gt;&gt; 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

你的方法并没有错,第一步是正确的:按userNameaddress对用户进行分组。个人而言,我可能会反过来做,因为很可能有更多具有相同地址的用户,但从实现正确结果的角度来看,顺序并不重要。

我注意到预期的输出类似于带有共同特征(userNameaddress)的List<User>,以及连接的transactionList列表。虽然使用具有复合键的映射(例如${userName}_${address})可能会很有帮助,但我更愿意选择List<User>,顺便说一句,这与预期的输出相符。

因此,第二步是遍历所有这些条目,并将Map中的List<User>减少为单个用户。每个内部条目都将与单个用户关联(因为同时有userNameaddress)。对于这个任务来说,使用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&lt;User&gt; 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&lt;User&gt;, 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&lt;User&gt; 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&lt;String, Map&lt;String,List&lt;User&gt;&gt;&gt; map = userLists.stream()
     .collect(Collectors.groupingBy(
            User::getUserName, 
            Collectors.groupingBy(User::getAddress)));

List&lt;User&gt; list = new ArrayList&lt;&gt;();                 // stored output

map.forEach((userName, groupedByAddress) -&gt; {        // for each &#39;userName&#39;
    groupedByAddress.forEach((address, users) -&gt; {   // ... and each &#39;address&#39;
        User userToAdd = new User();                 // ...... create an &#39;User&#39;
        userToAdd.setUserName(userName);             // ...... with the &#39;userName&#39;  
        userToAdd.setAddress(address);               // ...... with the &#39;address&#39;
        users.forEach(user -&gt; {                      // ...... and of each the user&#39;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 -&gt; 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&lt;Transaction&gt; overall = new ArrayList&lt;&gt;(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&lt;User&gt; users = userLists.getUserList().stream()
        .collect(Collectors.toMap(
                u -&gt; Arrays.asList(u.getUserName(), u.getAddress()),
                Function.identity(),
                User::mergeUsers
        )).values();

huangapple
  • 本文由 发表于 2020年10月21日 23:42:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/64467151.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定