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