data_structures_rmolinari 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3765b8df91fcc62eb885e32ff5ad4b0b4678bba6f322cb5c8282657052aed8c6
4
- data.tar.gz: 845bea3649dc51dab697927132c0fc2f62dcacf5c25e1c717b99e57819b52286
3
+ metadata.gz: 9f006234ee3b216d5607e9b10bb1958a6107ccfa0cc8c359f98383dc7fde14ee
4
+ data.tar.gz: f281ab0768e24e7c983cd046ba7b185dab8fd972fb3065fd73ff575782bf5486
5
5
  SHA512:
6
- metadata.gz: 23687561ec6ddb12369ca5e75db33ffd710295097cd0c91b72fb278fea3b11b23152867bb4aea6a4fd17b2f95184fb5433c9ff009db92a5c4bab78686ae472de
7
- data.tar.gz: d930a674f85aa0a57030ed59f2b39979c7dbb8d8f74e0a9718d44112c1efe05657e0d55773b623c3724f040054e2fedf453de30b72d5631404642081455406f9
6
+ metadata.gz: e274a97f177fad44bad20ecf24ecca1385fee3c217e7e42aac076c24377970c6444dfdbadc6fd3e1e201555177429c9f8eddaee211e463dd60f6b36e74004eec
7
+ data.tar.gz: 293fc0b2973a8d851c27f4e64177dbf7b9a25b2bb7eb9efb4b33abdb07c4e006f80f4450996ef99da7e8bb1516ca8aa89ab893258960d9127d101995906254ed
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [0.3.0] 2023-01-06
6
+
7
+ ### Added
8
+
9
+ - Start this file
10
+ - `Heap` can be constructed as "non-addressable"
11
+ - `update` is not possible but duplicates can be inserted and overall performance is a little better.
12
+
13
+ ### Changed
14
+
15
+ - `LogicError` gets a subclassed `InternalLogicError` for issues inside the library.
16
+ - `Shared::Pair` becomes `Shared::Point`
17
+ - this doesn't change the API of `MaxPrioritySearchTree` because of ducktyping. But client code (of which there is none) might be
18
+ using the `Pair` name.
@@ -4,7 +4,7 @@
4
4
  # The data structure provides efficient actions to merge two disjoint subsets, i.e., replace them by their union, and determine if
5
5
  # two elements are in the same subset.
6
6
  #
7
- # The elements of the set must be 0, 1, ..., n-1, where n is the size of the universe. Client code can map its data to these
7
+ # The elements of the set are 0, 1, ..., n-1, where n is the size of the universe. Client code can map its data to these
8
8
  # representatives.
9
9
  #
10
10
  # See https://en.wikipedia.org/wiki/Disjoint-set_data_structure for a good introduction.
@@ -60,7 +60,7 @@ class DataStructuresRMolinari::DisjointUnion
60
60
  end
61
61
 
62
62
  private def check_value(v)
63
- raise "Value must be given and be in (0..#{@size - 1})" unless v && v.between?(0, @size - 1)
63
+ raise DataError, "Value must be given and be in (0..#{@size - 1})" unless v && v.between?(0, @size - 1)
64
64
  end
65
65
 
66
66
  private def link(e, f)
@@ -13,7 +13,7 @@ require_relative 'shared'
13
13
  # Ruby.
14
14
  #
15
15
  # This is a generic implementation, intended to allow easy configuration for concrete instances. See the parameters to the
16
- # initializer and the defintiaons concrete realisations like MaxValSegmentTree.
16
+ # initializer and the definitions of concrete realisations like MaxValSegmentTree.
17
17
  #
18
18
  # We do O(n) work to build the internal data structure at initialization. Then we answer queries in O(log n) time.
19
19
  class DataStructuresRMolinari::GenericSegmentTree
@@ -24,14 +24,14 @@ class DataStructuresRMolinari::GenericSegmentTree
24
24
  # - For example, if we are calculating sums over subintervals, combine.call(a, b) = a + b, while if we are doing maxima we will
25
25
  # return max(a, b).
26
26
  # - Things get more complicated when we are calculating, say, the _index_ of the maximal value in a subinterval. Now it is not
27
- # enough simple to store that index at each tree node, because to combine the indices from two child nodes we need to know
27
+ # enough simply to store that index at each tree node, because to combine the indices from two child nodes we need to know
28
28
  # both the index of the maximal element in each child node's interval, but also the maximal values themselves, so we know
29
29
  # which one "wins" for the parent node. This affects the sort of work we need to do when combining and the value provided by
30
30
  # the +single_cell_array_val+ lambda.
31
31
  # @param single_cell_array_val a lambda that takes an index i and returns the value we need to store in the #build
32
32
  # operation for the subinterval i..i.
33
- # - This is often simply be the value data[i], but in some cases it will be something else. For example, when we are
34
- # calculating the index of the maximal value on each subinterval we will retern the pair [i, data[i]] here.
33
+ # - This will often simply be the value data[i], but in some cases it will be something else. For example, when we are
34
+ # calculating the index of the maximal value on each subinterval we need [i, data[i]] here.
35
35
  # - If +update_at+ is called later, this lambda must close over the underlying data in a way that captures the updated value.
36
36
  # @param size the size of the underlying data array, used in certain internal arithmetic.
37
37
  # @param identity the value to return when we are querying on an empty interval
@@ -96,7 +96,7 @@ class DataStructuresRMolinari::GenericSegmentTree
96
96
  private def update_val_at(idx, tree_idx, tree_l, tree_r)
97
97
  if tree_l == tree_r
98
98
  # We have found the spot!
99
- raise LogicError, 'tree_l == tree_r, but they do not agree with the idx holding the updated value' unless tree_l == idx
99
+ raise InternalLogicError, 'tree_l == tree_r, but they do not agree with the idx holding the updated value' unless tree_l == idx
100
100
 
101
101
  @tree[tree_idx] = @single_cell_array_val.call(tree_l)
102
102
  else
@@ -13,8 +13,8 @@ require_relative 'shared'
13
13
  # - +empty?+
14
14
  # - is the heap empty?
15
15
  # - O(1)
16
- # - +insert+
17
- # - add a new element to the heap with an associated priority
16
+ # - +insert(item, priority)+
17
+ # - add a new item to the heap with an associated priority
18
18
  # - O(log N)
19
19
  # - +top+
20
20
  # - return the lowest-priority element, which is the element at the root of the tree. In a max-heap this is the highest-priority
@@ -23,12 +23,18 @@ require_relative 'shared'
23
23
  # - +pop+
24
24
  # - removes and returns the item that would be returned by +top+
25
25
  # - O(log N)
26
- # - +update+
26
+ # - +update(item, priority)+
27
27
  # - tell the heap that the priority of a particular item has changed
28
28
  # - O(log N)
29
29
  #
30
30
  # Here N is the number of elements in the heap.
31
31
  #
32
+ # The internal requirements needed to implement +update+ have several consequences.
33
+ # - Items added to the heap must be distinct. Otherwise we would not know which occurrence to update
34
+ # - There is some bookkeeping overhead.
35
+ # If client code doesn't need to call +update+ then we can create a "non-addressable" heap that allows for the insertion of
36
+ # duplicate items and has slightly faster runtime overall. See the arguments to the initializer.
37
+ #
32
38
  # References:
33
39
  #
34
40
  # - https://en.wikipedia.org/wiki/Binary_heap
@@ -36,31 +42,31 @@ require_relative 'shared'
36
42
  # DOI 10.1007/s00224-017-9760-2
37
43
  #
38
44
  # @todo
39
- # - allow for priorities comparable only via +<=>+, like arrays
40
- # - this requires different handling for max-heaps, as we can't just negate the priorities and use min-heap logic
41
- # - relax the requirement that priorities must be comparable vai +<+ and respond to negation. Instead, allow comparison via +<=>+
42
- # and handle max-heaps differently.
43
- # - this will allow priorities to be arrays for tie-breakers and similar.
44
- # - offer a non-addressable version that doesn't support +update+
45
- # - configure through the initializer
46
- # - other operations will be a little quicker, and we can add the same item more than once. The paper by Chen et al. referenced
47
- # in the Wikipedia article for Pairing Heaps suggests that using such a priority queue for Dijkstra's algorithm and inserting
48
- # multiple copies of a key rather than updating its priority is faster in practice than other approaches that have better
49
- # theoretical performance.
45
+ # - let caller see the priority of the top element. Maybe this is useful sometimes.
50
46
  class DataStructuresRMolinari::Heap
47
+ include Shared
51
48
  include Shared::BinaryTreeArithmetic
52
49
 
50
+ # The number of items currently in the heap
53
51
  attr_reader :size
54
52
 
55
- Pair = Struct.new(:priority, :item)
53
+ # An (item, priority) pair
54
+ InternalPair = Struct.new(:item, :priority)
55
+ private_constant :InternalPair
56
56
 
57
57
  # @param max_heap when truthy, make a max-heap rather than a min-heap
58
- # @param debug when truthy, verify the heap property after each update than might violate it. This makes operations much slower.
59
- def initialize(max_heap: false, debug: false)
58
+ # @param addressable when truthy, the heap is _addressable_. This means that
59
+ # - item priorities are updatable with +update(item, p)+, and
60
+ # - items added to the heap must be distinct.
61
+ # When falsy, priorities are not updateable but items may be inserted multiple times. Operations are slightly faster because
62
+ # there is less internal bookkeeping.
63
+ # @param debug when truthy, verify the heap property after each change that might violate it. This makes operations much slower.
64
+ def initialize(max_heap: false, addressable: true, debug: false)
60
65
  @data = []
61
66
  @size = 0
62
67
  @max_heap = max_heap
63
- @index_of = {}
68
+ @addressable = addressable
69
+ @index_of = {} # used in addressable heaps
64
70
  @debug = debug
65
71
  end
66
72
 
@@ -70,26 +76,24 @@ class DataStructuresRMolinari::Heap
70
76
  end
71
77
 
72
78
  # Insert a new element into the heap with the given priority.
73
- # @param value the item to be inserted. It is an error to insert an item that is already present in the heap, though we don't
74
- # check for this.
75
- # @param priority the priority to use for new item. The values used as priorities ust be totally ordered via +<+ and, if +self+ is
76
- # a max-heap, must respond to negation +@-+ in the natural order-respecting way.
77
- # @todo
78
- # - check for duplicate
79
+ # @param value the item to be inserted.
80
+ # - If the heap is addressible (the default) it is an error to insert an item that is already present in the heap.
81
+ # @param priority the priority to use for new item. The values used as priorities must be totally ordered via +<=>+.
79
82
  def insert(value, priority)
80
- priority *= -1 if @max_heap
83
+ raise DataError, "Heap already contains #{value}" if @addressable && contains?(value)
81
84
 
82
85
  @size += 1
83
86
 
84
- d = Pair.new(priority, value)
87
+ d = InternalPair.new(value, priority)
85
88
  assign(d, @size)
86
89
 
87
90
  sift_up(@size)
88
91
  end
89
92
 
90
93
  # Return the top of the heap without removing it
91
- # @return the value with minimal (maximal for max-heaps) priority. Strictly speaking, it returns the item at the root of the
92
- # binary tree; this element has minimal priority, but there may be other elements with the same priority.
94
+ # @return a value with minimal priority (maximal for max-heaps). Strictly speaking, it returns the item at the root of the
95
+ # binary tree; this element has minimal priority, but there may be other elements with the same priority and they do not appear
96
+ # at the top of the heap in any guaranteed order.
93
97
  def top
94
98
  raise 'Heap is empty!' unless @size.positive?
95
99
 
@@ -100,12 +104,11 @@ class DataStructuresRMolinari::Heap
100
104
  # @return (see #top)
101
105
  def pop
102
106
  result = top
103
- @index_of.delete(result)
104
-
105
107
  assign(@data[@size], root)
106
108
 
107
109
  @data[@size] = nil
108
110
  @size -= 1
111
+ @index_of.delete(result) if @addressable
109
112
 
110
113
  sift_down(root) if @size.positive?
111
114
 
@@ -113,21 +116,20 @@ class DataStructuresRMolinari::Heap
113
116
  end
114
117
 
115
118
  # Update the priority of the given element and maintain the necessary heap properties.
119
+ #
116
120
  # @param element the item whose priority we are updating. It is an error to update the priority of an element not already in the
117
121
  # heap
118
122
  # @param priority the new priority
119
- #
120
- # @todo
121
- # - check that the element is in the heap
122
123
  def update(element, priority)
123
- priority *= -1 if @max_heap
124
+ raise LogicError, 'Cannot update priorities in a non-addressable heap' unless @addressable
125
+ raise DataError, "Cannot update priority for value #{element} not already in the heap" unless contains?(element)
124
126
 
125
127
  idx = @index_of[element]
126
128
  old = @data[idx].priority
127
129
  @data[idx].priority = priority
128
- if priority > old
130
+ if less_than_priority?(old, priority)
129
131
  sift_down(idx)
130
- elsif priority < old
132
+ elsif less_than_priority?(priority, old)
131
133
  sift_up(idx)
132
134
  end
133
135
 
@@ -141,7 +143,7 @@ class DataStructuresRMolinari::Heap
141
143
  x = @data[idx]
142
144
  while idx != root
143
145
  i = parent(idx)
144
- break unless x.priority < @data[i].priority
146
+ break unless less_than?(x, @data[i])
145
147
 
146
148
  assign(@data[i], idx)
147
149
  idx = i
@@ -156,9 +158,9 @@ class DataStructuresRMolinari::Heap
156
158
  x = @data[idx]
157
159
 
158
160
  while (j = left(idx)) <= @size
159
- j += 1 if j + 1 <= @size && @data[j + 1].priority < @data[j].priority
161
+ j += 1 if j + 1 <= @size && less_than?(@data[j + 1], @data[j])
160
162
 
161
- break unless @data[j].priority < x.priority
163
+ break unless less_than?(@data[j], x)
162
164
 
163
165
  assign(@data[j], idx)
164
166
  idx = j
@@ -171,7 +173,27 @@ class DataStructuresRMolinari::Heap
171
173
  # Put the pair in the given heap location
172
174
  private def assign(pair, idx)
173
175
  @data[idx] = pair
174
- @index_of[pair.item] = idx
176
+ @index_of[pair.item] = idx if @addressable
177
+ end
178
+
179
+ # Compare the priorities of two items with <=> and return truthy exactly when the result is -1.
180
+ #
181
+ # If this is a max-heap return truthy exactly when the result of <=> is 1.
182
+ #
183
+ # The arguments can also be the priorities themselves.
184
+ private def less_than?(p1, p2)
185
+ less_than_priority?(p1.priority, p2.priority)
186
+ end
187
+
188
+ # Direct comparison of priorities
189
+ private def less_than_priority?(priority1, priority2)
190
+ return (priority1 <=> priority2) == 1 if @max_heap
191
+
192
+ (priority1 <=> priority2) == -1
193
+ end
194
+
195
+ private def contains?(item)
196
+ !!@index_of[item]
175
197
  end
176
198
 
177
199
  # For debugging
@@ -180,8 +202,8 @@ class DataStructuresRMolinari::Heap
180
202
  left = left(idx)
181
203
  right = right(idx)
182
204
 
183
- raise "Heap property violated by left child of index #{idx}" if left <= @size && @data[idx].priority >= @data[left].priority
184
- raise "Heap property violated by right child of index #{idx}" if right <= @size && @data[idx].priority >= @data[right].priority
205
+ raise InternalLogicError, "Heap property violated by left child of index #{idx}" if left <= @size && less_than?(@data[left], @data[idx])
206
+ raise InternalLogicError, "Heap property violated by right child of index #{idx}" if right <= @size && less_than?(@data[right], @data[idx])
185
207
  end
186
208
  end
187
209
  end
@@ -1,13 +1,9 @@
1
1
  require 'set'
2
2
  require_relative 'shared'
3
3
 
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
4
+ # 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
5
  # questions about P.
7
6
  #
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
7
  # The data structure was introduced in 1985 by Edward McCreight. Later, De, Maheshwari, Nandy, and Smid showed how to construct a
12
8
  # PST in-place (using only O(1) extra memory), at the expense of some slightly more complicated code for the various supported
13
9
  # operations. It is their approach that we have implemented.
@@ -33,11 +29,15 @@ require_relative 'shared'
33
29
  #
34
30
  # The final operation (enumerate) takes O(m + log n) time, where m is the number of points that are enumerated.
35
31
  #
32
+ # In the current implementation no two points can share an x-value and no two points can share a y-value. This (rather severe)
33
+ # restriction can be relaxed with some more complicated code.
34
+ #
35
+ #
36
36
  # 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
37
  # MaxPST.
38
38
  #
39
39
  # References:
40
- # * E.M. McCreight, _Priority search trees_, SIAM J. Comput., 14(2):257-276, 1985. Later, De,
40
+ # * E.M. McCreight, _Priority search trees_, SIAM J. Comput., 14(2):257-276, 1985.
41
41
  # * M. De, A. Maheshwari, S. C. Nandy, M. Smid, _An In-Place Priority Search Tree_, 23rd Canadian Conference on Computational
42
42
  # Geometry, 2011
43
43
  class DataStructuresRMolinari::MaxPrioritySearchTree
@@ -113,12 +113,12 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
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 }
@@ -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
@@ -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)
@@ -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
@@ -786,7 +786,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
786
786
  p_in = right(p_in)
787
787
  left = true
788
788
  else
789
- raise LogicError, 'q_in cannot be active, by the value in the right child of p_in!' if right_in
789
+ raise InternalLogicError, 'q_in cannot be active, by the value in the right child of p_in!' if right_in
790
790
 
791
791
  p = left(p_in)
792
792
  q = right(p_in)
@@ -796,7 +796,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
796
796
  end
797
797
  elsif left_val.x <= x1
798
798
  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
799
+ raise InternalLogicError, 'q_in cannot be active, by the value in the right child of p_in!' if right_in
800
800
 
801
801
  q = right(p_in)
802
802
  p_in = left(p_in)
@@ -810,7 +810,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
810
810
  right_in = true
811
811
  end
812
812
  else
813
- raise LogicError, 'q_in cannot be active, by the value in the right child of p_in!' if right_in
813
+ raise InternalLogicError, 'q_in cannot be active, by the value in the right child of p_in!' if right_in
814
814
 
815
815
  q = left(p_in)
816
816
  deactivate_p_in.call
@@ -846,8 +846,8 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
846
846
 
847
847
  # q has two children. Cases!
848
848
  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
849
+ raise InternalLogicError, 'p_in should not be active, based on the value at left(q)' if left_in
850
+ raise InternalLogicError, 'q_in should not be active, based on the value at left(q)' if right_in
851
851
 
852
852
  left = true
853
853
  if @data[right(q)].x < x0
@@ -878,7 +878,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
878
878
 
879
879
  # Given: q' is active and satisfied x0 <= x(q') <= x1
880
880
  enumerate_right_in = lambda do
881
- raise LogicError, 'right_in should be true if we call enumerate_right_in' unless right_in
881
+ raise InternalLogicError, 'right_in should be true if we call enumerate_right_in' unless right_in
882
882
 
883
883
  if @data[q_in].y >= y0
884
884
  report.call(q_in)
@@ -910,7 +910,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
910
910
  # q' has two children
911
911
  right_val = @data[right(q_in)]
912
912
  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
913
+ raise InternalLogicError, 'p_in cannot be active, by the value in the left child of q_in' if left_in
914
914
 
915
915
  if right_val.x < x0
916
916
  p = right(q_in)
@@ -970,7 +970,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
970
970
 
971
971
  while left || left_in || right_in || right
972
972
  # 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
973
+ raise InternalLogicError, 'It should not be that q_in is active but p_in is not' if right_in && !left_in
974
974
 
975
975
  set_i = []
976
976
  set_i << :left if left
@@ -988,7 +988,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
988
988
  when :right
989
989
  enumerate_right.call
990
990
  else
991
- raise LogicError, "bad symbol #{z}"
991
+ raise InternalLogicError, "bad symbol #{z}"
992
992
  end
993
993
  end
994
994
  return result unless block_given?
@@ -1134,7 +1134,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
1134
1134
  private def verify_properties
1135
1135
  # It's a max-heap in y
1136
1136
  (2..@size).each do |node|
1137
- raise LogicError, "Heap property violated at child #{node}" unless @data[node].y < @data[parent(node)].y
1137
+ raise InternalLogicError, "Heap property violated at child #{node}" unless @data[node].y < @data[parent(node)].y
1138
1138
  end
1139
1139
 
1140
1140
  # Left subtree has x values less than all of the right subtree
@@ -1144,7 +1144,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
1144
1144
  left_max = max_x_in_subtree(left(node))
1145
1145
  right_min = min_x_in_subtree(right(node))
1146
1146
 
1147
- raise LogicError, "Left-right property of x-values violated at #{node}" unless left_max < right_min
1147
+ raise InternalLogicError, "Left-right property of x-values violated at #{node}" unless left_max < right_min
1148
1148
  end
1149
1149
  end
1150
1150
 
@@ -2,15 +2,13 @@ require 'must_be'
2
2
 
3
3
  require_relative 'shared'
4
4
 
5
+ # THIS CLASS IS INCOMPLETE AND NOT USABLE
6
+ #
5
7
  # A priority search tree (PST) stores points in two dimensions (x,y) and can efficiently answer certain questions about the set of
6
8
  # point.
7
9
  #
8
10
  # The structure was introduced by McCreight [1].
9
11
  #
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
12
  # See more: https://en.wikipedia.org/wiki/Priority_search_tree
15
13
  #
16
14
  # 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
@@ -45,7 +43,7 @@ class DataStructuresRMolinari::MinmaxPrioritySearchTree
45
43
 
46
44
  # The array of pairs is turned into a minmax PST in-place without cloning. So clone before passing it in, if you care.
47
45
  #
48
- # Each element must respond to #x and #y. Use Pair (above) if you like.
46
+ # Each element must respond to #x and #y. Use Point (above) if you like.
49
47
  def initialize(data, verify: false)
50
48
  @data = data
51
49
  @size = @data.size
@@ -75,7 +73,7 @@ class DataStructuresRMolinari::MinmaxPrioritySearchTree
75
73
  #
76
74
  # Here T(x) is the subtree rooted at x
77
75
  def leftmost_ne(x0, y0)
78
- best = Pair.new(INFINITY, INFINITY)
76
+ best = Point.new(INFINITY, INFINITY)
79
77
  p = q = root
80
78
 
81
79
  in_q = ->(pair) { pair.x >= x0 && pair.y >= y0 }
@@ -284,7 +282,7 @@ class DataStructuresRMolinari::MinmaxPrioritySearchTree
284
282
  #
285
283
  # This method returns p*
286
284
  # def highest_3_sided_up(x0, x1, y0)
287
- # best = Pair.new(INFINITY, -INFINITY)
285
+ # best = Point.new(INFINITY, -INFINITY)
288
286
 
289
287
  # in_q = lambda do |pair|
290
288
  # pair.x >= x0 && pair.x <= x1 && pair.y >= y0
@@ -407,7 +405,7 @@ class DataStructuresRMolinari::MinmaxPrioritySearchTree
407
405
  # - If Q intersect P is empty then p* = best
408
406
  #
409
407
  # 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)
408
+ best = Point.new(INFINITY, -INFINITY)
411
409
  p = root # root of the whole tree AND the pair stored there
412
410
 
413
411
  in_q = lambda do |pair|
@@ -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,8 @@
1
1
  require_relative 'data_structures_rmolinari/shared'
2
2
 
3
3
  module DataStructuresRMolinari
4
- Pair = Shared::Pair
4
+ # A struct responding to +.x+ and +.y+.
5
+ Point = Shared::Point
5
6
  end
6
7
 
7
8
  # These define classes inside module DataStructuresRMolinari
@@ -11,13 +12,15 @@ require_relative 'data_structures_rmolinari/heap'
11
12
  require_relative 'data_structures_rmolinari/max_priority_search_tree'
12
13
  require_relative 'data_structures_rmolinari/minmax_priority_search_tree'
13
14
 
15
+ # A namespace to hold the provided classes. We want to avoid polluting the global namespace with names like "Heap"
14
16
  module DataStructuresRMolinari
15
17
  ########################################
16
18
  # Concrete instances of Segment Tree
17
19
  #
18
20
  # @todo consider moving these into generic_segment_tree.rb
19
21
 
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.
22
+ # 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)?"
23
+ # in O(log n) time.
21
24
  class MaxValSegmentTree
22
25
  extend Forwardable
23
26
 
@@ -25,28 +28,27 @@ module DataStructuresRMolinari
25
28
  def_delegator :@structure, :update_at
26
29
 
27
30
  # @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.
31
+ # - This will usually be an Array, but it could also be a hash or a proc.
29
32
  def initialize(data)
30
33
  @structure = GenericSegmentTree.new(
31
34
  combine: ->(a, b) { [a, b].max },
32
35
  single_cell_array_val: ->(i) { data[i] },
33
36
  size: data.size,
34
- identity: -Float::INFINITY
37
+ identity: -Shared::INFINITY
35
38
  )
36
39
  end
37
40
 
38
- # The maximum value in A(i..j)
41
+ # The maximum value in A(i..j).
39
42
  #
40
43
  # The arguments must be integers in 0...(A.size)
41
- # @return the largest value in A(i..j).
42
- # - Return +nil+ if i > j
44
+ # @return the largest value in A(i..j) or -Infinity if i > j.
43
45
  def max_on(i, j)
44
46
  @structure.query_on(i, j)
45
47
  end
46
48
  end
47
49
 
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.
50
+ # 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
51
+ # subinterval A(i..j)?" in O(log n) time.
50
52
  class IndexOfMaxValSegmentTree
51
53
  extend Forwardable
52
54
 
@@ -66,7 +68,7 @@ module DataStructuresRMolinari
66
68
  # The index of the maximum value in A(i..j)
67
69
  #
68
70
  # The arguments must be integers in 0...(A.size)
69
- # @return (Integer, nil) the index of the largest value in A(i..j).
71
+ # @return (Integer, nil) the index of the largest value in A(i..j) or +nil+ if i > j.
70
72
  # - If there is more than one entry with that value, return one the indices. There is no guarantee as to which one.
71
73
  # - Return +nil+ if i > j
72
74
  def index_of_max_val_on(i, j)
metadata CHANGED
@@ -1,7 +1,7 @@
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.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rory Molinari
@@ -80,6 +80,7 @@ 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