data_structures_rmolinari 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/data_structures_rmolinari/disjoint_union_internal.rb +69 -0
- 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 +47 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8d63f6987f160f00ce92f79643f904f95bc230e4a5b60679f4301ecd366622a
|
4
|
+
data.tar.gz: cdc660ab53a0259a79b72ecc3928f640faa7b1251a1bae3baabb1c70bac20d01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 057d29af42606c7ecc4a78e6c752fa5f7a29b41788f21ca7ede6bab0893d1ba63b6c9dc3e5f3b5747f3912718565d6dfcb5952d189f78b40a6ef77efe95f34c9
|
7
|
+
data.tar.gz: '08f6921ab4b1c7cf84c9fe5624266b7060f4f1d4f1ecc56e3850106b3bb30e0c6b30c3ea808a61939966350118684f3353ccbb628d24b353b35ae865268222d2'
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# A "disjoint set union" that represents a set of elements that belonging to _disjoint_ subsets. Alternatively, this expresses a
|
2
|
+
# partion of a fixed set.
|
3
|
+
#
|
4
|
+
# The data structure provides efficient actions to merge two disjoint subsets, i.e., replace them by their union, and determine if
|
5
|
+
# two elements are in the same subset.
|
6
|
+
#
|
7
|
+
# The elements of the set must be 0, 1, ..., n-1. Client code can map its data to these representatives. The code uses several ideas
|
8
|
+
# from Tarjan and van Leeuwen for efficiency
|
9
|
+
#
|
10
|
+
# See https://en.wikipedia.org/wiki/Disjoint-set_data_structure for a good introduction.
|
11
|
+
#
|
12
|
+
# - Tarjan, Robert E., van Leeuwen, Jan (1984). "Worst-case analysis of set union algorithms". Journal of the ACM. 31 (2): 245–281.
|
13
|
+
class DisjointUnionInternal
|
14
|
+
attr_reader :subset_count
|
15
|
+
|
16
|
+
# @param size the size of the universe, which must be known at the time of construction. The elements 0, 1, ..., size - 1 start
|
17
|
+
# out in disjoint singleton subsets.
|
18
|
+
def initialize(size)
|
19
|
+
@size = size
|
20
|
+
# Initialize to
|
21
|
+
@d = (0...size).to_a
|
22
|
+
@rank = [0] * size
|
23
|
+
|
24
|
+
@subset_count = size
|
25
|
+
end
|
26
|
+
|
27
|
+
# Declare that e and f are equivalent, i.e., in the same subset. If they are already in the same subset this is a no-op.
|
28
|
+
#
|
29
|
+
# Each argument must be one of 0, 1, ..., size-1.
|
30
|
+
def unite(e, f)
|
31
|
+
check_value(e)
|
32
|
+
check_value(f)
|
33
|
+
raise 'Uniting an element with itself is meaningless' if e == f
|
34
|
+
|
35
|
+
e_root = find(e)
|
36
|
+
f_root = find(f)
|
37
|
+
return if e_root == f_root
|
38
|
+
|
39
|
+
@subset_count -= 1
|
40
|
+
link(e_root, f_root)
|
41
|
+
end
|
42
|
+
|
43
|
+
# The canonical representative of the subset containing e. Two elements d and e are in the same subset exactly when find(d) ==
|
44
|
+
# find(e).
|
45
|
+
# @param e must be one of 0, 1, ..., size-1.
|
46
|
+
# @return (Integer) one of 0, 1, ..., size-1.
|
47
|
+
def find(e)
|
48
|
+
# We implement find with "halving" to shrink the length of paths to the root. See Tarjan and van Leeuwin p 252.
|
49
|
+
x = e
|
50
|
+
x = @d[x] = @d[@d[x]] while @d[@d[x]] != @d[x]
|
51
|
+
@d[x]
|
52
|
+
end
|
53
|
+
|
54
|
+
private def check_value(v)
|
55
|
+
raise "Value must be given and be in (0..#{@size - 1})" unless v && v.between?(0, @size - 1)
|
56
|
+
end
|
57
|
+
|
58
|
+
private def link(e, f)
|
59
|
+
# Choose which way around to do the linking using the element "ranks". See Tarjan and van Leeuwen, p 250.
|
60
|
+
if @rank[e] > @rank[f]
|
61
|
+
@d[f] = e
|
62
|
+
elsif @rank[e] == @rank[f]
|
63
|
+
@d[f] = e
|
64
|
+
@rank[e] += 1
|
65
|
+
else
|
66
|
+
@d[e] = f
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -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,54 @@
|
|
1
|
+
require_relative 'data_structures_rmolinari/shared'
|
2
|
+
require_relative 'data_structures_rmolinari/disjoint_union_internal'
|
3
|
+
require_relative 'data_structures_rmolinari/generic_segment_tree_internal'
|
4
|
+
require_relative 'data_structures_rmolinari/heap_internal'
|
1
5
|
require_relative 'data_structures_rmolinari/max_priority_search_tree_internal'
|
2
6
|
require_relative 'data_structures_rmolinari/minmax_priority_search_tree_internal'
|
3
7
|
|
4
8
|
module DataStructuresRMolinari
|
9
|
+
Pair = Shared::Pair
|
10
|
+
|
11
|
+
########################################
|
12
|
+
# Priority Search Trees
|
13
|
+
#
|
14
|
+
# Note that MinmaxPrioritySearchTree is only a fragment of what we need
|
15
|
+
|
5
16
|
MaxPrioritySearchTree = MaxPrioritySearchTreeInternal
|
6
17
|
MinmaxPrioritySearchTree = MinmaxPrioritySearchTreeInternal
|
18
|
+
|
19
|
+
########################################
|
20
|
+
# Segment Trees
|
21
|
+
|
22
|
+
GenericSegmentTree = GenericSegmentTreeInternal
|
23
|
+
|
24
|
+
# Takes an array A[0...n] and tells us what the maximum value is on a subinterval i..j in O(log n) time.
|
25
|
+
#
|
26
|
+
# TODO:
|
27
|
+
# - allow min val too
|
28
|
+
# - add a flag to the initializer
|
29
|
+
# - call it ExtremalValSegment tree or something similar
|
30
|
+
class MaxValSegmentTree
|
31
|
+
extend Forwardable
|
32
|
+
|
33
|
+
def_delegator :@structure, :query_on, :max_on
|
34
|
+
|
35
|
+
def initialize(data)
|
36
|
+
@structure = GenericSegmentTree.new(
|
37
|
+
combine: ->(a, b) { [a, b].max },
|
38
|
+
single_cell_array_val: ->(i) { data[i] },
|
39
|
+
size: data.size,
|
40
|
+
identity: -Float::INFINITY
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
########################################
|
46
|
+
# Heap
|
47
|
+
|
48
|
+
Heap = HeapInternal
|
49
|
+
|
50
|
+
########################################
|
51
|
+
# Disjoint Union
|
52
|
+
|
53
|
+
DisjointUnion = DisjointUnionInternal
|
7
54
|
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.1
|
4
|
+
version: 0.2.1
|
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
|
@@ -72,12 +72,17 @@ description: |
|
|
72
72
|
Sometimes it is not enough to read the description of a data structure and accompanying pseudo-code.
|
73
73
|
Actually implementing the structure is often helpful in understanding what is going on. It is also
|
74
74
|
usually fun.
|
75
|
+
|
76
|
+
We implement Disjoin Union, Heap, Priority Search Tree, and Segment Tree.
|
75
77
|
email: rorymolinari+rubygems@gmail.com
|
76
78
|
executables: []
|
77
79
|
extensions: []
|
78
80
|
extra_rdoc_files: []
|
79
81
|
files:
|
80
82
|
- lib/data_structures_rmolinari.rb
|
83
|
+
- lib/data_structures_rmolinari/disjoint_union_internal.rb
|
84
|
+
- lib/data_structures_rmolinari/generic_segment_tree_internal.rb
|
85
|
+
- lib/data_structures_rmolinari/heap_internal.rb
|
81
86
|
- lib/data_structures_rmolinari/max_priority_search_tree_internal.rb
|
82
87
|
- lib/data_structures_rmolinari/minmax_priority_search_tree_internal.rb
|
83
88
|
- lib/data_structures_rmolinari/shared.rb
|
@@ -91,7 +96,7 @@ require_paths:
|
|
91
96
|
- lib
|
92
97
|
required_ruby_version: !ruby/object:Gem::Requirement
|
93
98
|
requirements:
|
94
|
-
- -
|
99
|
+
- - "~>"
|
95
100
|
- !ruby/object:Gem::Version
|
96
101
|
version: 3.1.3
|
97
102
|
required_rubygems_version: !ruby/object:Gem::Requirement
|