data_structures_rmolinari 0.2.2 → 0.3.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 +18 -0
- data/lib/data_structures_rmolinari/disjoint_union.rb +2 -2
- data/lib/data_structures_rmolinari/generic_segment_tree.rb +5 -5
- data/lib/data_structures_rmolinari/heap.rb +64 -42
- data/lib/data_structures_rmolinari/max_priority_search_tree.rb +23 -23
- data/lib/data_structures_rmolinari/minmax_priority_search_tree.rb +6 -8
- data/lib/data_structures_rmolinari/shared.rb +9 -1
- data/lib/data_structures_rmolinari.rb +12 -10
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f006234ee3b216d5607e9b10bb1958a6107ccfa0cc8c359f98383dc7fde14ee
|
4
|
+
data.tar.gz: f281ab0768e24e7c983cd046ba7b185dab8fd972fb3065fd73ff575782bf5486
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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
|
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
|
34
|
-
# calculating the index of the maximal value on each subinterval we
|
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
|
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
|
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
|
-
# -
|
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
|
-
|
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
|
59
|
-
|
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
|
-
@
|
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.
|
74
|
-
#
|
75
|
-
# @param priority the priority to use for new item. The values used as priorities
|
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
|
-
|
83
|
+
raise DataError, "Heap already contains #{value}" if @addressable && contains?(value)
|
81
84
|
|
82
85
|
@size += 1
|
83
86
|
|
84
|
-
d =
|
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
|
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
|
-
|
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
|
130
|
+
if less_than_priority?(old, priority)
|
129
131
|
sift_down(idx)
|
130
|
-
elsif priority
|
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
|
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]
|
161
|
+
j += 1 if j + 1 <= @size && less_than?(@data[j + 1], @data[j])
|
160
162
|
|
161
|
-
break unless @data[j]
|
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[
|
184
|
-
raise "Heap property violated by right child of index #{idx}" if right <= @size && @data[
|
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.
|
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 =
|
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 }
|
@@ -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
|
@@ -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)
|
@@ -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
|
@@ -786,7 +786,7 @@ class DataStructuresRMolinari::MaxPrioritySearchTree
|
|
786
786
|
p_in = right(p_in)
|
787
787
|
left = true
|
788
788
|
else
|
789
|
-
raise
|
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
|
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
|
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
|
850
|
-
raise
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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 =
|
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 =
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
# -
|
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: -
|
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)
|
49
|
-
#
|
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.
|
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
|