1010def comparison (a ,b ):
1111 return 1 if a > b else (- 1 if a < b else 0 )
1212
13- def compare (a ,b ,comparison ,comparisonCache ):
14- if (a ,b ) in comparisonCache :
15- return - 1
16- elif (b ,a ) in comparisonCache :
17- return 1
18- else :
19- d = comparison (a ,b )
20- if d < 0 :
21- comparisonCache .add ((a ,b ))
22- else :
23- comparisonCache .add ((b ,a ))
24-
25- def safeSorted (data ,comparison ,comparisonCache = set ()):
13+ def safeSorted (data ,comparison ):
2614 """
2715 A simpleminded recursive merge sort that will work even if the comparison function fails to be a partial order.
2816 Makes (shallow) copies of the data, which uses more memory than is absolutely necessary. In the intended application,
@@ -32,13 +20,13 @@ def safeSorted(data,comparison,comparisonCache=set()):
3220 n = len (data )
3321 if n <= 1 :
3422 return list (data )
35- d1 = safeSorted (data [:n // 2 ],comparison , comparisonCache = comparisonCache )
36- d2 = safeSorted (data [n // 2 :],comparison , comparisonCache = comparisonCache )
23+ d1 = safeSorted (data [:n // 2 ],comparison )
24+ d2 = safeSorted (data [n // 2 :],comparison )
3725 i1 = 0
3826 i2 = 0
3927 out = []
4028 while i1 < len (d1 ) and i2 < len (d2 ):
41- if compare (d1 [i1 ], d2 [i2 ], comparison , comparisonCache ) < 0 :
29+ if compare (d1 [i1 ], d2 [i2 ], comparison ) < 0 :
4230 out .append (d1 [i1 ])
4331 i1 += 1
4432 else :
@@ -50,68 +38,68 @@ def safeSorted(data,comparison,comparisonCache=set()):
5038 out += d2 [i2 :]
5139 return out
5240
41+ def closed (path ):
42+ return path [- 1 ] == path [0 ]
43+
44+ def inside (z , path ):
45+ for p in path :
46+ if p == z :
47+ return False
48+ try :
49+ phases = sorted ((cmath .phase (p - z ) for p in path ))
50+ # make a ray that is relatively far away from any points
51+ if len (phases ) == 1 :
52+ # should not happen
53+ bestPhase = phases [0 ] + math .pi
54+ else :
55+ bestIndex = max ( (phases [i + 1 ]- phases [i ],i ) for i in range (len (phases )- 1 ))[1 ]
56+ bestPhase = (phases [bestIndex + 1 ]+ phases [bestIndex ])/ 2.
57+ ray = cmath .rect (1. , bestPhase )
58+ rotatedPath = tuple ((p - z ) / ray for p in path )
59+ # now we just need to check shiftedPath's intersection with the positive real line
60+ s = 0
61+ for i ,p2 in enumerate (rotatedPath ):
62+ p1 = rotatedPath [i - 1 ]
63+ if p1 .imag == p2 .imag :
64+ # horizontal lines can't intersect positive real line once phase selection was done
65+ continue
66+ # (1/m)y + xIntercept = x
67+ reciprocalSlope = (p2 .real - p1 .real )/ (p2 .imag - p1 .imag )
68+ xIntercept = p2 .real - reciprocalSlope * p2 .imag
69+ if xIntercept == 0 :
70+ return False # on boundary
71+ if xIntercept > 0 :
72+ if p1 .imag < p2 .imag :
73+ s += 1
74+ else :
75+ s -= 1
76+ return s != 0
77+
78+ except OverflowError :
79+ return False
80+
81+ def nestedPaths (path1 , path2 ):
82+ if not closed (path2 ):
83+ return False
84+ k = min (pointsToCheck , len (path1 ))
85+ for point in sample (path1 , k ):
86+ if inside (point , path2 ):
87+ return True
88+ return False
89+
90+ def fixPath (path ):
91+ out = [complex (point [0 ],point [1 ]) for point in path ]
92+ if out [0 ] != out [- 1 ] and abs (out [0 ]- out [- 1 ]) <= tolerance :
93+ out .append (out [0 ])
94+ return out
95+
5396def comparePaths (path1 ,path2 ,tolerance = 0.05 ,pointsToCheck = 3 ):
5497 """
5598 outer paths come before inner ones
5699 open ones before closed paths
57100 otherwise, top to bottom bounds, left to right
58101 """
59102
60- def fixPath (path ):
61- out = [complex (point [0 ],point [1 ]) for point in path ]
62- if out [0 ] != out [- 1 ] and abs (out [0 ]- out [- 1 ]) <= tolerance :
63- out .append (out [0 ])
64- return out
65-
66- def closed (path ):
67- return path [- 1 ] == path [0 ]
68-
69- def inside (z , path ):
70- for p in path :
71- if p == z :
72- return False
73- try :
74- phases = sorted ((cmath .phase (p - z ) for p in path ))
75- # make a ray that is relatively far away from any points
76- if len (phases ) == 1 :
77- # should not happen
78- bestPhase = phases [0 ] + math .pi
79- else :
80- bestIndex = max ( (phases [i + 1 ]- phases [i ],i ) for i in range (len (phases )- 1 ))[1 ]
81- bestPhase = (phases [bestIndex + 1 ]+ phases [bestIndex ])/ 2.
82- ray = cmath .rect (1. , bestPhase )
83- rotatedPath = tuple ((p - z ) / ray for p in path )
84- # now we just need to check shiftedPath's intersection with the positive real line
85- s = 0
86- for i ,p2 in enumerate (rotatedPath ):
87- p1 = rotatedPath [i - 1 ]
88- if p1 .imag == p2 .imag :
89- # horizontal lines can't intersect positive real line once phase selection was done
90- continue
91- # (1/m)y + xIntercept = x
92- reciprocalSlope = (p2 .real - p1 .real )/ (p2 .imag - p1 .imag )
93- xIntercept = p2 .real - reciprocalSlope * p2 .imag
94- if xIntercept == 0 :
95- return False # on boundary
96- if xIntercept > 0 :
97- if p1 .imag < p2 .imag :
98- s += 1
99- else :
100- s -= 1
101- return s != 0
102-
103- except OverflowError :
104- return False
105-
106- def nestedPaths (path1 , path2 ):
107- if not closed (path2 ):
108- return False
109- k = min (pointsToCheck , len (path1 ))
110- for point in sample (path1 , k ):
111- if inside (point , path2 ):
112- return True
113- return False
114-
115103 path1 = fixPath (path1 )
116104 path2 = fixPath (path2 )
117105
@@ -130,6 +118,18 @@ def nestedPaths(path1, path2):
130118 else :
131119 return comparison (y1 ,y2 )
132120
121+ def orderedPaths (sortedPaths ):
122+ level = []
123+ while len (sortedPaths ):
124+ path = sortedPaths [0 ]
125+ if closed (path ):
126+ for p in level :
127+ if inside (path , p ):
128+ return [level ] + orderedPaths (sortedPaths )
129+ level .append (path )
130+ sortedPaths .pop (0 )
131+ return [level ]
132+
133133def message (string ):
134134 if not quiet :
135135 sys .stderr .write (string + "\n " )
0 commit comments