data_structures_rmolinari 0.1.0 → 0.2.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/lib/data_structures_rmolinari/generic_segment_tree_internal.rb +119 -0
- data/lib/data_structures_rmolinari/heap_internal.rb +179 -0
- data/lib/data_structures_rmolinari/max_priority_search_tree_internal.rb +47 -48
- data/lib/data_structures_rmolinari/minmax_priority_search_tree_internal.rb +8 -14
- data/lib/data_structures_rmolinari/shared.rb +58 -0
- data/lib/data_structures_rmolinari.rb +39 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea5f3d2fed60234fefe18577985f1e8ae967ecec92ef0812ccaeff0fb81ca392
|
4
|
+
data.tar.gz: e3e59746e8d7e881209a2c3a0e78ea186aa0562d138d7e2b6ed261943f6cb45f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e480ccee258f97479f1768b8f94b65a1c13f3b1e8f15e843c4240adeac555574383ef7de3928a057fd0b6707823cb4eeddee2128a49175c2f671e1526f887c2e
|
7
|
+
data.tar.gz: 30ac4bf22fe37bdfbf4eeccdcdfc2feba23278703957eebf5f9be6c30bb3e68024ccd49ef63c892d78c1fd58b54336a762c86cb107f9f27937222e9baf252ef4
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require_relative 'shared'
|
2
|
+
|
3
|
+
# A Segment Tree, which can be used for various interval-related purposes, like efficiently finding the sum (or min or max) on a
|
4
|
+
# arbitrary subarray of a given array.
|
5
|
+
#
|
6
|
+
# There is an excellent description of the data structure at https://cp-algorithms.com/data_structures/segment_tree.html. The
|
7
|
+
# Wikipedia article (https://en.wikipedia.org/wiki/Segment_tree) appears to describe a different data structure which is sometimes
|
8
|
+
# called an "interval tree."
|
9
|
+
#
|
10
|
+
# For more details (and some close-to-metal analysis of run time, especially for large datasets) see
|
11
|
+
# https://en.algorithmica.org/hpc/data-structures/segment-trees/. In particular, this shows how to do a bottom-up
|
12
|
+
# implementation, which is faster, at least for large datasets and cache-relevant compiled code.
|
13
|
+
#
|
14
|
+
# This is a generic implementation.
|
15
|
+
#
|
16
|
+
# We do O(n) work to build the internal data structure at initialization. Then we answer queries in O(log n) time.
|
17
|
+
#
|
18
|
+
# @todo
|
19
|
+
# - provide a data-update operation like update_val_at(idx, val)
|
20
|
+
# - this is O(log n)
|
21
|
+
# - note that this may need some rework. Consider something like IndexOfMaxVal: @merge needs to know about the underlying data
|
22
|
+
# in that case. Hmmm. Maybe the lambda can close over the data in a way that makes it possible to change the data "from the
|
23
|
+
# outside". Yes:
|
24
|
+
# a = [1,2,3]
|
25
|
+
# foo = ->() { a.max }
|
26
|
+
# foo.call # 3
|
27
|
+
# a = [1,2,4]
|
28
|
+
# foo.call # 4
|
29
|
+
# - Offer an optional parameter base_case_value_extractor (<-- need better name) to be used in #determine_val in the case that
|
30
|
+
# left == tree_l && right == tree_r instead of simply returning @tree[tree_idx]
|
31
|
+
# - Use case: https://cp-algorithms.com/data_structures/segment_tree.html#saving-the-entire-subarrays-in-each-vertex, such as
|
32
|
+
# finding the least element in a subarray l..r no smaller than a given value x. In this case we store a sorted version the
|
33
|
+
# entire subarray at each node and use a binary search on it.
|
34
|
+
# - the default value would simply be the identity function.
|
35
|
+
# - NOTE that in this case, we have different "combine" functions in #determine_val and #build. In #build we would combine
|
36
|
+
# sorted lists into a larger sorted list. In #determine_val we combine results via #min.
|
37
|
+
# - Think about the interface before doing this.
|
38
|
+
class GenericSegmentTreeInternal
|
39
|
+
include Shared::BinaryTreeArithmetic
|
40
|
+
|
41
|
+
# Construct a concrete instance of a Segment Tree. See details at the links above for the underlying concepts here.
|
42
|
+
# @param combine a lambda that takes two values and munges them into a combined value.
|
43
|
+
# - For example, if we are calculating sums over subintervals, combine.call(a, b) = a + b, while if we are doing maxima we will
|
44
|
+
# return max(a, b)
|
45
|
+
# @param single_cell_array_val a lambda that takes an index i and returns the value we need to store in the #build
|
46
|
+
# operation for the subinterval i..i. This is often simply be the value data[i], but in some cases - like "index of max val" -
|
47
|
+
# it will be something else.
|
48
|
+
# @param size the size of the underlying data array, used in certain internal arithmetic.
|
49
|
+
# @param identity is the value to return when we are querying on an empty interval
|
50
|
+
# - for sums, this will be zero; for maxima, this will be -Infinity, etc
|
51
|
+
def initialize(combine:, single_cell_array_val:, size:, identity:)
|
52
|
+
@combine = combine
|
53
|
+
@single_cell_array_val = single_cell_array_val
|
54
|
+
@size = size
|
55
|
+
@identity = identity
|
56
|
+
|
57
|
+
@tree = []
|
58
|
+
build(root, 0, @size - 1)
|
59
|
+
end
|
60
|
+
|
61
|
+
# The desired value (max, sum, etc.) on the subinterval left..right.
|
62
|
+
# @param left the left end of the subinterval.
|
63
|
+
# @param right the right end (inclusive) of the subinterval.
|
64
|
+
#
|
65
|
+
# The type of the return value depends on the concrete instance of the segment tree.
|
66
|
+
def query_on(left, right)
|
67
|
+
raise "Bad query interval #{left}..#{right}" if left.negative? || right >= @size
|
68
|
+
|
69
|
+
return @identity if left > right # empty interval
|
70
|
+
|
71
|
+
determine_val(root, left, right, 0, @size - 1)
|
72
|
+
end
|
73
|
+
|
74
|
+
private def determine_val(tree_idx, left, right, tree_l, tree_r)
|
75
|
+
# Does the current tree node exactly serve up the interval we're interested in?
|
76
|
+
return @tree[tree_idx] if left == tree_l && right == tree_r
|
77
|
+
|
78
|
+
# We need to go further down the tree
|
79
|
+
mid = midpoint(tree_l, tree_r)
|
80
|
+
if mid >= right
|
81
|
+
# Our interval is contained by the left child's interval
|
82
|
+
determine_val(left(tree_idx), left, right, tree_l, mid)
|
83
|
+
elsif mid + 1 <= left
|
84
|
+
# Our interval is contained by the right child's interval
|
85
|
+
determine_val(right(tree_idx), left, right, mid + 1, tree_r)
|
86
|
+
else
|
87
|
+
# Our interval is split between the two, so we need to combine the results from the children.
|
88
|
+
@combine.call(
|
89
|
+
determine_val(left(tree_idx), left, mid, tree_l, mid),
|
90
|
+
determine_val(right(tree_idx), mid + 1, right, mid + 1, tree_r)
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Build the internal data structure.
|
96
|
+
#
|
97
|
+
# - tree_idx is the index into @tree
|
98
|
+
# - tree_l..tree_r is the subinterval of the underlying data that node tree_idx corresponds to
|
99
|
+
private def build(tree_idx, tree_l, tree_r)
|
100
|
+
if tree_l == tree_r
|
101
|
+
@tree[tree_idx] = @single_cell_array_val.call(tree_l) # single-cell interval
|
102
|
+
else
|
103
|
+
# divide and conquer
|
104
|
+
mid = midpoint(tree_l, tree_r)
|
105
|
+
left = left(tree_idx)
|
106
|
+
right = right(tree_idx)
|
107
|
+
|
108
|
+
build(left, tree_l, mid)
|
109
|
+
build(right, mid + 1, tree_r)
|
110
|
+
|
111
|
+
@tree[tree_idx] = @combine.call(@tree[left], @tree[right])
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Do it in one place so we don't accidently round up here and down there, which would lead to chaos
|
116
|
+
private def midpoint(left, right)
|
117
|
+
(left + right) / 2
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require_relative 'shared'
|
2
|
+
|
3
|
+
# A heap is a balanced binary tree in which each entry has an associated priority. For each node p of the tree that isn't the root,
|
4
|
+
# the priority of the element at p is not less than the priority of the element at the parent of p.
|
5
|
+
#
|
6
|
+
# Thus the priority at each node p - root or not - is no greater than the priorities of the elements in the subtree rooted at p. It
|
7
|
+
# is a "min-heap".
|
8
|
+
#
|
9
|
+
# We can make it a max-heap, in which each node's priority is no greater than the priority of its parent, via a parameter to the
|
10
|
+
# initializer.
|
11
|
+
#
|
12
|
+
# We provide the following operations
|
13
|
+
# - +empty?+
|
14
|
+
# - is the heap empty?
|
15
|
+
# - O(1)
|
16
|
+
# - +insert+
|
17
|
+
# - add a new element to the heap with an associated priority
|
18
|
+
# - O(log N)
|
19
|
+
# - +top+
|
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
|
21
|
+
# element.
|
22
|
+
# - O(1)
|
23
|
+
# - +pop+
|
24
|
+
# - removes and returns the item that would be returned by +top+
|
25
|
+
# - O(log N)
|
26
|
+
# - +update+
|
27
|
+
# - tell the heap that the priority of a particular item has changed
|
28
|
+
# - O(log N)
|
29
|
+
#
|
30
|
+
# Here N is the number of elements in the heap.
|
31
|
+
#
|
32
|
+
# References:
|
33
|
+
#
|
34
|
+
# - https://en.wikipedia.org/wiki/Binary_heap
|
35
|
+
# - Edelkamp, S., Elmasry, A., Katajainen, J., _Optimizing Binary Heaps_, Theory Comput Syst (2017), vol 61, pp 606-636,
|
36
|
+
# DOI 10.1007/s00224-017-9760-2
|
37
|
+
#
|
38
|
+
# @todo
|
39
|
+
# - relax the requirement that priorities must be comparable vai +<+ and respond to negation. Instead, allow comparison via +<=>+
|
40
|
+
# and handle max-heaps differently.
|
41
|
+
# - this will allow priorities to be arrays for tie-breakers and similar.
|
42
|
+
class HeapInternal
|
43
|
+
include Shared::BinaryTreeArithmetic
|
44
|
+
|
45
|
+
attr_reader :size
|
46
|
+
|
47
|
+
Pair = Struct.new(:priority, :item)
|
48
|
+
|
49
|
+
# @param max_heap when truthy, make a max-heap rather than a min-heap
|
50
|
+
# @param debug when truthy, verify the heap property after each update than might violate it. This makes operations much slower.
|
51
|
+
def initialize(max_heap: false, debug: false)
|
52
|
+
@data = []
|
53
|
+
@size = 0
|
54
|
+
@max_heap = max_heap
|
55
|
+
@index_of = {}
|
56
|
+
@debug = debug
|
57
|
+
end
|
58
|
+
|
59
|
+
# Is the heap empty?
|
60
|
+
def empty?
|
61
|
+
@size.zero?
|
62
|
+
end
|
63
|
+
|
64
|
+
# Insert a new element into the heap with the given property.
|
65
|
+
# @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
|
66
|
+
# check for this.
|
67
|
+
# @param priority the priority to use for new item. The values used as priorities ust be totally ordered via +<+ and, if +self+ is
|
68
|
+
# a max-heap, must respond to negation +@-+ in the natural order-respecting way.
|
69
|
+
# @todo
|
70
|
+
# - check for duplicate
|
71
|
+
def insert(value, priority)
|
72
|
+
priority *= -1 if @max_heap
|
73
|
+
|
74
|
+
@size += 1
|
75
|
+
|
76
|
+
d = Pair.new(priority, value)
|
77
|
+
assign(d, @size)
|
78
|
+
|
79
|
+
sift_up(@size)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Return the top of the heap without removing it
|
83
|
+
# @return the value with minimal (maximal for max-heaps) priority. Strictly speaking, it returns the item at the root of the
|
84
|
+
# binary tree; this element has minimal priority, but there may be other elements with the same priority.
|
85
|
+
def top
|
86
|
+
raise 'Heap is empty!' unless @size.positive?
|
87
|
+
|
88
|
+
@data[root].item
|
89
|
+
end
|
90
|
+
|
91
|
+
# Return the top of the heap and remove it, updating the structure to maintain the necessary properties.
|
92
|
+
# @return (see #top)
|
93
|
+
def pop
|
94
|
+
result = top
|
95
|
+
@index_of.delete(result)
|
96
|
+
|
97
|
+
assign(@data[@size], root)
|
98
|
+
|
99
|
+
@data[@size] = nil
|
100
|
+
@size -= 1
|
101
|
+
|
102
|
+
sift_down(root) if @size.positive?
|
103
|
+
|
104
|
+
result
|
105
|
+
end
|
106
|
+
|
107
|
+
# Update the priority of the given element and maintain the necessary heap properties.
|
108
|
+
# @param element the item whose priority we are updating. It is an error to update the priority of an element not already in the
|
109
|
+
# heap
|
110
|
+
# @param priority the new priority
|
111
|
+
#
|
112
|
+
# @todo
|
113
|
+
# - check that the element is in the heap
|
114
|
+
def update(element, priority)
|
115
|
+
priority *= -1 if @max_heap
|
116
|
+
|
117
|
+
idx = @index_of[element]
|
118
|
+
old = @data[idx].priority
|
119
|
+
@data[idx].priority = priority
|
120
|
+
if priority > old
|
121
|
+
sift_down(idx)
|
122
|
+
elsif priority < old
|
123
|
+
sift_up(idx)
|
124
|
+
end
|
125
|
+
|
126
|
+
check_heap_property if @debug
|
127
|
+
end
|
128
|
+
|
129
|
+
# Filter the value at index up to its correct location. Algorithm from Edelkamp et. al.
|
130
|
+
private def sift_up(idx)
|
131
|
+
return if idx == root
|
132
|
+
|
133
|
+
x = @data[idx]
|
134
|
+
while idx != root
|
135
|
+
i = parent(idx)
|
136
|
+
break unless x.priority < @data[i].priority
|
137
|
+
|
138
|
+
assign(@data[i], idx)
|
139
|
+
idx = i
|
140
|
+
end
|
141
|
+
assign(x, idx)
|
142
|
+
|
143
|
+
check_heap_property if @debug
|
144
|
+
end
|
145
|
+
|
146
|
+
# Filter the value at index down to its correct location. Algorithm from Edelkamp et. al.
|
147
|
+
private def sift_down(idx)
|
148
|
+
x = @data[idx]
|
149
|
+
|
150
|
+
while (j = left(idx)) <= @size
|
151
|
+
j += 1 if j + 1 <= @size && @data[j + 1].priority < @data[j].priority
|
152
|
+
|
153
|
+
break unless @data[j].priority < x.priority
|
154
|
+
|
155
|
+
assign(@data[j], idx)
|
156
|
+
idx = j
|
157
|
+
end
|
158
|
+
assign(x, idx)
|
159
|
+
|
160
|
+
check_heap_property if @debug
|
161
|
+
end
|
162
|
+
|
163
|
+
# Put the pair in the given heap location
|
164
|
+
private def assign(pair, idx)
|
165
|
+
@data[idx] = pair
|
166
|
+
@index_of[pair.item] = idx
|
167
|
+
end
|
168
|
+
|
169
|
+
# For debugging
|
170
|
+
private def check_heap_property
|
171
|
+
(root..@size).each do |idx|
|
172
|
+
left = left(idx)
|
173
|
+
right = right(idx)
|
174
|
+
|
175
|
+
raise "Heap property violated by left child of index #{idx}" if left <= @size && @data[idx].priority >= @data[left].priority
|
176
|
+
raise "Heap property violated by right child of index #{idx}" if right <= @size && @data[idx].priority >= @data[right].priority
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
require 'set'
|
2
|
-
|
3
2
|
require_relative 'shared'
|
4
3
|
|
5
|
-
class LogicError < StandardError; end
|
6
4
|
|
7
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
|
8
6
|
# questions about P.
|
@@ -44,6 +42,7 @@ class LogicError < StandardError; end
|
|
44
42
|
# Geometry, 2011
|
45
43
|
class MaxPrioritySearchTreeInternal
|
46
44
|
include Shared
|
45
|
+
include BinaryTreeArithmetic
|
47
46
|
|
48
47
|
# Construct a MaxPST from the collection of points in +data+.
|
49
48
|
#
|
@@ -1047,52 +1046,52 @@ class MaxPrioritySearchTreeInternal
|
|
1047
1046
|
########################################
|
1048
1047
|
# Tree arithmetic
|
1049
1048
|
|
1050
|
-
# First element and root of the tree structure
|
1051
|
-
private def root
|
1052
|
-
|
1053
|
-
end
|
1054
|
-
|
1055
|
-
# Indexing is from 1
|
1056
|
-
private def parent(i)
|
1057
|
-
|
1058
|
-
end
|
1059
|
-
|
1060
|
-
private def left(i)
|
1061
|
-
|
1062
|
-
end
|
1063
|
-
|
1064
|
-
private def right(i)
|
1065
|
-
|
1066
|
-
end
|
1067
|
-
|
1068
|
-
private def level(i)
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
end
|
1076
|
-
|
1077
|
-
# i has no children
|
1078
|
-
private def leaf?(i)
|
1079
|
-
|
1080
|
-
end
|
1081
|
-
|
1082
|
-
# i has exactly one child (the left)
|
1083
|
-
private def one_child?(i)
|
1084
|
-
|
1085
|
-
end
|
1086
|
-
|
1087
|
-
# i has two children
|
1088
|
-
private def two_children?(i)
|
1089
|
-
|
1090
|
-
end
|
1091
|
-
|
1092
|
-
# i is the left child of its parent.
|
1093
|
-
private def left_child?(i)
|
1094
|
-
|
1095
|
-
end
|
1049
|
+
# # First element and root of the tree structure
|
1050
|
+
# private def root
|
1051
|
+
# 1
|
1052
|
+
# end
|
1053
|
+
|
1054
|
+
# # Indexing is from 1
|
1055
|
+
# private def parent(i)
|
1056
|
+
# i >> 1
|
1057
|
+
# end
|
1058
|
+
|
1059
|
+
# private def left(i)
|
1060
|
+
# i << 1
|
1061
|
+
# end
|
1062
|
+
|
1063
|
+
# private def right(i)
|
1064
|
+
# 1 + (i << 1)
|
1065
|
+
# end
|
1066
|
+
|
1067
|
+
# private def level(i)
|
1068
|
+
# l = 0
|
1069
|
+
# while i > root
|
1070
|
+
# i >>= 1
|
1071
|
+
# l += 1
|
1072
|
+
# end
|
1073
|
+
# l
|
1074
|
+
# end
|
1075
|
+
|
1076
|
+
# # i has no children
|
1077
|
+
# private def leaf?(i)
|
1078
|
+
# i > @last_non_leaf
|
1079
|
+
# end
|
1080
|
+
|
1081
|
+
# # i has exactly one child (the left)
|
1082
|
+
# private def one_child?(i)
|
1083
|
+
# i == @parent_of_one_child
|
1084
|
+
# end
|
1085
|
+
|
1086
|
+
# # i has two children
|
1087
|
+
# private def two_children?(i)
|
1088
|
+
# i <= @last_parent_of_two_children
|
1089
|
+
# end
|
1090
|
+
|
1091
|
+
# # i is the left child of its parent.
|
1092
|
+
# private def left_child?(i)
|
1093
|
+
# (i & 1).zero?
|
1094
|
+
# end
|
1096
1095
|
|
1097
1096
|
private def swap(index1, index2)
|
1098
1097
|
return if index1 == index2
|
@@ -1,3 +1,7 @@
|
|
1
|
+
require 'must_be'
|
2
|
+
|
3
|
+
require_relative 'shared'
|
4
|
+
|
1
5
|
# A priority search tree (PST) stores points in two dimensions (x,y) and can efficiently answer certain questions about the set of
|
2
6
|
# point.
|
3
7
|
#
|
@@ -26,28 +30,18 @@
|
|
26
30
|
# I started implementing the in-place PST. Then, finding the follow-up paper [3], decided to do that one instead, as the paper says
|
27
31
|
# it is more flexible. The point is to learn a new data structure and its associated algorithms.
|
28
32
|
#
|
29
|
-
#
|
33
|
+
# The algorithms are rather bewildering. Highest3SidedUp is complicated, and only two of the functions CheckLeft, CheckLeftIn,
|
30
34
|
# CheckRight, CheckRightIn are given; the other two are "symmetric". But it's not really clear what the first are actually doing, so
|
31
35
|
# it's hard to know what the others actually do.
|
32
36
|
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
|
37
|
+
# The implementation is incomplete. The pseduo-code in the paper is buggy (see the code below), which makes progress difficult.
|
38
|
+
#
|
36
39
|
# [1] E. McCreight, _Priority Search Trees_, SIAM J. Computing, v14, no 3, May 1985, pp 257-276.
|
37
40
|
# [2] De, Maheshwari, Nandy, Smid, _An in-place priority search tree_, 23rd Annual Canadian Conference on Computational Geometry.
|
38
41
|
# [3] De, Maheshwari, Nandy, Smid, _An in-place min-max priority search tree_, Computational Geometry, v46 (2013), pp 310-327.
|
39
42
|
# [4] Atkinson, Sack, Santoro, Strothotte, _Min-max heaps and generalized priority queues_, Commun. ACM 29 (10) (1986), pp 996-1000.
|
40
|
-
|
41
|
-
require 'must_be'
|
42
|
-
|
43
|
-
Pair = Struct.new(:x, :y) do
|
44
|
-
def fmt
|
45
|
-
"(#{x},#{y})"
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
43
|
class MinmaxPrioritySearchTreeInternal
|
50
|
-
|
44
|
+
include Shared
|
51
45
|
|
52
46
|
# The array of pairs is turned into a minmax PST in-place without cloning. So clone before passing it in, if you care.
|
53
47
|
#
|
@@ -3,4 +3,62 @@ module Shared
|
|
3
3
|
INFINITY = Float::INFINITY
|
4
4
|
|
5
5
|
Pair = Struct.new(:x, :y)
|
6
|
+
|
7
|
+
# @private
|
8
|
+
class LogicError < StandardError; end
|
9
|
+
|
10
|
+
# @private
|
11
|
+
#
|
12
|
+
# Provide simple arithmetic for an implied binary tree stored in an array, with the root at 1
|
13
|
+
module BinaryTreeArithmetic
|
14
|
+
# First element and root of the tree structure
|
15
|
+
private def root
|
16
|
+
1
|
17
|
+
end
|
18
|
+
|
19
|
+
# The parent of node i
|
20
|
+
private def parent(i)
|
21
|
+
i >> 1
|
22
|
+
end
|
23
|
+
|
24
|
+
# The left child of node i
|
25
|
+
private def left(i)
|
26
|
+
i << 1
|
27
|
+
end
|
28
|
+
|
29
|
+
# The right child of node i
|
30
|
+
private def right(i)
|
31
|
+
1 + (i << 1)
|
32
|
+
end
|
33
|
+
|
34
|
+
# The level in the tree of node i. The root is at level 0.
|
35
|
+
private def level(i)
|
36
|
+
l = 0
|
37
|
+
while i > root
|
38
|
+
i >>= 1
|
39
|
+
l += 1
|
40
|
+
end
|
41
|
+
l
|
42
|
+
end
|
43
|
+
|
44
|
+
# i has no children
|
45
|
+
private def leaf?(i)
|
46
|
+
i > @last_non_leaf
|
47
|
+
end
|
48
|
+
|
49
|
+
# i has exactly one child (the left)
|
50
|
+
private def one_child?(i)
|
51
|
+
i == @parent_of_one_child
|
52
|
+
end
|
53
|
+
|
54
|
+
# i has two children
|
55
|
+
private def two_children?(i)
|
56
|
+
i <= @last_parent_of_two_children
|
57
|
+
end
|
58
|
+
|
59
|
+
# i is the left child of its parent.
|
60
|
+
private def left_child?(i)
|
61
|
+
(i & 1).zero?
|
62
|
+
end
|
63
|
+
end
|
6
64
|
end
|
@@ -1,7 +1,46 @@
|
|
1
|
+
require_relative 'data_structures_rmolinari/shared'
|
2
|
+
require_relative 'data_structures_rmolinari/generic_segment_tree_internal'
|
3
|
+
require_relative 'data_structures_rmolinari/heap_internal'
|
1
4
|
require_relative 'data_structures_rmolinari/max_priority_search_tree_internal'
|
2
5
|
require_relative 'data_structures_rmolinari/minmax_priority_search_tree_internal'
|
3
6
|
|
4
7
|
module DataStructuresRMolinari
|
8
|
+
Pair = Shared::Pair
|
9
|
+
|
10
|
+
########################################
|
11
|
+
# Priority Search Trees
|
12
|
+
#
|
13
|
+
# Note that MinmaxPrioritySearchTree is only a fragment of what we need
|
5
14
|
MaxPrioritySearchTree = MaxPrioritySearchTreeInternal
|
6
15
|
MinmaxPrioritySearchTree = MinmaxPrioritySearchTreeInternal
|
16
|
+
|
17
|
+
########################################
|
18
|
+
# Segment Trees
|
19
|
+
|
20
|
+
GenericSegmentTree = GenericSegmentTreeInternal
|
21
|
+
|
22
|
+
# Takes an array A[0...n] and tells us what the maximum value is on a subinterval i..j in O(log n) time.
|
23
|
+
#
|
24
|
+
# TODO:
|
25
|
+
# - allow min val too
|
26
|
+
# - add a flag to the initializer
|
27
|
+
# - call it ExtremalValSegment tree or something similar
|
28
|
+
class MaxValSegmentTree
|
29
|
+
extend Forwardable
|
30
|
+
|
31
|
+
def_delegator :@structure, :query_on, :max_on
|
32
|
+
|
33
|
+
def initialize(data)
|
34
|
+
@structure = GenericSegmentTree.new(
|
35
|
+
combine: ->(a, b) { [a, b].max },
|
36
|
+
single_cell_array_val: ->(i) { data[i] },
|
37
|
+
size: data.size,
|
38
|
+
identity: -Float::INFINITY
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
########################################
|
44
|
+
# Heap
|
45
|
+
Heap = HeapInternal
|
7
46
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: data_structures_rmolinari
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rory Molinari
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-01-
|
11
|
+
date: 2023-01-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: must_be
|
@@ -78,6 +78,8 @@ extensions: []
|
|
78
78
|
extra_rdoc_files: []
|
79
79
|
files:
|
80
80
|
- lib/data_structures_rmolinari.rb
|
81
|
+
- lib/data_structures_rmolinari/generic_segment_tree_internal.rb
|
82
|
+
- lib/data_structures_rmolinari/heap_internal.rb
|
81
83
|
- lib/data_structures_rmolinari/max_priority_search_tree_internal.rb
|
82
84
|
- lib/data_structures_rmolinari/minmax_priority_search_tree_internal.rb
|
83
85
|
- lib/data_structures_rmolinari/shared.rb
|
@@ -91,7 +93,7 @@ require_paths:
|
|
91
93
|
- lib
|
92
94
|
required_ruby_version: !ruby/object:Gem::Requirement
|
93
95
|
requirements:
|
94
|
-
- -
|
96
|
+
- - "~>"
|
95
97
|
- !ruby/object:Gem::Version
|
96
98
|
version: 3.1.3
|
97
99
|
required_rubygems_version: !ruby/object:Gem::Requirement
|