feat(graph): add DSU-based account merge algorithm#7377
feat(graph): add DSU-based account merge algorithm#7377nickzerjeski wants to merge 4 commits intoTheAlgorithms:masterfrom
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #7377 +/- ##
============================================
+ Coverage 79.50% 79.55% +0.05%
- Complexity 7142 7161 +19
============================================
Files 796 797 +1
Lines 23368 23426 +58
Branches 4596 4610 +14
============================================
+ Hits 18578 18636 +58
- Misses 4050 4051 +1
+ Partials 740 739 -1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds a Disjoint Set Union (Union-Find) based solution for the “merge accounts by shared email” problem under com.thealgorithms.graph, along with JUnit tests. The PR also introduces a new geometry utility for 2D line segment intersection (with tests) and extends binary search test coverage for null inputs.
Changes:
- Added
AccountMerge.mergeAccounts(...)implementing account merging via DSU (path compression + union by rank). - Added
AccountMergeTestcovering shared-email merges, same-name/no-shared-email separation, and empty input. - Added
LineIntersection+LineIntersectionTest, and extendedIterativeBinarySearchTestfor null array/key cases.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/main/java/com/thealgorithms/graph/AccountMerge.java |
New DSU-based account merging implementation. |
src/test/java/com/thealgorithms/graph/AccountMergeTest.java |
Tests for account merging behavior and ordering. |
src/main/java/com/thealgorithms/geometry/LineIntersection.java |
New geometry utility for segment intersection + intersection point computation. |
src/test/java/com/thealgorithms/geometry/LineIntersectionTest.java |
Tests for segment intersection and computed intersection point. |
src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java |
Added tests for null array and null key behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Map<Integer, List<String>> rootToEmails = new LinkedHashMap<>(); | ||
| for (Map.Entry<String, Integer> entry : emailToAccount.entrySet()) { | ||
| int root = dsu.find(entry.getValue()); | ||
| rootToEmails.computeIfAbsent(root, ignored -> new ArrayList<>()).add(entry.getKey()); | ||
| } |
There was a problem hiding this comment.
Accounts that contain only a name (i.e., no emails) are currently omitted from the output. rootToEmails is populated only by iterating emailToAccount, so accounts with an empty email list never become a root entry and are dropped. Consider explicitly handling accounts with account.size() <= 1 (or no emails after filtering) so they are returned as standalone merged accounts, and add a test for this case.
| double x1 = p1.x(); | ||
| double y1 = p1.y(); | ||
| double x2 = p2.x(); | ||
| double y2 = p2.y(); | ||
| double x3 = q1.x(); | ||
| double y3 = q1.y(); | ||
| double x4 = q2.x(); | ||
| double y4 = q2.y(); | ||
|
|
||
| double denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); | ||
| if (denominator == 0.0) { | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| double numeratorX = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4); | ||
| double numeratorY = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4); | ||
|
|
||
| return Optional.of(new Point2D.Double(numeratorX / denominator, numeratorY / denominator)); | ||
| } | ||
|
|
||
| private static int orientation(Point a, Point b, Point c) { | ||
| long cross = (long) (b.x() - a.x()) * (c.y() - a.y()) - (long) (b.y() - a.y()) * (c.x() - a.x()); |
There was a problem hiding this comment.
intersectionPoint computes the line intersection using double intermediates derived from int coordinates. For large coordinate values, the products and subtractions in denominator/numerators can lose precision (catastrophic cancellation), which can incorrectly produce denominator == 0.0 or a significantly inaccurate intersection point. Consider performing the determinant arithmetic in long (or BigInteger if you want to be fully safe) and only converting to double at the final division step; also avoid exact == 0.0 checks in favor of an integer-zero check (if using long) or an epsilon (if staying with double).
| double x1 = p1.x(); | |
| double y1 = p1.y(); | |
| double x2 = p2.x(); | |
| double y2 = p2.y(); | |
| double x3 = q1.x(); | |
| double y3 = q1.y(); | |
| double x4 = q2.x(); | |
| double y4 = q2.y(); | |
| double denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); | |
| if (denominator == 0.0) { | |
| return Optional.empty(); | |
| } | |
| double numeratorX = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4); | |
| double numeratorY = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4); | |
| return Optional.of(new Point2D.Double(numeratorX / denominator, numeratorY / denominator)); | |
| } | |
| private static int orientation(Point a, Point b, Point c) { | |
| long cross = (long) (b.x() - a.x()) * (c.y() - a.y()) - (long) (b.y() - a.y()) * (c.x() - a.x()); | |
| long x1 = p1.x(); | |
| long y1 = p1.y(); | |
| long x2 = p2.x(); | |
| long y2 = p2.y(); | |
| long x3 = q1.x(); | |
| long y3 = q1.y(); | |
| long x4 = q2.x(); | |
| long y4 = q2.y(); | |
| long det1 = x1 * y2 - y1 * x2; | |
| long det2 = x3 * y4 - y3 * x4; | |
| long denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); | |
| if (denominator == 0L) { | |
| return Optional.empty(); | |
| } | |
| long numeratorX = det1 * (x3 - x4) - (x1 - x2) * det2; | |
| long numeratorY = det1 * (y3 - y4) - (y1 - y2) * det2; | |
| return Optional.of(new Point2D.Double(numeratorX / (double) denominator, numeratorY / (double) denominator)); | |
| } | |
| private static int orientation(Point a, Point b, Point c) { | |
| long cross = ((long) b.x() - a.x()) * ((long) c.y() - a.y()) - ((long) b.y() - a.y()) * ((long) c.x() - a.x()); |
| package com.thealgorithms.geometry; | ||
|
|
||
| import java.awt.geom.Point2D; | ||
| import java.util.Optional; | ||
|
|
||
| /** | ||
| * Utility methods for checking and computing 2D line segment intersections. | ||
| */ | ||
| public final class LineIntersection { | ||
| private LineIntersection() { | ||
| } | ||
|
|
There was a problem hiding this comment.
The PR title/description focus on adding a DSU-based account merge algorithm, but this PR also introduces a new geometry algorithm (LineIntersection) and its tests (and separately updates binary search tests). Please either update the PR description/title to reflect these additional changes or split these unrelated additions into separate PRs to keep review scope focused.
7f0c002 to
dbc9534
Compare
Description
Add an account merge implementation using Disjoint Set Union (Union-Find).
Fixes #6831
Changes
AccountMerge.mergeAccounts(List<List<String>>)incom.thealgorithms.graph.Validation
mvn -q -Dtest=AccountMergeTest test