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 +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
|