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.
- 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:
|