data_structures_rmolinari 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/lib/data_structures_rmolinari/disjoint_union.rb +30 -14
- data/lib/data_structures_rmolinari/generic_segment_tree.rb +8 -8
- data/lib/data_structures_rmolinari/heap.rb +64 -42
- data/lib/data_structures_rmolinari/max_priority_search_tree.rb +70 -119
- data/lib/data_structures_rmolinari/shared.rb +9 -1
- data/lib/data_structures_rmolinari.rb +17 -14
- metadata +3 -3
- data/lib/data_structures_rmolinari/minmax_priority_search_tree.rb +0 -670
@@ -1,30 +1,27 @@
|
|
1
|
+
require 'must_be'
|
1
2
|
require 'set'
|
2
3
|
require_relative 'shared'
|
3
4
|
|
4
|
-
|
5
|
-
# A priority search tree (PST) stores a set, P, of two-dimensional points (x,y) in a way that allows efficient answes to certain
|
5
|
+
# A priority search tree (PST) stores a set, P, of two-dimensional points (x,y) in a way that allows efficient answers to certain
|
6
6
|
# questions about P.
|
7
7
|
#
|
8
|
-
# (In the current implementation no two points can share an x-value and no two points can share a y-value. This (rather severe)
|
9
|
-
# restriction can be relaxed with some more complicated code.)
|
10
|
-
#
|
11
8
|
# The data structure was introduced in 1985 by Edward McCreight. Later, De, Maheshwari, Nandy, and Smid showed how to construct a
|
12
9
|
# PST in-place (using only O(1) extra memory), at the expense of some slightly more complicated code for the various supported
|
13
10
|
# operations. It is their approach that we have implemented.
|
14
11
|
#
|
15
12
|
# The PST structure is an implicit, balanced binary tree with the following properties:
|
16
|
-
# * The tree is a _max-heap_ in the y coordinate. That is, the point at each node has a y-value
|
13
|
+
# * The tree is a _max-heap_ in the y coordinate. That is, the point at each node has a y-value no greater than its parent.
|
17
14
|
# * For each node p, the x-values of all the nodes in the left subtree of p are less than the x-values of all the nodes in the right
|
18
15
|
# subtree of p. Note that this says nothing about the x-value at the node p itself. The tree is thus _almost_ a binary search tree
|
19
16
|
# in the x coordinate.
|
20
17
|
#
|
21
18
|
# Given a set of n points, we can answer the following questions quickly:
|
22
19
|
#
|
23
|
-
# - +
|
24
|
-
# - +
|
25
|
-
# - +
|
26
|
-
# - +
|
27
|
-
# - +
|
20
|
+
# - +smallest_x_in_ne+: for x0 and y0, what is the leftmost point (x, y) in P satisfying x >= x0 and y >= y0?
|
21
|
+
# - +largest_x_in_nw+: for x0 and y0, what is the rightmost point (x, y) in P satisfying x <= x0 and y >= y0?
|
22
|
+
# - +largest_y_in_ne+: for x0 and y0, what is the highest point (x, y) in P satisfying x >= x0 and y >= y0?
|
23
|
+
# - +largest_y_in_nw+: for x0 and y0, what is the highest point (x, y) in P satisfying x <= x0 and y >= y0?
|
24
|
+
# - +largest_y_in_3_sided+: for x0, x1, and y0, what is the highest point (x, y) in P satisfying x >= x0, x <= x1 and y >= y0?
|
28
25
|
# - +enumerate_3_sided+: for x0, x1, and y0, enumerate all points in P satisfying x >= x0, x <= x1 and y >= y0.
|
29
26
|
#
|
30
27
|
# (Here, "leftmost/rightmost" means "minimal/maximal x", and "highest" means "maximal y".)
|
@@ -33,11 +30,15 @@ require_relative 'shared'
|
|
33
30
|
#
|
34
31
|
# The final operation (enumerate) takes O(m + log n) time, where m is the number of points that are enumerated.
|
35
32
|
#
|
33
|
+
# In the current implementation no two points can share an x-value. This (rather severe) restriction can be relaxed with some more
|
34
|
+
# complicated code, but it hasn't been written yet. See issue #9.
|
35
|
+
#
|
36
|
+
#
|
36
37
|
# There is a related data structure called the Min-max priority search tree so we have called this a "Max priority search tree", or
|
37
38
|
# MaxPST.
|
38
39
|
#
|
39
40
|
# References:
|
40
|
-
# * E.M. McCreight, _Priority search trees_, SIAM J. Comput., 14(2):257-276, 1985.
|
41
|
+
# * E.M. McCreight, _Priority search trees_, SIAM J. Comput., 14(2):257-276, 1985.
|
41
42
|
# * M. De, A. Maheshwari, S. C. Nandy, M. Smid, _An In-Place Priority Search Tree_, 23rd Canadian Conference on Computational
|
42
43
|
# Geometry, 2011
|
43
44
|
class DataStructuresRMolinari::MaxPrioritySearchTree
|
@@ -49,7 +50,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
49
50
|
# @param data [Array] the set P of points presented as an array. The tree is built in the array in-place without cloning.
|
50
51
|
# - Each element of the array must respond to +#x+ and +#y+.
|
51
52
|
# - This is not checked explicitly but a missing method exception will be thrown when we try to call one of them.
|
52
|
-
# - The +x+ values must be distinct
|
53
|
+
# - The +x+ values must be distinct. We raise a +Shared::DataError+ if this isn't the case.
|
53
54
|
# - This is a restriction that simplifies some of the algorithm code. It can be removed as the cost of some extra work. Issue
|
54
55
|
# #9.
|
55
56
|
#
|
@@ -60,9 +61,8 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
60
61
|
@size = @data.size
|
61
62
|
|
62
63
|
construct_pst
|
63
|
-
return unless verify
|
64
64
|
|
65
|
-
verify_properties
|
65
|
+
verify_properties if verify
|
66
66
|
end
|
67
67
|
|
68
68
|
########################################
|
@@ -74,11 +74,11 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
74
74
|
# structure. Define p* as
|
75
75
|
#
|
76
76
|
# - (infty, -infty) if Q \intersect P is empty and
|
77
|
-
# - the highest (max-
|
77
|
+
# - the highest (max-y) point in Q \intersect P otherwise, breaking ties by preferring smaller values of x
|
78
78
|
#
|
79
79
|
# This method returns p* in O(log n) time and O(1) extra space.
|
80
|
-
def
|
81
|
-
|
80
|
+
def largest_y_in_ne(x0, y0)
|
81
|
+
largest_y_in_quadrant(x0, y0, :ne)
|
82
82
|
end
|
83
83
|
|
84
84
|
# Return the highest point in P to the "northwest" of (x0, y0).
|
@@ -87,17 +87,17 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
87
87
|
# structure. Define p* as
|
88
88
|
#
|
89
89
|
# - (-infty, -infty) if Q \intersect P is empty and
|
90
|
-
# - the highest (max-y) point in Q \intersect P otherwise
|
90
|
+
# - the highest (max-y) point in Q \intersect P otherwise, breaking ties by preferring smaller values of x
|
91
91
|
#
|
92
92
|
# This method returns p* in O(log n) time and O(1) extra space.
|
93
|
-
def
|
94
|
-
|
93
|
+
def largest_y_in_nw(x0, y0)
|
94
|
+
largest_y_in_quadrant(x0, y0, :nw)
|
95
95
|
end
|
96
96
|
|
97
|
-
# The basic algorithm is from De et al. section 3.1. We have generalaized it slightly to allow it to calculate both
|
98
|
-
#
|
97
|
+
# The basic algorithm is from De et al. section 3.1. We have generalaized it slightly to allow it to calculate both largest_y_in_ne and
|
98
|
+
# largest_y_in_nw
|
99
99
|
#
|
100
|
-
# Note that
|
100
|
+
# Note that largest_y_in_ne(x0, y0) = largest_y_in_3_sided(x0, infinty, y0) so we don't really need this. But it's a bit faster than the
|
101
101
|
# general case and is a simple algorithm that introduces a typical way that an algorithm interacts with the data structure.
|
102
102
|
#
|
103
103
|
# From the paper:
|
@@ -108,17 +108,17 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
108
108
|
# - If Q intersect P is empty then p* = best
|
109
109
|
#
|
110
110
|
# Here, P is the set of points in our data structure and T_p is the subtree rooted at p
|
111
|
-
private def
|
111
|
+
private def largest_y_in_quadrant(x0, y0, quadrant)
|
112
112
|
quadrant.must_be_in [:ne, :nw]
|
113
113
|
|
114
114
|
p = root
|
115
115
|
if quadrant == :ne
|
116
|
-
best =
|
116
|
+
best = Point.new(INFINITY, -INFINITY)
|
117
117
|
preferred_child = ->(n) { right(n) }
|
118
118
|
nonpreferred_child = ->(n) { left(n) }
|
119
119
|
sufficient_x = ->(x) { x >= x0 }
|
120
120
|
else
|
121
|
-
best =
|
121
|
+
best = Point.new(-INFINITY, -INFINITY)
|
122
122
|
preferred_child = ->(n) { left(n) }
|
123
123
|
nonpreferred_child = ->(n) { right(n) }
|
124
124
|
sufficient_x = ->(x) { x <= x0 }
|
@@ -135,10 +135,10 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
135
135
|
#
|
136
136
|
# takes as input a point t and does the following: if t \in Q and y(t) > y(best) then it assignes best = t
|
137
137
|
#
|
138
|
-
#
|
138
|
+
# We break ties by preferring points with smaller x values
|
139
139
|
update_highest = lambda do |node|
|
140
140
|
t = @data[node]
|
141
|
-
if in_q.call(t) && t.y > best.y
|
141
|
+
if in_q.call(t) && (t.y > best.y || (t.y == best.y && t.x < best.x))
|
142
142
|
best = t
|
143
143
|
end
|
144
144
|
end
|
@@ -194,7 +194,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
194
194
|
# - the leftmost (min-x) point in Q \intersect P otherwise.
|
195
195
|
#
|
196
196
|
# This method returns p* in O(log n) time and O(1) extra space.
|
197
|
-
def
|
197
|
+
def smallest_x_in_ne(x0, y0)
|
198
198
|
extremal_in_x_dimension(x0, y0, :ne)
|
199
199
|
end
|
200
200
|
|
@@ -207,14 +207,14 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
207
207
|
# - the leftmost (min-x) point in Q \intersect P otherwise.
|
208
208
|
#
|
209
209
|
# This method returns p* in O(log n) time and O(1) extra space.
|
210
|
-
def
|
210
|
+
def largest_x_in_nw(x0, y0)
|
211
211
|
extremal_in_x_dimension(x0, y0, :nw)
|
212
212
|
end
|
213
213
|
|
214
|
-
# A genericized version of the paper's
|
214
|
+
# A genericized version of the paper's smallest_x_in_ne that can calculate either smallest_x_in_ne or largest_x_in_nw as specifies via a
|
215
215
|
# parameter.
|
216
216
|
#
|
217
|
-
# Quadrant is either :ne (which gives
|
217
|
+
# Quadrant is either :ne (which gives smallest_x_in_ne) or :nw (which gives largest_x_in_nw).
|
218
218
|
#
|
219
219
|
# From De et al:
|
220
220
|
#
|
@@ -228,10 +228,10 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
228
228
|
|
229
229
|
if quadrant == :ne
|
230
230
|
sign = 1
|
231
|
-
best =
|
231
|
+
best = Point.new(INFINITY, INFINITY)
|
232
232
|
else
|
233
233
|
sign = -1
|
234
|
-
best =
|
234
|
+
best = Point.new(-INFINITY, INFINITY)
|
235
235
|
end
|
236
236
|
|
237
237
|
p = q = root
|
@@ -245,7 +245,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
245
245
|
# takes as input a point t and does the following: if t \in Q and x(t) < x(best) then it assignes best = t
|
246
246
|
#
|
247
247
|
# Note that the paper identifies a node in the tree with its value. We need to grab the correct node.
|
248
|
-
|
248
|
+
update_best = lambda do |node|
|
249
249
|
t = @data[node]
|
250
250
|
if in_q.call(t) && sign * t.x < sign * best.x
|
251
251
|
best = t
|
@@ -261,13 +261,13 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
261
261
|
#
|
262
262
|
# - If x0 <= x(c1) then all subtrees have large enough x values and we look for the leftmost node in c with a large enough y
|
263
263
|
# value. Both p and q are sent into that subtree.
|
264
|
-
# - If x0 >= x(ck) the the rightmost subtree is our only hope
|
264
|
+
# - If x0 >= x(ck) the the rightmost subtree is our only hope
|
265
265
|
# - Otherwise, x(c1) < x0 < x(ck) and we let i be least so that x(ci) <= x0 < x(c(i+1)). Then q becomes the lefmost cj in c not
|
266
266
|
# to the left of ci such that y(cj) >= y0, if any. p becomes ci if y(ci) >= y0 and q otherwise. If there is no such j, we put
|
267
267
|
# q = p. This may leave both of p, q undefined which means there is no useful way forward and we return nils to signal this to
|
268
268
|
# calling code.
|
269
269
|
#
|
270
|
-
# The same logic applies to
|
270
|
+
# The same logic applies to largest_x_in_nw, though everything is "backwards"
|
271
271
|
# - membership of Q depends on having a small-enough value of x, rather than a large-enough one
|
272
272
|
# - among the ci, values towards the end of the array tend not to be in Q while values towards the start of the array tend to be
|
273
273
|
# in Q
|
@@ -302,14 +302,14 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
302
302
|
new_p ||= new_q # if nodes[i] is no good, send p along with q
|
303
303
|
new_q ||= new_p # but if there is no worthwhile value for q we should send it along with p
|
304
304
|
|
305
|
-
return [new_q, new_p] if quadrant == :nw # swap for the
|
305
|
+
return [new_q, new_p] if quadrant == :nw # swap for the largest_x_in_nw case.
|
306
306
|
|
307
307
|
[new_p, new_q]
|
308
308
|
end
|
309
309
|
|
310
310
|
until leaf?(p)
|
311
|
-
|
312
|
-
|
311
|
+
update_best.call(p)
|
312
|
+
update_best.call(q)
|
313
313
|
|
314
314
|
if p == q
|
315
315
|
if one_child?(p)
|
@@ -324,7 +324,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
324
324
|
q = p # p itself is just one layer above the leaves, or is itself a leaf
|
325
325
|
elsif one_child?(q)
|
326
326
|
# This generic approach is not as fast as the bespoke checks described in the paper. But it is easier to maintain the code
|
327
|
-
# this way and allows easy implementation of
|
327
|
+
# this way and allows easy implementation of largest_x_in_nw
|
328
328
|
p, q = determine_next_nodes.call(left(p), right(p), left(q))
|
329
329
|
else
|
330
330
|
p, q = determine_next_nodes.call(left(p), right(p), left(q), right(q))
|
@@ -332,8 +332,8 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
332
332
|
break unless p # we've run out of useful nodes
|
333
333
|
end
|
334
334
|
end
|
335
|
-
|
336
|
-
|
335
|
+
update_best.call(p) if p
|
336
|
+
update_best.call(q) if q
|
337
337
|
best
|
338
338
|
end
|
339
339
|
|
@@ -346,10 +346,10 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
346
346
|
# MaxPST. (Note that Q is empty if x1 < x0.) Define p* as
|
347
347
|
#
|
348
348
|
# - (infty, -infty) if Q \intersect P is empty and
|
349
|
-
# - the highest (max-
|
349
|
+
# - the highest (max-y) point in Q \intersect P otherwise, breaking ties by preferring smaller x values.
|
350
350
|
#
|
351
351
|
# This method returns p* in O(log n) time and O(1) extra space.
|
352
|
-
def
|
352
|
+
def largest_y_in_3_sided(x0, x1, y0)
|
353
353
|
# From the paper:
|
354
354
|
#
|
355
355
|
# The three real numbers x0, x1, and y0 define the three-sided range Q = [x0,x1] X [y0,∞). If Q \intersect P̸ is not \empty,
|
@@ -373,7 +373,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
373
373
|
#
|
374
374
|
# Sometimes we don't have a relevant node to the left or right of Q. The booleans L and R (which we call left and right) track
|
375
375
|
# whether p and q are defined at the moment.
|
376
|
-
best =
|
376
|
+
best = Point.new(INFINITY, -INFINITY)
|
377
377
|
p = q = left = right = nil
|
378
378
|
|
379
379
|
x_range = (x0..x1)
|
@@ -389,7 +389,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
389
389
|
# Note that the paper identifies a node in the tree with its value. We need to grab the correct node.
|
390
390
|
update_highest = lambda do |node|
|
391
391
|
t = @data[node]
|
392
|
-
if in_q.call(t) && t.y > best.y
|
392
|
+
if in_q.call(t) && (t.y > best.y || (t.y == best.y && t.x < best.x))
|
393
393
|
best = t
|
394
394
|
end
|
395
395
|
end
|
@@ -570,7 +570,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
570
570
|
# My high-level understanding of the algorithm
|
571
571
|
# --------------------------------------------
|
572
572
|
#
|
573
|
-
# We need to find all elements of Q \intersect P, so it isn't enough, as it was in
|
573
|
+
# We need to find all elements of Q \intersect P, so it isn't enough, as it was in largest_y_in_3_sided simply to keep track of p and
|
574
574
|
# q. We need to track four nodes, p, p', q', and q which are (with a little handwaving) respectively
|
575
575
|
#
|
576
576
|
# - the rightmost node to the left of Q' = [x0, x1] X [-infinity, infinity],
|
@@ -641,7 +641,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
641
641
|
end
|
642
642
|
current = parent(current)
|
643
643
|
else
|
644
|
-
raise
|
644
|
+
raise InternalLogicError, "Explore(t) state is somehow #{state} rather than 0, 1, or 2."
|
645
645
|
end
|
646
646
|
end
|
647
647
|
end
|
@@ -692,8 +692,6 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
692
692
|
# The four key helpers described in the paper
|
693
693
|
|
694
694
|
# Handle the next step of the subtree at p
|
695
|
-
#
|
696
|
-
# I need to go through this with paper, pencil, and some diagrams.
|
697
695
|
enumerate_left = lambda do
|
698
696
|
if leaf?(p)
|
699
697
|
left = false
|
@@ -786,7 +784,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
786
784
|
p_in = right(p_in)
|
787
785
|
left = true
|
788
786
|
else
|
789
|
-
raise
|
787
|
+
raise InternalLogicError, 'q_in cannot be active, by the value in the right child of p_in!' if right_in
|
790
788
|
|
791
789
|
p = left(p_in)
|
792
790
|
q = right(p_in)
|
@@ -796,7 +794,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
796
794
|
end
|
797
795
|
elsif left_val.x <= x1
|
798
796
|
if right_val.x > x1
|
799
|
-
raise
|
797
|
+
raise InternalLogicError, 'q_in cannot be active, by the value in the right child of p_in!' if right_in
|
800
798
|
|
801
799
|
q = right(p_in)
|
802
800
|
p_in = left(p_in)
|
@@ -810,7 +808,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
810
808
|
right_in = true
|
811
809
|
end
|
812
810
|
else
|
813
|
-
raise
|
811
|
+
raise InternalLogicError, 'q_in cannot be active, by the value in the right child of p_in!' if right_in
|
814
812
|
|
815
813
|
q = left(p_in)
|
816
814
|
deactivate_p_in.call
|
@@ -846,8 +844,8 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
846
844
|
|
847
845
|
# q has two children. Cases!
|
848
846
|
if @data[left(q)].x < x0
|
849
|
-
raise
|
850
|
-
raise
|
847
|
+
raise InternalLogicError, 'p_in should not be active, based on the value at left(q)' if left_in
|
848
|
+
raise InternalLogicError, 'q_in should not be active, based on the value at left(q)' if right_in
|
851
849
|
|
852
850
|
left = true
|
853
851
|
if @data[right(q)].x < x0
|
@@ -878,7 +876,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
878
876
|
|
879
877
|
# Given: q' is active and satisfied x0 <= x(q') <= x1
|
880
878
|
enumerate_right_in = lambda do
|
881
|
-
raise
|
879
|
+
raise InternalLogicError, 'right_in should be true if we call enumerate_right_in' unless right_in
|
882
880
|
|
883
881
|
if @data[q_in].y >= y0
|
884
882
|
report.call(q_in)
|
@@ -910,7 +908,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
910
908
|
# q' has two children
|
911
909
|
right_val = @data[right(q_in)]
|
912
910
|
if left_val.x < x0
|
913
|
-
raise
|
911
|
+
raise InternalLogicError, 'p_in cannot be active, by the value in the left child of q_in' if left_in
|
914
912
|
|
915
913
|
if right_val.x < x0
|
916
914
|
p = right(q_in)
|
@@ -970,7 +968,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
970
968
|
|
971
969
|
while left || left_in || right_in || right
|
972
970
|
# byebug if $do_it
|
973
|
-
raise
|
971
|
+
raise InternalLogicError, 'It should not be that q_in is active but p_in is not' if right_in && !left_in
|
974
972
|
|
975
973
|
set_i = []
|
976
974
|
set_i << :left if left
|
@@ -988,7 +986,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
988
986
|
when :right
|
989
987
|
enumerate_right.call
|
990
988
|
else
|
991
|
-
raise
|
989
|
+
raise InternalLogicError, "bad symbol #{z}"
|
992
990
|
end
|
993
991
|
end
|
994
992
|
return result unless block_given?
|
@@ -999,13 +997,14 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
999
997
|
|
1000
998
|
private def construct_pst
|
1001
999
|
raise DataError, 'Duplicate x values are not supported' if contains_duplicates?(@data, by: :x)
|
1002
|
-
raise DataError, 'Duplicate y values are not supported' if contains_duplicates?(@data, by: :y)
|
1003
1000
|
|
1004
|
-
# We follow the algorithm in the paper by De, Maheshwari et al.
|
1001
|
+
# We follow the algorithm in the paper by De, Maheshwari et al, which takes O(n log^2 n) time. Their follow-up paper that
|
1002
|
+
# defines the Min-max PST, describes how to do the construction in O(n log n) time, but it is more complex and probably not
|
1003
|
+
# worth the trouble of both a bespoke heapsort the special sorting algorithm of Katajainen and Pasanen.
|
1005
1004
|
|
1006
|
-
# Since we are building an implicit binary tree, things are simpler if the array is 1-based. This
|
1007
|
-
#
|
1008
|
-
# construction.
|
1005
|
+
# Since we are building an implicit binary tree, things are simpler if the array is 1-based. This requires a malloc (perhaps)
|
1006
|
+
# and memcpy (for sure), which isn't great, but it's in the C layer so cheap compared to the O(n log^2 n) work we need to do for
|
1007
|
+
# construction.
|
1009
1008
|
@data.unshift nil
|
1010
1009
|
|
1011
1010
|
h = Math.log2(@size).floor
|
@@ -1052,63 +1051,14 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
1052
1051
|
end
|
1053
1052
|
end
|
1054
1053
|
|
1055
|
-
########################################
|
1056
|
-
# Tree arithmetic
|
1057
|
-
|
1058
|
-
# # First element and root of the tree structure
|
1059
|
-
# private def root
|
1060
|
-
# 1
|
1061
|
-
# end
|
1062
|
-
|
1063
|
-
# # Indexing is from 1
|
1064
|
-
# private def parent(i)
|
1065
|
-
# i >> 1
|
1066
|
-
# end
|
1067
|
-
|
1068
|
-
# private def left(i)
|
1069
|
-
# i << 1
|
1070
|
-
# end
|
1071
|
-
|
1072
|
-
# private def right(i)
|
1073
|
-
# 1 + (i << 1)
|
1074
|
-
# end
|
1075
|
-
|
1076
|
-
# private def level(i)
|
1077
|
-
# l = 0
|
1078
|
-
# while i > root
|
1079
|
-
# i >>= 1
|
1080
|
-
# l += 1
|
1081
|
-
# end
|
1082
|
-
# l
|
1083
|
-
# end
|
1084
|
-
|
1085
|
-
# # i has no children
|
1086
|
-
# private def leaf?(i)
|
1087
|
-
# i > @last_non_leaf
|
1088
|
-
# end
|
1089
|
-
|
1090
|
-
# # i has exactly one child (the left)
|
1091
|
-
# private def one_child?(i)
|
1092
|
-
# i == @parent_of_one_child
|
1093
|
-
# end
|
1094
|
-
|
1095
|
-
# # i has two children
|
1096
|
-
# private def two_children?(i)
|
1097
|
-
# i <= @last_parent_of_two_children
|
1098
|
-
# end
|
1099
|
-
|
1100
|
-
# # i is the left child of its parent.
|
1101
|
-
# private def left_child?(i)
|
1102
|
-
# (i & 1).zero?
|
1103
|
-
# end
|
1104
|
-
|
1105
1054
|
private def swap(index1, index2)
|
1106
1055
|
return if index1 == index2
|
1107
1056
|
|
1108
1057
|
@data[index1], @data[index2] = @data[index2], @data[index1]
|
1109
1058
|
end
|
1110
1059
|
|
1111
|
-
# The index in @data[l..r] having the largest value for y
|
1060
|
+
# The index in @data[l..r] having the largest value for y, breaking ties with the smaller x value. Since we are already sorted by
|
1061
|
+
# x we don't actually need to check this.
|
1112
1062
|
private def index_with_largest_y_in(l, r)
|
1113
1063
|
return nil if r < l
|
1114
1064
|
|
@@ -1134,7 +1084,8 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
1134
1084
|
private def verify_properties
|
1135
1085
|
# It's a max-heap in y
|
1136
1086
|
(2..@size).each do |node|
|
1137
|
-
|
1087
|
+
byebug unless @data[node].y <= @data[parent(node)].y
|
1088
|
+
raise InternalLogicError, "Heap property violated at child #{node}" unless @data[node].y <= @data[parent(node)].y
|
1138
1089
|
end
|
1139
1090
|
|
1140
1091
|
# Left subtree has x values less than all of the right subtree
|
@@ -1144,7 +1095,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
1144
1095
|
left_max = max_x_in_subtree(left(node))
|
1145
1096
|
right_min = min_x_in_subtree(right(node))
|
1146
1097
|
|
1147
|
-
raise
|
1098
|
+
raise InternalLogicError, "Left-right property of x-values violated at #{node}" unless left_max < right_min
|
1148
1099
|
end
|
1149
1100
|
end
|
1150
1101
|
|
@@ -1,11 +1,19 @@
|
|
1
1
|
# Some odds and ends shared by other classes
|
2
2
|
module Shared
|
3
|
+
# Infinity without having to put a +Float::+ prefix every time
|
3
4
|
INFINITY = Float::INFINITY
|
4
5
|
|
5
|
-
|
6
|
+
# An (x, y) coordinate pair.
|
7
|
+
Point = Struct.new(:x, :y)
|
6
8
|
|
7
9
|
# @private
|
10
|
+
|
11
|
+
# Used for errors related to logic errors in client code
|
8
12
|
class LogicError < StandardError; end
|
13
|
+
# Used for errors related to logic errors in library code
|
14
|
+
class InternalLogicError < LogicError; end
|
15
|
+
|
16
|
+
# Used for errors related to data, such as duplicated elements where they must be distinct.
|
9
17
|
class DataError < StandardError; end
|
10
18
|
|
11
19
|
# @private
|
@@ -1,7 +1,10 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
1
3
|
require_relative 'data_structures_rmolinari/shared'
|
2
4
|
|
3
5
|
module DataStructuresRMolinari
|
4
|
-
|
6
|
+
# A struct responding to +.x+ and +.y+.
|
7
|
+
Point = Shared::Point
|
5
8
|
end
|
6
9
|
|
7
10
|
# These define classes inside module DataStructuresRMolinari
|
@@ -9,15 +12,16 @@ require_relative 'data_structures_rmolinari/disjoint_union'
|
|
9
12
|
require_relative 'data_structures_rmolinari/generic_segment_tree'
|
10
13
|
require_relative 'data_structures_rmolinari/heap'
|
11
14
|
require_relative 'data_structures_rmolinari/max_priority_search_tree'
|
12
|
-
require_relative 'data_structures_rmolinari/minmax_priority_search_tree'
|
13
15
|
|
16
|
+
# A namespace to hold the provided classes. We want to avoid polluting the global namespace with names like "Heap"
|
14
17
|
module DataStructuresRMolinari
|
15
18
|
########################################
|
16
19
|
# Concrete instances of Segment Tree
|
17
20
|
#
|
18
|
-
# @todo consider moving these into generic_segment_tree.rb
|
21
|
+
# @todo consider moving these into generic_segment_tree.rb and renaming that file
|
19
22
|
|
20
|
-
#
|
23
|
+
# A segment tree that for an array A(0...n) answers questions of the form "what is the maximum value in the subinterval A(i..j)?"
|
24
|
+
# in O(log n) time.
|
21
25
|
class MaxValSegmentTree
|
22
26
|
extend Forwardable
|
23
27
|
|
@@ -25,28 +29,27 @@ module DataStructuresRMolinari
|
|
25
29
|
def_delegator :@structure, :update_at
|
26
30
|
|
27
31
|
# @param data an object that contains values at integer indices based at 0, via +data[i]+.
|
28
|
-
# -
|
32
|
+
# - This will usually be an Array, but it could also be a hash or a proc.
|
29
33
|
def initialize(data)
|
30
|
-
@structure =
|
34
|
+
@structure = SegmentTreeTemplate.new(
|
31
35
|
combine: ->(a, b) { [a, b].max },
|
32
36
|
single_cell_array_val: ->(i) { data[i] },
|
33
37
|
size: data.size,
|
34
|
-
identity: -
|
38
|
+
identity: -Shared::INFINITY
|
35
39
|
)
|
36
40
|
end
|
37
41
|
|
38
|
-
# The maximum value in A(i..j)
|
42
|
+
# The maximum value in A(i..j).
|
39
43
|
#
|
40
44
|
# The arguments must be integers in 0...(A.size)
|
41
|
-
# @return the largest value in A(i..j).
|
42
|
-
# - Return +nil+ if i > j
|
45
|
+
# @return the largest value in A(i..j) or -Infinity if i > j.
|
43
46
|
def max_on(i, j)
|
44
47
|
@structure.query_on(i, j)
|
45
48
|
end
|
46
49
|
end
|
47
50
|
|
48
|
-
# A segment tree that for an array A(0...n)
|
49
|
-
#
|
51
|
+
# A segment tree that for an array A(0...n) answers questions of the form "what is the index of the maximal value in the
|
52
|
+
# subinterval A(i..j)?" in O(log n) time.
|
50
53
|
class IndexOfMaxValSegmentTree
|
51
54
|
extend Forwardable
|
52
55
|
|
@@ -55,7 +58,7 @@ module DataStructuresRMolinari
|
|
55
58
|
|
56
59
|
# @param (see MaxValSegmentTree#initialize)
|
57
60
|
def initialize(data)
|
58
|
-
@structure =
|
61
|
+
@structure = SegmentTreeTemplate.new(
|
59
62
|
combine: ->(p1, p2) { p1[1] >= p2[1] ? p1 : p2 },
|
60
63
|
single_cell_array_val: ->(i) { [i, data[i]] },
|
61
64
|
size: data.size,
|
@@ -66,7 +69,7 @@ module DataStructuresRMolinari
|
|
66
69
|
# The index of the maximum value in A(i..j)
|
67
70
|
#
|
68
71
|
# The arguments must be integers in 0...(A.size)
|
69
|
-
# @return (Integer, nil) the index of the largest value in A(i..j).
|
72
|
+
# @return (Integer, nil) the index of the largest value in A(i..j) or +nil+ if i > j.
|
70
73
|
# - If there is more than one entry with that value, return one the indices. There is no guarantee as to which one.
|
71
74
|
# - Return +nil+ if i > j
|
72
75
|
def index_of_max_val_on(i, j)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: data_structures_rmolinari
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rory Molinari
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-01-
|
11
|
+
date: 2023-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: must_be
|
@@ -80,12 +80,12 @@ executables: []
|
|
80
80
|
extensions: []
|
81
81
|
extra_rdoc_files: []
|
82
82
|
files:
|
83
|
+
- CHANGELOG.md
|
83
84
|
- lib/data_structures_rmolinari.rb
|
84
85
|
- lib/data_structures_rmolinari/disjoint_union.rb
|
85
86
|
- lib/data_structures_rmolinari/generic_segment_tree.rb
|
86
87
|
- lib/data_structures_rmolinari/heap.rb
|
87
88
|
- lib/data_structures_rmolinari/max_priority_search_tree.rb
|
88
|
-
- lib/data_structures_rmolinari/minmax_priority_search_tree.rb
|
89
89
|
- lib/data_structures_rmolinari/shared.rb
|
90
90
|
homepage: https://github.com/rmolinari/data_structures
|
91
91
|
licenses:
|