I have a users
list and I want to find all users having duplicate names:
var allNames = users
.stream()
.map(u -> u.getName()).collect(Collectors.toList());
var duplicateNames = allNames
.stream()
.filter(i -> Collections.frequency(allNames, i) > 1)
.collect(Collectors.toSet());
Can I improve/simplify the above solution?
For example, actually I create a list with all names and then filter it. How can I traverse the list to find its duplicate names without creating the additional list allNames
?
One solution is
var duplicate = users.stream()
.collect(Collectors.toMap(User::getName, u -> false, (x,y) -> true))
.entrySet().stream()
.filter(Map.Entry::getValue)
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
This creates an intermediate Map<String,Boolean>
to record which name is occurring more than once. You could use the keySet()
of that map instead of collecting to a new Set
:
var duplicate = users.stream()
.collect(Collectors.collectingAndThen(
Collectors.toMap(User::getName, u -> false, (x,y) -> true, HashMap::new),
m -> {
m.values().removeIf(dup -> !dup);
return m.keySet();
}));
A loop solution can be much simpler:
HashSet<String> seen = new HashSet<>(), duplicate = new HashSet<>();
for(User u: users)
if(!seen.add(u.getName())) duplicate.add(u.getName());
Group by the names, find entries with more than one value:
Map<String, List<User>> grouped = users.stream()
.collect(groupingBy(User::getName));
List<User> duplicated =
grouped.values().stream()
.filter(v -> v.size() > 1)
.flatMap(List::stream)
.collect(toList());
(You can do this in a single expression if you want. I only separated the steps to make it a little more clear what is happening).
Note that this does not preserve the order of the users from the original list.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With