Merge Overlapping Straight Fence Runs From a Property Survey
Applied IntuitionSoftware Engineer · Technical ScreenLast updated
Collapse a pile of straight fence-line survey readings into one run per physically continuous stretch of fence, by detecting which readings sit on the same infinite line and connect up. Tests exact collinearity detection, correct handling of every orientation, and interval merging per line.
A land survey records fence lines as a list of straight runs. Each run is a list of integer survey points [x, y], all lying on one straight line, already given in order along that line. Different runs can lie on the same line and physically overlap — for example, two separate survey passes that both recorded part of the same fence.
Consolidate the input into the fewest runs that represent the same physical fence layout: wherever two or more runs sit on one line and connect end to end, fold them into a single run carrying every point from the group.
Your function needs to handle every orientation correctly, including perfectly horizontal and perfectly vertical runs — don't assume every run has a well-defined, finite slope.
Function signature
def merge_fence_runs(runs: list[list[list[int]]]) -> list[list[list[int]]]:
runs[i] is the list of [x, y] points making up run i.
Rules
- Treat two runs as being on the same infinite line only when every point from both, taken together, satisfies one shared linear equation; runs on two different lines stay separate forever, regardless of whether they physically cross paths or meet at a single shared point.
- Define a run's span as everything between its first and last point along its own line. For two runs confirmed to share a line, they're combinable the moment their spans have any point in common, and sharing just an endpoint is enough to count.
- Merging is transitive: if run A overlaps run B, and run B overlaps run C, all three combine into one run even if A and C don't directly overlap.
- Each output run is the sorted, deduplicated set of points from all of its merged input runs, sorted by
xascending, then byyascending. - Output order: whichever input run shows up earliest in the input list determines where its line's group slots into the output; within one line's group, if the runs didn't all connect into a single piece, list the separate pieces sorted by their own first point.
Examples
Example 1 — diagonal runs that chain together
runs = [[[0, 0], [2, 0], [4, 0]], [[4, 0], [6, 0]], [[9, 0], [11, 0]], [[0, 1], [0, 4]]]
merge_fence_runs(runs)
-> [[[0, 0], [2, 0], [4, 0], [6, 0]], [[9, 0], [11, 0]], [[0, 1], [0, 4]]]
The first two runs share (4, 0) and merge. The third run is on the same horizontal line but has a gap (from 6 to 9), so it stays separate. The fourth run is vertical, a different line entirely.
Example 2 — no merges needed
runs = [[[1, 1], [2, 2]], [[5, 0], [5, 3]]]
merge_fence_runs(runs) -> [[[1, 1], [2, 2]], [[5, 0], [5, 3]]]
One diagonal run and one vertical run on entirely different lines, so both pass through unchanged.
Constraints
1 <= len(runs) <= 10^42 <= len(runs[i]) <= 100, with at most10^5points total across all runs.- Coordinates are integers and stay within
[-10^4, 10^4]. - Points within a run are distinct, collinear, and already given in order along the line.
- Different runs may share points or be exact duplicates of each other.
Hints
Hint 1
Represent a line as a normalized (A, B, C) triple from Ax + By = C, reduced by the greatest common divisor of A and B with a fixed sign convention, so two runs on the same line always hash to the exact same key regardless of which two points you used to compute it.
Hint 2
Avoid floating-point slope entirely — compute the line's direction and normal from integer coordinate differences so collinearity comparisons are always exact.
Hint 3
Sorting points of a run lexicographically by (x, y) gives a consistent, monotonic order along any straight line, including perfectly vertical ones, so you can use that single ordering to detect overlap without special-casing orientation.
Hint 4
Within one line's group of runs, sort by each run's starting point and sweep left to right, merging into a running group whenever the next run's start is not strictly past the current group's farthest point.
Follow-up questions
- How would you adapt this if two points could be given as floating-point survey coordinates instead of exact integers?
- How would you detect and report a run whose points are not actually collinear, as a data-quality check before merging?
- If a new run arrives one at a time instead of all at once, how would you support incremental merging without recomputing everything from scratch?
- How would you extend this to merge segments that lie on the same line but are allowed to have a small gap, say up to a given tolerance, rather than requiring exact overlap or touching?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.