2

我正在寻找一些帮助来对具有多个键的列表中的对象列表进行分组。

基本上,我有一个用户列表,其中包含他们的订单列表,我希望能够使用 UserName 和 Address 作为键将它们组合在一起。

示例数据:

<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 或类似的东西,然后如果密钥匹配,则遍历并放置一个事务列表。

在此先感谢您的帮助。

4

2 回答 2

3

您的方法没有错,第一步是正确的:按userName然后对用户进行分组address。就个人而言,我会倒置它,因为更有可能有更多用户使用相同的地址,但就实现正确结果而言,顺序并不重要。

我注意到预期的输出看起来像List<User>具有共同特征 (userNameaddress) 和串联transactionList列表的减少用户。尽管带有复合键(例如${userName}_${address})的映射可能看起来很有帮助,但我宁愿选择List<User>,顺便说一下,它符合预期的输出。

因此,第二步是迭代所有这些条目并List<User>Map单个用户中减少。每个内部条目将与单个用户相关联(因为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<>();                 // 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 非常适合分组以获得中间结果,但是,对于这样的进一步减少,它将非常笨拙。

于 2020-10-21T16:16:23.683 回答
1

您正在寻找的实际上是基于普通用户的姓名和地址的合并功能。如果您查看Collectors.toMapAPI,它会为您提供类似的功能以及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);
}

并且由于您只是在寻找 this 的值,因此Map具有完整解决方案的输出将是:

Collection<User> users = userLists.getUserList().stream()
        .collect(Collectors.toMap(
                u -> Arrays.asList(u.getUserName(), u.getAddress()),
                Function.identity(),
                User::mergeUsers
        )).values();
于 2020-10-21T16:42:23.370 回答