使用流来合并两个具有更新值的列表。

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

Using stream to merge two list with updated values

问题

有没有更加优雅的方法将两个列表合并成第三个列表,如果值已更新,则从其中一个列表获取值并从另一个列表获取值?

为了更清楚,让我们考虑我有两个用户列表:

List<User> users = Arrays.asList(
    User.builder()
        .id(1).surname("john").build(),
    User.builder()
        .id(2).surname("paul").build(),
    User.builder()
        .id(3).surname("george").build(),
    User.builder()
        .id(4).surname("walter").build());

List<User> updatedUsers = Arrays.asList(
    User.builder()
        .id(1).surname("john").email("myEmail@email.com").build(),
    User.builder()
        .id(2).surname("paul").email("myEmail@email.com").build());

我期望得到一个包含所有用户和已更新值替换的共同用户的列表。

我使用以下代码来实现预期的输出:

Map<Integer, User> usersMap = users.stream()
    .collect(Collectors.toMap(User::getId, Function.identity()));
Map<Integer, User> updatedUsersMap = updatedUsers.stream()
    .collect(Collectors.toMap(User::getId, Function.identity()));

usersMap.putAll(updatedUsersMap);
List<User> output = usersMap.values().stream().toList();

我得到了预期的结果,只是想知道是否有另一种使用流更高效和更优雅的方法。谢谢!

英文:

Is there a more elegant way to merge two lists in a 3rd one which take values from one and take values from second if the value is updated ?

To be clearer, let's consider I have 2 lists of users:

List&lt;User&gt; users = Arrays.asList(
        User.builder()
            .id(1).surname(&quot;john&quot;).build(),
        User.builder()
            .id(2).surname(&quot;paul&quot;).build(),
        User.builder()
            .id(3).surname(&quot;george&quot;).build(),
        User.builder()
            .id(4).surname(&quot;walter&quot;).build());


List&lt;User&gt; updatedUsers = Arrays.asList(
        User.builder()
            .id(1).surname(&quot;john&quot;).email(&quot;myEmail@email.com&quot;).build(),
        User.builder()
            .id(2).surname(&quot;paul&quot;).email(&quot;myEmail@email.com&quot;).build());

I'm expecting to get a list containing all users and common users replaced by the updated values.

I used the following code to implement the expected output:

Map&lt;Integer, User&gt; usersMap = users.stream()
        .collect(Collectors.toMap(User::getId, Function.identity()));
Map&lt;Integer, User&gt; updatedUsersMap = updatedUsers.stream()
        .collect(Collectors.toMap(User::getId, Function.identity()));


usersMap.putAll(updatedUsersMap);
List&lt;User&gt; output = usersMap.values().stream().toList();

I do have the expected result and I just wonder if there's another way to do it using stream, more efficient and elegant.
Thanks !

答案1

得分: 2

Your solution seems efficient enough to me, what is elegant might be matter of subject. Developers often use List when Set is a more suitable collection (do you really want to access users by index?).

If the collections were Sets, User.equals() method was based only on the id field, and you want to emphasize the data of updatedUsers override the base data, you could describe the result using set operations (yes, these are not part of standard Java - I borrow them from Guava or you can use any other implementation).

Set<Users> usersSet = new HashSet<>(users);
Set<Users> updatedUsersSet = new HashSet<>(updatedUsers);
Set<Users> output = Sets.union(updatedUsersSet, Sets.difference(usersSet, updatedUsersSet));

The problem is shifted to a higher level of abstraction which I admit not everyone must like.

英文:

Your solution seems efficient enough to me, what is elegant might be matter of subject. Developers often use List when Set is more suitable collection (do you really want to access users by index?).

If the collections were Sets, User.equals() method was based only on the id field, and you want to emphasize the data of updatedUsers override the base data, you could describe result using set operations (yes, these are not part of standard Java - I borrow them from Guava or you can use any other implementation).

Set&lt;Users&gt; usersSet = new HashSet&lt;&gt;(users);
Set&lt;Users&gt; updatedUsersSet = new HashSet&lt;&gt;(updatedUsers);
Set&lt;Users&gt; output = Sets.union(updatedUsersSet, Sets.difference(usersSet, updatedUsersSet));

The problem is shifted to higher level of abstraction which I admit not everyone must like.

答案2

得分: 1

你可以在一次操作中完成,而不需要创建中间映射,只需连接用户流和更新后的用户流,如果遇到重复的键,则收集第二个值:

List<User> result = new ArrayList<>(
        Stream.concat(users.stream(), updatedUsers.stream())
              .collect(Collectors.toMap(User::getId, 
                                        Function.identity(),
                                        (first, second) -> second)).values());
英文:

You can do it in one go without creating intermediate maps if you concat the stream of users and updated users and collecting the second value if you encounter a duplicate key:

List&lt;User&gt; result = new ArrayList&lt;&gt;(
        Stream.concat(users.stream(), updatedUsers.stream())
              .collect(Collectors.toMap(User::getId, 
                                        Function.identity(),
                                        (first,second) -&gt; second)).values());

答案3

得分: 0

流在许多情况下非常有用。 但它们也会产生相当多的开销。 只需使用少量的命令式编程来执行相同的操作。

您正在创建一个新用户并将其放入列表中。 繁重的工作已经完成。 由于已创建了更新后的用户,并且无法确定用户是新用户还是更新后的用户,只需正常将其添加到映射中。 它将被添加为新用户或替换现有用户。

现在,您可以通过用户ID获取更新后的映射条目。

注意:在创建User实例时,您可能希望引入一些语法/不变式检查。 例如:

  • 用户ID超出范围
  • 电子邮件语法错误

您始终可以按以下方式检索用户列表。

List<User> userList = new ArrayList<>(userMap.values());

如果出于某种原因,您需要在采取行动之前验证用户是否在映射中,您可以查看用户的ID是否已经存在。

if (userMap.containsKey(userId)) {
  ...
}
英文:

Streams are very useful in many circumstances. But they also incur quite a bit of overhead. Just use a little imperative programming to do the same thing.

You are creating a new user and placing it in a list. The busy work is done. Since the updated user has been created and there is no way to tell if the user is a new user or an updated user, just add it to the map normally. It will either be added as a new user or replace an existing one.

Map&lt;Integer, User&gt; userMap = new HashMap&lt;&gt;();
 
for(User user : users) {
    userMap.put(user.getId(), user);
}
       
for(User user : updatedUsers) {
    userMap.put(user.getId(), user);
}

Now you can get the updated map entries by user id.

Note: You many want to institute some syntax/invariant checking when creating the User instance. For example:

  • user id out of range
  • email syntax error

You can always retrieve the users as a list as follows.

List&lt;User&gt; userList = new ArrayList&lt;&gt;(userMap.values());

If for some reason, you need to verify that a user is in the map before taking action, you can see if the user's id is already there.

if (userMap.containsKey(userId)) {
  ...
}

</details>



# 答案4
**得分**: 0

你可以通过`import`静态导入来使其看起来更整洁。此外,不必迭代每个集合以添加到组合的`Map`,你可以将组合的`Map`用作`Supplier`:

```java
Map<Integer, User> updatedUsersMap = updatedUsers.stream()
    .collect(Collectors.toMap(
        User::getId,
        Function.identity(),
        (k, v) -> v,
        () -> usersMap));
英文:

You could maybe give it a tidier look by importing static. Also, rather than iterating each collection to add to a combined Map, you could use the combining Map as a Supplier:

Map&lt;Integer, User&gt; updatedUsersMap = updatedUsers.stream().
    collect(toMap(
                User::getId,
                identity(),
                (k, v) -&gt;v,
                ()-&gt; { return usersMap; }));

huangapple
  • 本文由 发表于 2023年6月8日 18:38:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/76430996.html
匿名

发表评论

匿名网友

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

确定