data_structures_rmolinari 0.2.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,670 +0,0 @@
1
- require 'must_be'
2
-
3
- require_relative 'shared'
4
-
5
- # A priority search tree (PST) stores points in two dimensions (x,y) and can efficiently answer certain questions about the set of
6
- # point.
7
- #
8
- # The structure was introduced by McCreight [1].
9
- #
10
- # It is a binary search tree which is a max-heap by the y-coordinate, and, for a non-leaf node N storing (x, y), all the nodes in
11
- # the left subtree of N have smaller x values than any of the nodes in the right subtree of N. Note, though, that the x-value at N
12
- # has no particular property relative to the x values in its subtree. It is thus _almost_ a binary search tree in the x coordinate.
13
- #
14
- # See more: https://en.wikipedia.org/wiki/Priority_search_tree
15
- #
16
- # It is possible to build such a tree in place, given an array of pairs. See [2]. In a follow-up paper, [3], the authors show how to
17
- # construct a more flexible data structure,
18
- #
19
- # "[T]he Min-Max Priority Search tree for a set P of n points in R^2. It is a binary tree T with the following properties:
20
- #
21
- # * For each internal node u, all points in the left subtree of u have an x-coordinate which is less than the x-coordinate of any
22
- # point in the right subtree of u.
23
- # * The y-coordinate values of the nodes on even (resp. odd) levels are smaller (resp. greater) than the y-coordinate values of
24
- # their descendants (if any), where the root is at level zero.
25
- #
26
- # "The first property implies that T is a binary search three on the x-coordinates of the points in P, excepts that there is no
27
- # relation between the x-coordinates of the points stored at u and any of its children. The second property implies that T is a
28
- # min-max heap on the y-coordinates of the points in P."
29
- #
30
- # I started implementing the in-place PST. Then, finding the follow-up paper [3], decided to do that one instead, as the paper says
31
- # it is more flexible. The point is to learn a new data structure and its associated algorithms.
32
- #
33
- # The algorithms are rather bewildering. Highest3SidedUp is complicated, and only two of the functions CheckLeft, CheckLeftIn,
34
- # CheckRight, CheckRightIn are given; the other two are "symmetric". But it's not really clear what the first are actually doing, so
35
- # it's hard to know what the others actually do.
36
- #
37
- # The implementation is incomplete. The pseduo-code in the paper is buggy (see the code below), which makes progress difficult.
38
- #
39
- # [1] E. McCreight, _Priority Search Trees_, SIAM J. Computing, v14, no 3, May 1985, pp 257-276.
40
- # [2] De, Maheshwari, Nandy, Smid, _An in-place priority search tree_, 23rd Annual Canadian Conference on Computational Geometry.
41
- # [3] De, Maheshwari, Nandy, Smid, _An in-place min-max priority search tree_, Computational Geometry, v46 (2013), pp 310-327.
42
- # [4] Atkinson, Sack, Santoro, Strothotte, _Min-max heaps and generalized priority queues_, Commun. ACM 29 (10) (1986), pp 996-1000.
43
- class DataStructuresRMolinari::MinmaxPrioritySearchTree
44
- include Shared
45
-
46
- # The array of pairs is turned into a minmax PST in-place without cloning. So clone before passing it in, if you care.
47
- #
48
- # Each element must respond to #x and #y. Use Pair (above) if you like.
49
- def initialize(data, verify: false)
50
- @data = data
51
- @size = @data.size
52
-
53
- construct_pst
54
- return unless verify
55
-
56
- # puts "Validating tree structure..."
57
- verify_properties
58
- end
59
-
60
- # Let Q = [x0, infty) X [y0, infty) be the northeast "quadrant" defined by the point (x0, y0) and let P be the points in this data
61
- # structure. Define p* as
62
- #
63
- # - (infty, infty) if Q \intersect P is empty and
64
- # - the leftmost (i.e., min-x) point in Q \intersect P otherwise
65
- #
66
- # This method returns p*.
67
- #
68
- # From De et al:
69
- #
70
- # [t]he variables best, p, and q satisfy the folling invariant:
71
- #
72
- # - if Q \intersect P is nonempty then p* \in {best} \union T(p) \union T(q)
73
- # - if Q \intersect P is empty then p* = best
74
- # - p and q are at the same level of T and x(p) <= x(q)
75
- #
76
- # Here T(x) is the subtree rooted at x
77
- def leftmost_ne(x0, y0)
78
- best = Pair.new(INFINITY, INFINITY)
79
- p = q = root
80
-
81
- in_q = ->(pair) { pair.x >= x0 && pair.y >= y0 }
82
-
83
- # From the paper:
84
- #
85
- # takes as input a point t \in P and updates best as follows: if t \in Q and x(t) < x(best) then it assignes best = t
86
- #
87
- # Note that the paper identifies a node in the tree with its value. We need to grab the correct node.
88
- update_leftmost = lambda do |node|
89
- t = val_at(node)
90
- if in_q.call(t) && t.x < best.x
91
- best = t
92
- end
93
- end
94
-
95
- # Generalize the c1,...,c4 idea from the paper in line with the BUG 2 IN PAPER notes, below.
96
- #
97
- # Given: 0 or more nodes n1, ..., nk in the tree. All are at the same level, which is a "max level" in our MinmaxPST, such that
98
- # x(n1) <= x(n2) <= ... <= x(nk). (Note: it is expected that the nj are either children or grandchildren of p and q, though we
99
- # don't check that.)
100
- #
101
- # If k = 0 return nil. Otherwise...
102
- #
103
- # We return two values p_goal, q_goal (possibly equal) from among the nj such that
104
- #
105
- # - p_goal is not to the right of q_goal in the tree and so, in particular x(p_goal) <= x(q_goal)
106
- # - if and when the auction reaches p = p_goal and q = q_goal the algorithm invariant will be satisfied.
107
- #
108
- # As a special case, we return nil if we detect that none of the subtrees T(nj) contain any points in Q. This is a sign to
109
- # terminate the algorithm.
110
- #
111
- # See the notes at "BUG 2 IN PAPER" below for more details about what is going on.
112
- determine_goal_nodes = lambda do |nodes|
113
- node_count = nodes.size
114
- return nil if node_count.zero?
115
-
116
- if val_at(nodes.last).x <= x0
117
- # Only the rightmost subtree can possibly have anything Q, assuming that all the x-values are distinct.
118
- return [nodes.last, nodes.last]
119
- end
120
-
121
- if val_at(nodes.first).x > x0
122
- # All subtrees have x-values large enough to provide elements of Q. Since we are at a max-level the y-values help us work
123
- # out which subtree to focus on.
124
- leftmost = nodes.find { |node| val_at(node).y >= y0 }
125
-
126
- return nil unless leftmost # nothing left to find
127
-
128
- # Otherwise we explore the leftmost subtree. Its root is in Q and can't be beaten by anything to its right.
129
- return [leftmost, leftmost]
130
- end
131
-
132
- values = nodes.map { |n| val_at(n) }
133
-
134
- # Otherwise x(n1) <= x0 < x(nk). Thus i is well-defined.
135
- i = (0...node_count).select { |j| values[j].x <= x0 && x0 < values[j + 1].x }.min
136
-
137
- # these nodes all have large-enough x-values and so this finds the ones in the set Q.
138
- new_q = nodes[(i + 1)..].select { |node| val_at(node).y >= y0 }.min # could be nil
139
- new_p = nodes[i] if values[i].y >= y0 # The leftmost subtree is worth exploring if the y-value is big enough. Otherwise not
140
- new_p ||= new_q # if nodes[i] is no good we send p along with q
141
- new_q ||= new_p # but if there was no worthwhile value for q we should send it along with p
142
-
143
- return nil unless new_p
144
-
145
- [new_p, new_q]
146
- end
147
-
148
- until leaf?(p)
149
- level = Math.log2(p).floor # TODO: don't calculate log every time!
150
-
151
- update_leftmost.call(p)
152
- update_leftmost.call(q)
153
-
154
- if p == q
155
- if one_child?(p)
156
- p = q = left(p)
157
- else
158
- q = right(p)
159
- p = left(p)
160
- end
161
- else
162
- # p != q
163
- if leaf?(q)
164
- q = p # p itself is just one layer above the leaves, or is itself a leaf
165
- elsif one_child?(q)
166
- # Note that p has two children
167
- if val_at(left(q)).x < x0
168
- # x-values below p are too small
169
- p = q = left(q)
170
- elsif val_at(right(p)).x <= x0
171
- # x-values in T(right(p)) are too small. DISTINCT-X
172
- p = right(p)
173
- q = left(q)
174
- else
175
- # BUG 1 IN PAPER.
176
- #
177
- # So, x(q_l) >= x0 and x(p_r) > x0. But how can we be sure that the child of q isn't the winner?. Should we be trying
178
- # it in this case?
179
- #
180
- # Yes: otherwise it never gets checked.
181
-
182
- update_leftmost.call(left(q))
183
- q = right(p)
184
- p = left(p)
185
- end
186
- else
187
- # p and q both have two children
188
-
189
- # BUG 2 IN PAPER.
190
- #
191
- # Define c as the paper does:
192
- #
193
- # (c1, c2, c3, c4) = (left(p), right(p), left(q), right(q))
194
- #
195
- # Because of the PST property on x and the invariant x(p) <= x(q) we know that
196
- #
197
- # x(c1) <= x(c2) <= x(c3) <= x(c4)
198
- #
199
- # Similarly, the sets of values x(T(ci)) are pairwise ordered in the same sense.
200
- #
201
- # Suppose further that x(ci) <= x0 <= x(c(i+i)). Then we know several things
202
- #
203
- # - there might be a "winner" (point in Q) in T(ci), perhaps ci itself.
204
- # - there are not any winners in T(cj) for j < i, becasue the x-values there aren't big enough
205
- # - any winner in ck, for k >= i, will be the left of and thus beat any winner in c(k+1), because of the ordering of
206
- # x-values
207
- #
208
- # If x(c4) <= x0 then the rightmost subtree T(c4) is the only one worth checking and we set p = q = c4.
209
- # If x(c1) > x0 then we take i = 0 and ignore the logic on ci in what follows and setting p = q.
210
- #
211
- # Pretend for the moment that we are using a MaxPST instead of a MinmaxPST. Then we can look at y values to learn more.
212
- #
213
- # - if y(ci) >= y0 then we need to search T(ci), so we will update p = ci
214
- # - but if y(ci) < y0 then there are no winners in T(ci) because the y-values are too small.
215
- # - similarly, if y(c(i+i)) >= y0 then we need to search T(c(i+1)). Indeed c(i+1) itself is in Q and beats any winner in
216
- # subtrees further to the right
217
- # - so, let k > i be minimal such that y(ck) >= y0, if there is any. Note that ck is itself a winner. Then
218
- # - if y(ci) >= y0,
219
- # - set p = ci, and q = ck (or q = ci if there is no such k)
220
- # - otherwise (T(ci) has no winners because its y-values are too small)
221
- # - if k is defined set p = q = ck. Otherwise HALT (there are no more winners)
222
- #
223
- # But we are working with a MinmaxPST rather than a MaxPST, so we have to work harder. If c1, ..., c4 (the children of p
224
- # and q) are in a "max-level" of the tree - that is, an even level - then the logic above still applies. But if they are
225
- # at a min level things are trickier and we need to go another layer down.
226
- #
227
- # The paper knows that we need to look a further layer down, but the logic is too simplistic. It looks at cj for j > i and
228
- # checks if cj or either of its children are in Q. But that's not good enough. For the same reason that in a MaxPST we may
229
- # need to explore below T(ci) even if ci isn't in Q, we may need to decend through one of the grandchilden of p or q even
230
- # if that grandchild isn't in Q.
231
- #
232
- # Getting a bit handwavey especially over what happens near the leaves...
233
- #
234
- # Consider the children d1, d2, ..., dm, of ci, ..., c4 (and so grandchildren of p and q). They are at a max-level and so
235
- # the logic described applies to the dk. If ci happens to be a winner we can set p = ci and work out what to do with q by
236
- # looking at the children of c(i+1), ..., c4. Otherwise we look at all the dj values (up to 8 of them), apply the logic
237
- # above to work out that we want to head for, say, p = ds and q = dt, and in this cycle update p = parent(ds), q =
238
- # parent(dt). (We also need to submit the values c(i+1)..c4 to UpdateLeftmost.)
239
- #
240
- # In other words, we can use the MaxPST logic on d1,...,dm to decide where we need to go, and then step to the relevant
241
- # parents among the cj.
242
-
243
- c = [left(p), right(p), left(q), right(q)]
244
- if level.odd?
245
- # the elements of c are at an even level, and hence their y values are maxima for the subtrees. We can learn what we
246
- # need to know from them
247
- p, q = determine_goal_nodes.call(c)
248
- if p && !q
249
- # byebug
250
- # determine_goal_nodes.call(c)
251
- raise 'bad logic'
252
- end
253
- else
254
- # They are at an odd level and so aren't helpful in working out what to do next: we look at their children, which are in
255
- # a max-level. We need to check the elements of c against best since we are otherwise ignoring them.
256
- c.each { |n| update_leftmost.call(n) }
257
-
258
- d = c.map { [left(_1), right(_1)]}.flatten.select { |n| n <= @size }
259
-
260
- # Note that we are jumping down two levels here!
261
- p, q = determine_goal_nodes.call(d)
262
- if p && !q
263
- # byebug
264
- # determine_goal_nodes.call(c)
265
- raise 'bad logic'
266
- end
267
-
268
- p
269
- end
270
-
271
- return best unless p # nothing more to do
272
- end
273
- end
274
- end
275
- update_leftmost.call(p)
276
- update_leftmost.call(q)
277
- best
278
- end
279
-
280
- # Let Q be the "three-sided query range" [x0, x1] X [y0, infty) and let P_Q be P \intersect Q.
281
- #
282
- # If P_Q is empty then p* = (infty, -infty).
283
- # Otherwise, p* is the point in P_Q with maximal y value.
284
- #
285
- # This method returns p*
286
- # def highest_3_sided_up(x0, x1, y0)
287
- # best = Pair.new(INFINITY, -INFINITY)
288
-
289
- # in_q = lambda do |pair|
290
- # pair.x >= x0 && pair.x <= x1 && pair.y >= y0
291
- # end
292
-
293
- # # From the paper:
294
- # #
295
- # # takes as input a point t and does the following: if t \in Q and y(t) > y(best) then it assignes best = t
296
- # #
297
- # # Note that the paper identifies a node in the tree with its value. We need to grab the correct node.
298
- # #
299
- # # The algorithm is complicated. From the paper:
300
- # #
301
- # # Since Q is bounded by two vertical sides, we use four index variables p, p', q and q' to guide the search path. In addition,
302
- # # we use four bits L, L', R and R'; these correspond to the subtrees of T rooted at the nodes p, p', q, and q', respectively;
303
- # # if a bit is equal to one, then the corresonding node is referred to as an _active node_ (for example, if L = 1 then p is an
304
- # # active node), and the subtree rooted at that node may contain a candidate point for p*. So the search is required to be
305
- # # performed in the subtree rooted at all active nodes. More formally, at any instant of time the variables satisfy the folling
306
- # # invariants:
307
- # #
308
- # # - If L = 1 the x(p) < x0.
309
- # # - If L' = 1 then x0 <= x(p') <= x1.
310
- # # - If R = 1 then x(q) > x1.
311
- # # - If R' = 1 then x0 <= x(q') <= x1.
312
- # # - If L' = 1 and R' = 1 then x(p') <= x(q').
313
- # # - If P_Q is non-empty then p* = best or p* is in the subtree rooted at any one of the active nodes.
314
- # #
315
- # # There are more details in the paper
316
- # update_highest = lambda do |node|
317
- # t = val_at(node)
318
- # if in_q.call(t) && t.y > best.y
319
- # best = t
320
- # end
321
- # end
322
-
323
- # ex_update_highest = lambda do |node|
324
- # update_highest.call(node)
325
- # update_highest.call(left(node)) unless leaf?(node)
326
- # update_highest.call(right(node)) unless one_child?(node)
327
- # end
328
-
329
- # if val_at(root).x < x0
330
- # p = root
331
- # l = true
332
- # l_prime = r = r_prime = false
333
- # elsif val_at(root).x < x1
334
- # p_prime = root
335
- # l_prime = true
336
- # l = r = r_prime = false
337
- # else
338
- # q = root
339
- # r = true
340
- # l = l_prime = r_prime = false
341
- # end
342
-
343
- # set_z = lambda do
344
- # r = []
345
- # r << p if l
346
- # r << p_prime if l_prime
347
- # r << q if r
348
- # r << q_prime if r_primg
349
- # r
350
- # end
351
-
352
- # check_left = lambda do
353
- # if leaf?(p)
354
- # l = false
355
- # elsif one_child?(p)
356
- # p_l_x = val_at(left(p))
357
- # if x0 <= p_l_x && p_l_x <= x1
358
- # update_highest.call(left(p))
359
- # if l_prime && r_prime
360
- # ex_update_highest.call(p_prime)
361
- # elsif l_prime
362
- # q_prime = p_prime
363
- # r_prime = true
364
- # end
365
- # p_prime = left(p)
366
- # l_prime = true
367
- # l = false
368
- # elsif p_l_x < x0
369
- # p = left(p)
370
- # else
371
- # q = left(p)
372
- # r = true
373
- # l = false
374
- # end
375
- # else
376
- # # p has two children
377
-
378
- # end
379
-
380
- # while l || l_prime || r || r_prime
381
- # z_star = set_z.call.min_by(4) { level(_1) }
382
- # if z_star.include? p_prime
383
- # check_left_in(p_prime)
384
- # elsif z_star.include? q_prime
385
- # check_right_in(q_prime)
386
- # elsif z_star.include? p
387
- # check_left(p)
388
- # else
389
- # check_right(q)
390
- # end
391
- # end
392
- # end
393
-
394
- # Find the "highest" (max-y) point that is "northeast" of (x, y).
395
- #
396
- # That is, the point p* in Q = [x, infty) X [y, infty) with the largest y value, or (infty, -infty) if there is no point in that
397
- # quadrant.
398
- #
399
- # Algorithm is from De et al. section 3.1
400
- def highest_ne(x0, y0)
401
- raise "Write me"
402
- # From the paper:
403
- #
404
- # The algorithm uses two variables best and p, which satisfy the following invariant
405
- #
406
- # - If Q intersect P is nonempty then p* in {best} union T_p
407
- # - If Q intersect P is empty then p* = best
408
- #
409
- # Here, P is the set of points in our data structure and T_p is the subtree rooted at p
410
- best = Pair.new(INFINITY, -INFINITY)
411
- p = root # root of the whole tree AND the pair stored there
412
-
413
- in_q = lambda do |pair|
414
- pair.x >= x0 && pair.y >= y0
415
- end
416
-
417
- # From the paper:
418
- #
419
- # takes as input a point t and does the following: if t \in Q and y(t) > y(best) then it assignes best = t
420
- #
421
- # Note that the paper identifies a node in the tree with its value. We need to grab the correct node.
422
- update_highest = lambda do |node|
423
- t = val_at(node)
424
- if in_q.call(t) && t.y > best.y
425
- best = t
426
- end
427
- end
428
-
429
- # We could make this code more efficient. But since we only have O(log n) steps we won't actually gain much so let's keep it
430
- # readable and close to the paper's pseudocode for now.
431
- until leaf?(p)
432
- p_val = val_at(p)
433
- if in_q.call(p_val)
434
- # p \in Q and nothing in its subtree can beat it because of the max-heap
435
- update_highest.call(p)
436
- return best
437
-
438
- # p = left(p) <- from paper
439
- elsif p_val.y < y0
440
- # p is too low for Q, so the entire subtree is too low as well
441
- return best
442
-
443
- # p = left(p)
444
- elsif one_child?(p)
445
- # With just one child we need to check it
446
- p = left(p)
447
- elsif val_at(right(p)).x <= x0
448
- # right(p) might be in Q, but nothing in the left subtree can be, by the PST property on x.
449
- p = right(p)
450
- elsif val_at(left(p)).x >= x0
451
- # Both children are in Q, so try the higher of them. Note that nothing in either subtree will beat this one.
452
- higher = left(p)
453
- if val_at(right(p)).y > val_at(left(p)).y
454
- higher = right(p)
455
- end
456
- p = higher
457
- elsif val_at(right(p)).y < y0
458
- # Nothing in the right subtree is in Q, but maybe we'll find something in the left
459
- p = left(p)
460
- else
461
- # At this point we know that right(p) \in Q so we need to check it. Nothing in its subtree can beat it so we don't need to
462
- # look there. But there might be something better in the left subtree.
463
- update_highest.call(right(p))
464
- p = left(p)
465
- end
466
- end
467
- update_highest.call(p) # try the leaf
468
- best
469
- end
470
-
471
- # O(n log^2 n)
472
- private def construct_pst
473
- # We follow the algorithm in [3]. Indexing is from 1 there and we follow that here. The algorithm is almost exactly the same as
474
- # for the (max) PST.
475
- h = Math.log2(@size).floor
476
- a = @size - (2**h - 1) # the paper calls it A
477
- sort_subarray(1, @size)
478
- level = 0 # TODO: isn't level always equal to i in the loop?
479
-
480
- (0...h).each do |i|
481
- sense = level.even? ? :max : :min
482
- pow_of_2 = 2**i
483
-
484
- k = a / (2**(h - i))
485
- k1 = 2**(h + 1 - i) - 1
486
- k2 = (1 - k) * 2**(h - i) - 1 + a
487
- k3 = 2**(h - i) - 1
488
- (1..k).each do |j|
489
- l = index_with_extremal_y_in(pow_of_2 + (j - 1) * k1, pow_of_2 + j * k1 - 1, sense:)
490
- swap(l, pow_of_2 + j - 1)
491
- end
492
-
493
- if k < pow_of_2
494
- l = index_with_extremal_y_in(pow_of_2 + k * k1, pow_of_2 + k * k1 + k2 - 1, sense:)
495
- swap(l, pow_of_2 + k)
496
-
497
- m = pow_of_2 + k * k1 + k2
498
- (1..(pow_of_2 - k - 1)).each do |j|
499
- l = index_with_extremal_y_in(m + (j - 1) * k3, m + j * k3 - 1, sense:)
500
- swap(l, pow_of_2 + k + j)
501
- end
502
- end
503
- sort_subarray(2 * pow_of_2, @size)
504
- level += 1
505
- end
506
- end
507
-
508
- ########################################
509
- # Indexing the data structure as though it were from 1, even though the underlying @data is indexed from zero.
510
-
511
- # First element and root of the tree structure
512
- private def root
513
- 1
514
- end
515
-
516
- private def val_at(idx)
517
- @data[idx - 1]
518
- end
519
-
520
- # Indexing is from 1
521
- private def parent(i)
522
- i >> 1
523
- end
524
-
525
- private def left(i)
526
- i << 1
527
- end
528
-
529
- private def right(i)
530
- 1 + (i << 1)
531
- end
532
-
533
- private def leaf?(i)
534
- left(i) > @size
535
- end
536
-
537
- private def one_child?(i)
538
- left(i) <= @size && right(i) > @size
539
- end
540
-
541
- private def swap(index1, index2)
542
- return if index1 == index2
543
-
544
- @data[index1 - 1], @data[index2 - 1] = @data[index2 - 1], @data[index1 - 1]
545
- end
546
-
547
- private def level(i)
548
- count = 0
549
- while i > root
550
- i >>= 1
551
- count += 1
552
- end
553
- count
554
- end
555
-
556
- # The index in @data[l..r] having the largest/smallest value for y
557
- # The sense argument should be :min or :max
558
- private def index_with_extremal_y_in(l, r, sense:)
559
- return nil if r < l
560
-
561
- case sense
562
- when :min
563
- (l..r).min_by { |idx| val_at(idx).y }
564
- when :max
565
- (l..r).max_by { |idx| val_at(idx).y }
566
- else
567
- raise "Bad comparison sense #{sense}"
568
- end
569
- end
570
-
571
- # Sort the subarray @data[l..r]. This is much faster than a Ruby-layer heapsort because it is mostly happening in C.
572
- private def sort_subarray(l, r)
573
- # heapsort_subarray(l, r)
574
- return if l == r # 1-array already sorted!
575
-
576
- l -= 1
577
- r -= 1
578
- @data[l..r] = @data[l..r].sort_by(&:x)
579
- end
580
-
581
- ########################################
582
- # Debugging support
583
- #
584
- # These methods are not written for speed
585
-
586
- # Check that our data satisfies the requirements of a Priority Search Tree:
587
- # - max-heap in y
588
- # - all the x values in the left subtree are less than all the x values in the right subtree
589
- def verify_properties
590
- # It's a min-max heap in y
591
- (2..@size).each do |node|
592
- level = Math.log2(node).floor
593
- parent_level = level - 1
594
-
595
- _, _, min_y, max_y = minmax_in_subtree(node)
596
- parent_y = val_at(parent(node)).y
597
-
598
- it_is_fine = if parent_level.even?
599
- # max!
600
- parent_y > max_y
601
- else
602
- parent_y < min_y
603
- end
604
-
605
- raise "Heap property violated at child #{node}" unless it_is_fine
606
- end
607
-
608
- # Left subtree has x values less than all of the right subtree
609
- (1..@size).each do |node|
610
- next if right(node) >= @size
611
-
612
- left_max = max_x_in_subtree(left(node))
613
- right_min = min_x_in_subtree(right(node))
614
-
615
- raise "Left-right property of x-values violated at #{node}" unless left_max < right_min
616
- end
617
-
618
- nil
619
- end
620
-
621
- private def max_x_in_subtree(root)
622
- minmax_in_subtree(root)[1]
623
- end
624
-
625
- private def min_x_in_subtree(root)
626
- minmax_in_subtree(root)[0]
627
- end
628
-
629
- # Return min_x, max_x, min_y, max_y in subtree rooted at and including root
630
- private def minmax_in_subtree(root)
631
- @minmax_vals ||= []
632
- @minmax_vals[root] ||= calc_minmax_at(root).freeze
633
- end
634
-
635
- # No memoization
636
- private def calc_minmax_at(root)
637
- return [INFINITY, -INFINITY, INFINITY, -INFINITY] if root > @size
638
-
639
- pair = val_at(root)
640
-
641
- return [pair.x, pair.x, pair.y, pair.y] if leaf?(root)
642
-
643
- left = left(root)
644
- left_min_max = minmax_in_subtree(left)
645
- return left_min_max if one_child?(root)
646
-
647
- right = right(root)
648
- right_min_max = minmax_in_subtree(right)
649
-
650
- [
651
- [pair.x, left_min_max[0], right_min_max[0]].min,
652
- [pair.x, left_min_max[1], right_min_max[1]].max,
653
- [pair.y, left_min_max[2], right_min_max[2]].min,
654
- [pair.y, left_min_max[3], right_min_max[3]].max
655
- ]
656
- end
657
-
658
- private def output_quasi_dot
659
- (2..@size).to_a.reverse.map do |node|
660
- "#{val_at(parent(node)).fmt} -- #{val_at(node).fmt}"
661
- end.join("\n")
662
- end
663
-
664
- private def pair_to_s
665
- end
666
-
667
- ########################################
668
- # Dead code
669
-
670
- end