英文:
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<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());
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<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();
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 Set
s, 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 Set
s, 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<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 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<User> result = new ArrayList<>(
Stream.concat(users.stream(), updatedUsers.stream())
.collect(Collectors.toMap(User::getId,
Function.identity(),
(first,second) -> 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<Integer, User> userMap = new HashMap<>();
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<User> userList = new ArrayList<>(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 import
ing static
. Also, rather than iterating each collection to add to a combined Map
, you could use the combining Map
as a Supplier
:
Map<Integer, User> updatedUsersMap = updatedUsers.stream().
collect(toMap(
User::getId,
identity(),
(k, v) ->v,
()-> { return usersMap; }));
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论