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,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 less than its parent.
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
- # - +leftmost_ne+: for x0 and y0, what is the leftmost point (x, y) in P satisfying x >= x0 and y >= y0?
24
- # - +rightmost_nw+: for x0 and y0, what is the rightmost point (x, y) in P satisfying x <= x0 and y >= y0?
25
- # - +highest_ne+: for x0 and y0, what is the highest point (x, y) in P satisfying x >= x0 and y >= y0?
26
- # - +highest_nw+: for x0 and y0, what is the highest point (x, y) in P satisfying x <= x0 and y >= y0?
27
- # - +highest_3_sided+: for x0, x1, and y0, what is the highest point (x, y) in P satisfying x >= x0, x <= x1 and y >= y0?
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. Later, De,
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, as must the +y+ values. We raise a +Shared::DataError+ if this isn't the case.
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-x) point in Q \intersect P otherwise.
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 highest_ne(x0, y0)
81
- highest_in_quadrant(x0, y0, :ne)
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 highest_nw(x0, y0)
94
- highest_in_quadrant(x0, y0, :nw)
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 highest_ne and
98
- # highest_nw
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 highest_ne(x0, y0) = highest_3_sided(x0, infinty, y0) so we don't really need this. But it's a bit faster than the
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 highest_in_quadrant(x0, y0, quadrant)
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 = Pair.new(INFINITY, -INFINITY)
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 = Pair.new(-INFINITY, -INFINITY)
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
- # Note that the paper identifies a node in the tree with its value. We need to grab the correct node.
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 leftmost_ne(x0, y0)
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 rightmost_nw(x0, y0)
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 leftmost_ne that can calculate either leftmost_ne or rightmost_nw as specifies via a
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 leftmost_ne) or :nw (which gives rightmost_nw).
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 = Pair.new(INFINITY, INFINITY)
231
+ best = Point.new(INFINITY, INFINITY)
232
232
  else
233
233
  sign = -1
234
- best = Pair.new(-INFINITY, INFINITY)
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
- update_leftmost = lambda do |node|
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 the rightmost subtree.
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 rightmost_nw, though everything is "backwards"
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 rightmost_nw case.
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
- update_leftmost.call(p)
312
- update_leftmost.call(q)
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 rightmost_nw
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
- update_leftmost.call(p) if p
336
- update_leftmost.call(q) if q
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-x) point in Q \intersect P otherwise.
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 highest_3_sided(x0, x1, y0)
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 = Pair.new(INFINITY, -INFINITY)
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 highest_3_sided simply to keep track of p and
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 LogicError, "Explore(t) state is somehow #{state} rather than 0, 1, or 2."
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 LogicError, 'q_in cannot be active, by the value in the right child of p_in!' if right_in
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 LogicError, 'q_in cannot be active, by the value in the right child of p_in!' if right_in
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 LogicError, 'q_in cannot be active, by the value in the right child of p_in!' if right_in
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 LogicError, 'p_in should not be active, based on the value at left(q)' if left_in
850
- raise LogicError, 'q_in should not be active, based on the value at left(q)' if right_in
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 LogicError, 'right_in should be true if we call enumerate_right_in' unless right_in
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 LogicError, 'p_in cannot be active, by the value in the left child of q_in' if left_in
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 LogicError, 'It should not be that q_in is active but p_in is not' if right_in && !left_in
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 LogicError, "bad symbol #{z}"
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 probably requires a malloc and
1007
- # data copy, 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
1008
- # construction. In fact, we are probably doing O(n^2) work because of all the calls to #index_with_largest_y_in.
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
- raise LogicError, "Heap property violated at child #{node}" unless @data[node].y < @data[parent(node)].y
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 LogicError, "Left-right property of x-values violated at #{node}" unless left_max < right_min
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
- Pair = Struct.new(:x, :y)
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
- Pair = Shared::Pair
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
- # Takes an array A(0...n) and tells us what the maximum value is on a subinterval A(i..j) in O(log n) time.
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
- # - The usual use case will be an Array, but it could also be a hash or a proc of some sort.
32
+ # - This will usually be an Array, but it could also be a hash or a proc.
29
33
  def initialize(data)
30
- @structure = GenericSegmentTree.new(
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: -Float::INFINITY
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) efficiently answers questions of the form "what is the index of the maximal value in
49
- # a subinterval A(i..j) in O(log n) time.
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 = GenericSegmentTree.new(
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.2.2
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-06 00:00:00.000000000 Z
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: