Skip to content

Commit 007e72c

Browse files
authored
Merge branch 'master' into ramer-douglas-peucker
2 parents 3f5c2b4 + 144ef9c commit 007e72c

2 files changed

Lines changed: 120 additions & 5 deletions

File tree

geometry/segment_intersection.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"""
2+
Given two line segments, determine whether they intersect.
3+
4+
This is based on the algorithm described in Introduction to Algorithms
5+
(CLRS), Chapter 33.
6+
7+
Reference:
8+
- https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
9+
- https://en.wikipedia.org/wiki/Orientation_(geometry)
10+
"""
11+
12+
from __future__ import annotations
13+
14+
from typing import NamedTuple
15+
16+
17+
class Point(NamedTuple):
18+
"""A point in 2D space.
19+
20+
>>> Point(0, 0)
21+
Point(x=0, y=0)
22+
>>> Point(1, -3)
23+
Point(x=1, y=-3)
24+
"""
25+
26+
x: float
27+
y: float
28+
29+
30+
def direction(pivot: Point, target: Point, query: Point) -> float:
31+
"""Return the cross product of vectors (pivot->query) and (pivot->target).
32+
33+
The sign of the result encodes the orientation of the ordered triple
34+
(pivot, target, query):
35+
- Negative -> counter-clockwise (left turn)
36+
- Positive -> clockwise (right turn)
37+
- Zero -> collinear
38+
39+
>>> direction(Point(0, 0), Point(1, 0), Point(0, 1))
40+
-1
41+
>>> direction(Point(0, 0), Point(0, 1), Point(1, 0))
42+
1
43+
>>> direction(Point(0, 0), Point(1, 1), Point(2, 2))
44+
0
45+
"""
46+
return (query.x - pivot.x) * (target.y - pivot.y) - (target.x - pivot.x) * (
47+
query.y - pivot.y
48+
)
49+
50+
51+
def on_segment(seg_start: Point, seg_end: Point, point: Point) -> bool:
52+
"""Check whether *point*, known to be collinear with the segment, lies on it.
53+
54+
>>> on_segment(Point(0, 0), Point(4, 4), Point(2, 2))
55+
True
56+
>>> on_segment(Point(0, 0), Point(4, 4), Point(5, 5))
57+
False
58+
>>> on_segment(Point(0, 0), Point(4, 0), Point(2, 0))
59+
True
60+
"""
61+
return min(seg_start.x, seg_end.x) <= point.x <= max(
62+
seg_start.x, seg_end.x
63+
) and min(seg_start.y, seg_end.y) <= point.y <= max(seg_start.y, seg_end.y)
64+
65+
66+
def segments_intersect(p1: Point, p2: Point, p3: Point, p4: Point) -> bool:
67+
"""Return True if line segment p1p2 intersects line segment p3p4.
68+
69+
Uses the CLRS cross-product / orientation method. Handles both the
70+
general case (proper crossing) and degenerate cases where one endpoint
71+
lies exactly on the other segment.
72+
73+
>>> segments_intersect(Point(0, 0), Point(2, 2), Point(0, 2), Point(2, 0))
74+
True
75+
>>> segments_intersect(Point(0, 0), Point(2, 2), Point(1, 1), Point(3, 3))
76+
True
77+
>>> segments_intersect(Point(0, 0), Point(1, 0), Point(2, 0), Point(3, 0))
78+
False
79+
>>> segments_intersect(Point(0, 0), Point(1, 1), Point(1, 0), Point(2, 1))
80+
False
81+
>>> segments_intersect(Point(0, 0), Point(1, 1), Point(0, 1), Point(0, 2))
82+
False
83+
>>> segments_intersect(Point(0, 0), Point(1, 0), Point(1, 0), Point(2, 0))
84+
True
85+
"""
86+
d1 = direction(p3, p4, p1)
87+
d2 = direction(p3, p4, p2)
88+
d3 = direction(p1, p2, p3)
89+
d4 = direction(p1, p2, p4)
90+
91+
if ((d1 < 0 < d2) or (d2 < 0 < d1)) and ((d3 < 0 < d4) or (d4 < 0 < d3)):
92+
return True
93+
94+
if d1 == 0 and on_segment(p3, p4, p1):
95+
return True
96+
if d2 == 0 and on_segment(p3, p4, p2):
97+
return True
98+
if d3 == 0 and on_segment(p1, p2, p3):
99+
return True
100+
return d4 == 0 and on_segment(p1, p2, p4)
101+
102+
103+
if __name__ == "__main__":
104+
import doctest
105+
106+
doctest.testmod()
107+
108+
print("Enter four points as 'x y' pairs (one per line):")
109+
points = [Point(*map(float, input().split())) for _ in range(4)]
110+
p1, p2, p3, p4 = points
111+
result = segments_intersect(p1, p2, p3, p4)
112+
print(1 if result else 0)

sorts/tim_sort.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
def binary_search(lst, item, start, end):
1+
from typing import Any
2+
3+
4+
def binary_search(lst: list[Any], item: Any, start: int, end: int) -> int:
25
if start == end:
36
return start if lst[start] > item else start + 1
47
if start > end:
@@ -13,7 +16,7 @@ def binary_search(lst, item, start, end):
1316
return mid
1417

1518

16-
def insertion_sort(lst):
19+
def insertion_sort(lst: list[Any]) -> list[Any]:
1720
length = len(lst)
1821

1922
for index in range(1, length):
@@ -24,7 +27,7 @@ def insertion_sort(lst):
2427
return lst
2528

2629

27-
def merge(left, right):
30+
def merge(left: list[Any], right: list[Any]) -> list[Any]:
2831
if not left:
2932
return right
3033

@@ -37,7 +40,7 @@ def merge(left, right):
3740
return [right[0], *merge(left, right[1:])]
3841

3942

40-
def tim_sort(lst):
43+
def tim_sort(lst: list[Any] | tuple[Any, ...] | str) -> list[Any]:
4144
"""
4245
>>> tim_sort("Python")
4346
['P', 'h', 'n', 'o', 't', 'y']
@@ -53,7 +56,7 @@ def tim_sort(lst):
5356
length = len(lst)
5457
runs, sorted_runs = [], []
5558
new_run = [lst[0]]
56-
sorted_array = []
59+
sorted_array: list[Any] = []
5760
i = 1
5861
while i < length:
5962
if lst[i] < lst[i - 1]:

0 commit comments

Comments
 (0)