1 | // TODO: find splitPoint faster (just access the median element!) |
2 | // TODO: really slow making the initial tree (5s for ~250 points) |
3 | sclass PtTree extends AbstractSet<Pt> { |
4 | int size; |
5 | Node root = new Leaf(null); |
6 | |
7 | // config |
8 | |
9 | int maxPointsPerNode = 4; |
10 | |
11 | abstract class Node { |
12 | Node parent; |
13 | |
14 | abstract bool add(Pt p); |
15 | abstract void collectPointsIn(Rect r, L<Pt> out); |
16 | |
17 | abstract void replaceChild(Node from, Node to); |
18 | |
19 | void addAll(Iterable<Pt> l) { |
20 | fOr (Pt p : l) add(p); |
21 | } |
22 | } |
23 | |
24 | class Leaf extends Node { |
25 | Cl<Pt> points; |
26 | |
27 | *(Node *parent) {} |
28 | |
29 | bool add(Pt p) { |
30 | if (main contains(points, p)) false; |
31 | if (points == null) points = new L; |
32 | points.add(p); |
33 | ++size; |
34 | possiblySplit(); |
35 | true; |
36 | } |
37 | |
38 | void possiblySplit { |
39 | if (l(points) > maxPointsPerNode) |
40 | split(); |
41 | } |
42 | |
43 | void collectPointsIn(Rect r, L<Pt> out) { |
44 | fOr (p : points) |
45 | if (containsPt(r, p)) |
46 | out.add(p); |
47 | } |
48 | |
49 | record noeq ProposedSplit(int dimension, int splitPoint, int count) { |
50 | int error() { ret abs(count-half(l(points))); } |
51 | } |
52 | |
53 | Split split() { |
54 | int n = l(points), half = n/2; |
55 | |
56 | ProposedSplit xSplit = checkSplit(0); |
57 | ProposedSplit ySplit = checkSplit(1); |
58 | |
59 | var best = xSplit.error() < ySplit.error() ? xSplit : ySplit; |
60 | |
61 | new Split split; |
62 | split.dimension = (byte) best.dimension; |
63 | split.splitPoint = best.splitPoint; |
64 | Leaf a = new Leaf(split); |
65 | IPred<Pt> pred = p -> ptCoord(split.dimension, p) >= split.splitPoint; |
66 | a.addAll(antiFilter(pred, points)); |
67 | split.a = a; |
68 | Leaf b = new Leaf(split); |
69 | b.addAll(filter(pred, points)); |
70 | split.b = b; |
71 | |
72 | a.possiblySplit(); |
73 | b.possiblySplit(); |
74 | |
75 | ret replaceMeWith(split); |
76 | } |
77 | |
78 | <A extends Node> A replaceMeWith(A node) { |
79 | node.parent = parent; |
80 | if (parent != null) parent.replaceChild(this, node); |
81 | else if (root == this) root = node; |
82 | ret node; |
83 | } |
84 | |
85 | ProposedSplit checkSplit(int dimension) { |
86 | L<Pt> lx = sortedBy(points, p -> ptCoord(dimension, p)); |
87 | new ProposedSplit ps; |
88 | ps.dimension = dimension; |
89 | |
90 | int n = l(points), half = n/2; |
91 | int i = 0; |
92 | int splitPoint = Int.MIN_VALUE; |
93 | while true { |
94 | int lastSplitPoint = splitPoint; |
95 | splitPoint = ptCoord(lx.get(i++), dimension)+1; |
96 | int lastI = i; |
97 | while (i < n && ptCoord(lx.get(i), dimension) < splitPoint) ++i; |
98 | if (i >= half) { |
99 | if (abs(lastI-half) < abs(i-half)) { |
100 | ps.splitPoint = lastSplitPoint; |
101 | ps.count = lastI; |
102 | } else { |
103 | ps.splitPoint = splitPoint; |
104 | ps.count = i; |
105 | } |
106 | ret ps; |
107 | } |
108 | } |
109 | } |
110 | |
111 | void replaceChild(Node from, Node to) { unimplemented(); } // doesn't need |
112 | } |
113 | |
114 | static final int minInf = Int.MIN_VALUE/4; |
115 | static final int inf = Int.MAX_VALUE/4; |
116 | |
117 | class Split extends Node { |
118 | byte dimension; // 0 for X, 1 for Y |
119 | int splitPoint; |
120 | Node a, b; |
121 | |
122 | bool add(Pt p) { |
123 | ret (ptCoord(p, dimension) >= splitPoint ? b : a).add(p); |
124 | } |
125 | |
126 | Rect aRect() { |
127 | ret rectFromPoints(minInf, minInf, |
128 | dimension == 0 ? splitPoint : inf, |
129 | dimension == 1 ? splitPoint : inf); |
130 | } |
131 | |
132 | Rect bRect() { |
133 | ret rectFromPoints( |
134 | dimension == 0 ? splitPoint : minInf, |
135 | dimension == 1 ? splitPoint : minInf, |
136 | inf, inf); |
137 | } |
138 | |
139 | void collectPointsIn(Rect r, L<Pt> out) { |
140 | Rect rA = intersectRect(r, aRect()); |
141 | if (nempty(rA)) |
142 | a.collectPointsIn(rA, out); |
143 | |
144 | Rect rB = intersectRect(r, bRect()); |
145 | if (nempty(rB)) |
146 | b.collectPointsIn(rB, out); |
147 | } |
148 | |
149 | void replaceChild(Node from, Node to) { |
150 | if (a == from) a = to; |
151 | if (b == from) b = to; |
152 | } |
153 | } |
154 | |
155 | public bool add(Pt p) { |
156 | ret root.add(p); |
157 | } |
158 | |
159 | L<Pt> pointsIn aka pointsInRect(Rect r) { |
160 | new L<Pt> out; |
161 | root.collectPointsIn(r, out); |
162 | ret out; |
163 | } |
164 | |
165 | public bool contains(O p) { |
166 | ret p instanceof Pt && contains(p/Pt); |
167 | } |
168 | |
169 | bool contains(Pt p) { |
170 | ret nempty(pointsIn(rect(p.x, p.y, 1, 1))); |
171 | } |
172 | |
173 | // use this to make a PtTree! |
174 | // It's actually makes a balanced tree. |
175 | // It is assumed that points contains no duplicates |
176 | static PtTree fromPointSet(Iterable<Pt> points) { |
177 | new PtTree tree; |
178 | Leaf root = cast tree.root; |
179 | root.points = cloneList(points); |
180 | tree.size = l(root.points); |
181 | root.possiblySplit(); |
182 | ret tree; |
183 | } |
184 | |
185 | public int size() { ret size; } |
186 | |
187 | public Iterator<Pt> iterator() { |
188 | /* What we actually want is a coroutine yielding the values. |
189 | // Like this, if we had this syntax: |
190 | |
191 | ret coroutine { |
192 | void iterate(Node node) { |
193 | if (node cast Leaf) |
194 | returnAll(node.points); |
195 | else { |
196 | cast node to Split; |
197 | iterate(node.a); |
198 | iterate(node.b); |
199 | } |
200 | |
201 | run { iterate(root); } |
202 | } |
203 | }; |
204 | |
205 | // So how do we turn this into an iterator? |
206 | // Maybe through VStack.Computable. |
207 | */ |
208 | |
209 | new Var<Iterator<Pt>> out; |
210 | new VStack stack; |
211 | stack.push(new co_iterate(root, out)); |
212 | |
213 | ret iff_endOnNull(new IF0<Pt> { |
214 | bool done; |
215 | |
216 | public Pt get() { |
217 | while ping (true) { |
218 | // something in the pipe? return it |
219 | if (out.has()) |
220 | if (out->hasNext()) |
221 | ret out->next(); |
222 | else |
223 | out.clear(); // and clean the pipe afterwards, boy! |
224 | |
225 | if (done) null; // end of the line |
226 | // nothing in the pipe, gotta walk the tree some more |
227 | if (!step(stack)) |
228 | set done; // prepare to get off the train |
229 | } |
230 | } |
231 | }); |
232 | } |
233 | |
234 | record noeq co_iterate(Node node, Var<Iterator<Pt>> out) extends VStackComputableWithStep { |
235 | void step(VStack stack) { |
236 | if (step == 0) { |
237 | if (node cast Leaf) { |
238 | out.set(main iterator(node.points)); |
239 | stack.return(); |
240 | } else { |
241 | cast node to Split; |
242 | stack.call(new co_iterate(node.a, out)); |
243 | ret with step = 1; |
244 | } |
245 | } else { |
246 | cast node to Split; |
247 | stack.replace(new co_iterate(node.b, out)); |
248 | } |
249 | } |
250 | } |
251 | |
252 | } |
Began life as a copy of #1032229
download show line numbers debug dex old transpilations
Travelled to 3 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx
No comments. add comment
Snippet ID: | #1032244 |
Snippet name: | PtTree - tree structure for quick finding of points in any rectangle [backup with the cool co_iterate] |
Eternal ID of this version: | #1032244/2 |
Text MD5: | 48d18dfe58ade11919813153c81f7455 |
Author: | stefan |
Category: | javax / 2d space |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2021-08-22 19:06:59 |
Source code size: | 6595 bytes / 252 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 149 / 183 |
Version history: | 1 change(s) |
Referenced in: | [show references] |