Merges account records using Disjoint Set Union (Union-Find) on shared emails. Input format: each account is a list where the first element is the user name and the remaining elements are emails.
| 14 | * remaining elements are emails. |
| 15 | */ |
| 16 | public final class AccountMerge { |
| 17 | private AccountMerge() { |
| 18 | } |
| 19 | |
| 20 | public static List<List<String>> mergeAccounts(List<List<String>> accounts) { |
| 21 | if (accounts == null || accounts.isEmpty()) { |
| 22 | return List.of(); |
| 23 | } |
| 24 | |
| 25 | UnionFind dsu = new UnionFind(accounts.size()); |
| 26 | Map<String, Integer> emailToAccount = new HashMap<>(); |
| 27 | |
| 28 | for (int i = 0; i < accounts.size(); i++) { |
| 29 | List<String> account = accounts.get(i); |
| 30 | for (int j = 1; j < account.size(); j++) { |
| 31 | String email = account.get(j); |
| 32 | Integer previous = emailToAccount.putIfAbsent(email, i); |
| 33 | if (previous != null) { |
| 34 | dsu.union(i, previous); |
| 35 | } |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | Map<Integer, List<String>> rootToEmails = new LinkedHashMap<>(); |
| 40 | for (Map.Entry<String, Integer> entry : emailToAccount.entrySet()) { |
| 41 | int root = dsu.find(entry.getValue()); |
| 42 | rootToEmails.computeIfAbsent(root, ignored -> new ArrayList<>()).add(entry.getKey()); |
| 43 | } |
| 44 | for (int i = 0; i < accounts.size(); i++) { |
| 45 | if (accounts.get(i).size() <= 1) { |
| 46 | int root = dsu.find(i); |
| 47 | rootToEmails.computeIfAbsent(root, ignored -> new ArrayList<>()); |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | List<List<String>> merged = new ArrayList<>(); |
| 52 | for (Map.Entry<Integer, List<String>> entry : rootToEmails.entrySet()) { |
| 53 | int root = entry.getKey(); |
| 54 | List<String> emails = entry.getValue(); |
| 55 | Collections.sort(emails); |
| 56 | |
| 57 | List<String> mergedAccount = new ArrayList<>(); |
| 58 | mergedAccount.add(accounts.get(root).getFirst()); |
| 59 | mergedAccount.addAll(emails); |
| 60 | merged.add(mergedAccount); |
| 61 | } |
| 62 | |
| 63 | merged.sort((a, b) -> { |
| 64 | int cmp = a.getFirst().compareTo(b.getFirst()); |
| 65 | if (cmp != 0) { |
| 66 | return cmp; |
| 67 | } |
| 68 | if (a.size() == 1 || b.size() == 1) { |
| 69 | return Integer.compare(a.size(), b.size()); |
| 70 | } |
| 71 | return a.get(1).compareTo(b.get(1)); |
| 72 | }); |
| 73 | return merged; |
nothing calls this directly
no outgoing calls
no test coverage detected