Mam listę users i chcę znaleźć wszystkich użytkowników o zduplikowanych nazwach:

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());

Czy mogę ulepszyć / uprościć powyższe rozwiązanie?

Na przykład w rzeczywistości tworzę listę ze wszystkimi nazwami, a następnie ją filtruję. Jak przeglądać listę w celu znalezienia zduplikowanych nazw bez tworzenia dodatkowej listy allNames?

3
nimo23 3 kwiecień 2020, 15:01

3 odpowiedzi

Najlepsza odpowiedź

Jedno rozwiązanie to

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());

Tworzy to pośredni Map<String,Boolean> do rejestrowania, która nazwa występuje więcej niż raz. Możesz użyć keySet() tej mapy zamiast zbierać do nowej 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();
            }));

Rozwiązanie pętli może być znacznie prostsze:

HashSet<String> seen = new HashSet<>(), duplicate = new HashSet<>();
for(User u: users)
    if(!seen.add(u.getName())) duplicate.add(u.getName());
6
Holger 3 kwiecień 2020, 12:24

Grupuj według nazw, znajdź wpisy z więcej niż jedną wartością:

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());

(Możesz to zrobić jednym wyrażeniem, jeśli chcesz. Oddzieliłem tylko kroki, aby było trochę bardziej jasne, co się dzieje).

Zwróć uwagę, że nie zachowuje to kolejności użytkowników z oryginalnej listy.

2
Andy Turner 3 kwiecień 2020, 12:09

Rozwiązanie znajduję za pomocą @holger:

// collect all duplicate names with O(n)
var duplicateNames = all.stream()
                .collect(Collectors.groupingBy(Strategy::getName, Collectors.counting()))
                .entrySet()
                .stream()
                .filter(m -> m.getValue() > 1)
                .map(m -> m.getKey())
                .collect(Collectors.toList());

Czy wydajność tego rozwiązania jest O (n ^ 2) czy O (n)?

Jeśli ktoś może znaleźć ulepszenia, podziel się nimi.

1
nimo23 3 kwiecień 2020, 12:22