data_structures_rmolinari 0.2.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea5f3d2fed60234fefe18577985f1e8ae967ecec92ef0812ccaeff0fb81ca392
4
- data.tar.gz: e3e59746e8d7e881209a2c3a0e78ea186aa0562d138d7e2b6ed261943f6cb45f
3
+ metadata.gz: 3765b8df91fcc62eb885e32ff5ad4b0b4678bba6f322cb5c8282657052aed8c6
4
+ data.tar.gz: 845bea3649dc51dab697927132c0fc2f62dcacf5c25e1c717b99e57819b52286
5
5
  SHA512:
6
- metadata.gz: e480ccee258f97479f1768b8f94b65a1c13f3b1e8f15e843c4240adeac555574383ef7de3928a057fd0b6707823cb4eeddee2128a49175c2f671e1526f887c2e
7
- data.tar.gz: 30ac4bf22fe37bdfbf4eeccdcdfc2feba23278703957eebf5f9be6c30bb3e68024ccd49ef63c892d78c1fd58b54336a762c86cb107f9f27937222e9baf252ef4
6
+ metadata.gz: 23687561ec6ddb12369ca5e75db33ffd710295097cd0c91b72fb278fea3b11b23152867bb4aea6a4fd17b2f95184fb5433c9ff009db92a5c4bab78686ae472de
7
+ data.tar.gz: d930a674f85aa0a57030ed59f2b39979c7dbb8d8f74e0a9718d44112c1efe05657e0d55773b623c3724f040054e2fedf453de30b72d5631404642081455406f9
@@ -0,0 +1,77 @@
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, where n is the size of the universe. Client code can map its data to these
8
+ # representatives.
9
+ #
10
+ # See https://en.wikipedia.org/wiki/Disjoint-set_data_structure for a good introduction.
11
+ #
12
+ # The code uses several ideas from Tarjan and van Leeuwen for efficiency. We use "union by rank" in +unite+ and path-halving in
13
+ # +find+. Together, these make the amortized cost for each of n such operations effectively constant.
14
+ #
15
+ # - Tarjan, Robert E., van Leeuwen, Jan (1984). "Worst-case analysis of set union algorithms". Journal of the ACM. 31 (2): 245–281.
16
+ #
17
+ # @todo
18
+ # - allow caller to expand the size of the universe. This operation is called "make set".
19
+ # - All we need to do is increase the size of @d, set the parent pointers, define the new ranks (zero), and update @size.
20
+ class DataStructuresRMolinari::DisjointUnion
21
+ # The number of subsets in the partition.
22
+ attr_reader :subset_count
23
+
24
+ # @param size the size of the universe, which must be known at the time of construction. The elements 0, 1, ..., size - 1 start
25
+ # out in disjoint singleton subsets.
26
+ def initialize(size)
27
+ @size = size
28
+ # Initialize to
29
+ @d = (0...size).to_a
30
+ @rank = [0] * size
31
+
32
+ @subset_count = size
33
+ end
34
+
35
+ # 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.
36
+ #
37
+ # Each argument must be one of 0, 1, ..., size-1.
38
+ def unite(e, f)
39
+ check_value(e)
40
+ check_value(f)
41
+ raise 'Uniting an element with itself is meaningless' if e == f
42
+
43
+ e_root = find(e)
44
+ f_root = find(f)
45
+ return if e_root == f_root
46
+
47
+ @subset_count -= 1
48
+ link(e_root, f_root)
49
+ end
50
+
51
+ # The canonical representative of the subset containing e. Two elements d and e are in the same subset exactly when find(d) ==
52
+ # find(e).
53
+ # @param e must be one of 0, 1, ..., size-1.
54
+ # @return (Integer) one of 0, 1, ..., size-1.
55
+ def find(e)
56
+ # We implement find with "halving" to shrink the length of paths to the root. See Tarjan and van Leeuwin p 252.
57
+ x = e
58
+ x = @d[x] = @d[@d[x]] while @d[@d[x]] != @d[x]
59
+ @d[x]
60
+ end
61
+
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)
64
+ end
65
+
66
+ private def link(e, f)
67
+ # Choose which way around to do the linking using the element "ranks". See Tarjan and van Leeuwen, p 250.
68
+ if @rank[e] > @rank[f]
69
+ @d[f] = e
70
+ elsif @rank[e] == @rank[f]
71
+ @d[f] = e
72
+ @rank[e] += 1
73
+ else
74
+ @d[e] = f
75
+ end
76
+ end
77
+ end
@@ -8,45 +8,33 @@ require_relative 'shared'
8
8
  # called an "interval tree."
9
9
  #
10
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.
11
+ # https://en.algorithmica.org/hpc/data-structures/segment-trees/. In particular, this shows how to do a bottom-up implementation,
12
+ # which is faster, at least for large datasets and cache-relevant compiled code. These issues don't really apply to code written in
13
+ # Ruby.
13
14
  #
14
- # This is a generic implementation.
15
+ # This is a generic implementation, intended to allow easy configuration for concrete instances. See the parameters to the
16
+ # initializer and the defintiaons concrete realisations like MaxValSegmentTree.
15
17
  #
16
18
  # 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
19
+ class DataStructuresRMolinari::GenericSegmentTree
39
20
  include Shared::BinaryTreeArithmetic
40
21
 
41
22
  # Construct a concrete instance of a Segment Tree. See details at the links above for the underlying concepts here.
42
23
  # @param combine a lambda that takes two values and munges them into a combined value.
43
24
  # - 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)
25
+ # return max(a, b).
26
+ # - Things get more complicated when we are calculating, say, the _index_ of the maximal value in a subinterval. Now it is not
27
+ # enough simple to store that index at each tree node, because to combine the indices from two child nodes we need to know
28
+ # both the index of the maximal element in each child node's interval, but also the maximal values themselves, so we know
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
+ # the +single_cell_array_val+ lambda.
45
31
  # @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.
32
+ # operation for the subinterval i..i.
33
+ # - This is often simply be the value data[i], but in some cases it will be something else. For example, when we are
34
+ # calculating the index of the maximal value on each subinterval we will retern the pair [i, data[i]] here.
35
+ # - If +update_at+ is called later, this lambda must close over the underlying data in a way that captures the updated value.
48
36
  # @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
37
+ # @param identity the value to return when we are querying on an empty interval
50
38
  # - for sums, this will be zero; for maxima, this will be -Infinity, etc
51
39
  def initialize(combine:, single_cell_array_val:, size:, identity:)
52
40
  @combine = combine
@@ -62,15 +50,28 @@ class GenericSegmentTreeInternal
62
50
  # @param left the left end of the subinterval.
63
51
  # @param right the right end (inclusive) of the subinterval.
64
52
  #
65
- # The type of the return value depends on the concrete instance of the segment tree.
53
+ # The type of the return value depends on the concrete instance of the segment tree. We return the _identity_ element provided at
54
+ # construction time if the interval is empty.
66
55
  def query_on(left, right)
67
- raise "Bad query interval #{left}..#{right}" if left.negative? || right >= @size
56
+ raise DataError, "Bad query interval #{left}..#{right}" if left.negative? || right >= @size
68
57
 
69
58
  return @identity if left > right # empty interval
70
59
 
71
60
  determine_val(root, left, right, 0, @size - 1)
72
61
  end
73
62
 
63
+ # Update the value in the underlying array at the given idx
64
+ #
65
+ # @param idx an index in the underlying data array.
66
+ #
67
+ # Note that we don't need the updated value itself. We get that by calling the lambda +single_cell_array_val+ supplied at
68
+ # construction.
69
+ def update_at(idx)
70
+ raise DataError, 'Cannot update an index outside the initial range of the underlying data' unless (0...@size).cover?(idx)
71
+
72
+ update_val_at(idx, root, 0, @size - 1)
73
+ end
74
+
74
75
  private def determine_val(tree_idx, left, right, tree_l, tree_r)
75
76
  # Does the current tree node exactly serve up the interval we're interested in?
76
77
  return @tree[tree_idx] if left == tree_l && right == tree_r
@@ -92,6 +93,26 @@ class GenericSegmentTreeInternal
92
93
  end
93
94
  end
94
95
 
96
+ private def update_val_at(idx, tree_idx, tree_l, tree_r)
97
+ if tree_l == tree_r
98
+ # We have found the spot!
99
+ raise LogicError, 'tree_l == tree_r, but they do not agree with the idx holding the updated value' unless tree_l == idx
100
+
101
+ @tree[tree_idx] = @single_cell_array_val.call(tree_l)
102
+ else
103
+ # Recursively update the appropriate subtree
104
+ mid = midpoint(tree_l, tree_r)
105
+ left = left(tree_idx)
106
+ right = right(tree_idx)
107
+ if mid >= idx
108
+ update_val_at(idx, left(tree_idx), tree_l, mid)
109
+ else
110
+ update_val_at(idx, right(tree_idx), mid + 1, tree_r)
111
+ end
112
+ @tree[tree_idx] = @combine.call(@tree[left], @tree[right])
113
+ end
114
+ end
115
+
95
116
  # Build the internal data structure.
96
117
  #
97
118
  # - tree_idx is the index into @tree
@@ -36,10 +36,18 @@ require_relative 'shared'
36
36
  # DOI 10.1007/s00224-017-9760-2
37
37
  #
38
38
  # @todo
39
+ # - allow for priorities comparable only via +<=>+, like arrays
40
+ # - this requires different handling for max-heaps, as we can't just negate the priorities and use min-heap logic
39
41
  # - relax the requirement that priorities must be comparable vai +<+ and respond to negation. Instead, allow comparison via +<=>+
40
42
  # and handle max-heaps differently.
41
43
  # - this will allow priorities to be arrays for tie-breakers and similar.
42
- class HeapInternal
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.
50
+ class DataStructuresRMolinari::Heap
43
51
  include Shared::BinaryTreeArithmetic
44
52
 
45
53
  attr_reader :size
@@ -61,7 +69,7 @@ class HeapInternal
61
69
  @size.zero?
62
70
  end
63
71
 
64
- # Insert a new element into the heap with the given property.
72
+ # Insert a new element into the heap with the given priority.
65
73
  # @param value the item to be inserted. It is an error to insert an item that is already present in the heap, though we don't
66
74
  # check for this.
67
75
  # @param priority the priority to use for new item. The values used as priorities ust be totally ordered via +<+ and, if +self+ is
@@ -40,14 +40,18 @@ require_relative 'shared'
40
40
  # * E.M. McCreight, _Priority search trees_, SIAM J. Comput., 14(2):257-276, 1985. Later, De,
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
- class MaxPrioritySearchTreeInternal
43
+ class DataStructuresRMolinari::MaxPrioritySearchTree
44
44
  include Shared
45
45
  include BinaryTreeArithmetic
46
46
 
47
47
  # Construct a MaxPST from the collection of points in +data+.
48
48
  #
49
- # @param data [Array] the set P of points presented as an array. The tree is built in the array in-place without cloning. Each
50
- # element of the array must respond to +#x+ and +#y+ (though this is not currently checked).
49
+ # @param data [Array] the set P of points presented as an array. The tree is built in the array in-place without cloning.
50
+ # - Each element of the array must respond to +#x+ and +#y+.
51
+ # - This is not checked explicitly but a missing method exception will be thrown when we try to call one of them.
52
+ # - The +x+ values must be distinct, as must the +y+ values. We raise a +Shared::DataError+ if this isn't the case.
53
+ # - This is a restriction that simplifies some of the algorithm code. It can be removed as the cost of some extra work. Issue
54
+ # #9.
51
55
  #
52
56
  # @param verify [Boolean] when truthy, check that the properties of a PST are satisified after construction, raising an exception
53
57
  # if not.
@@ -69,7 +73,7 @@ class MaxPrioritySearchTreeInternal
69
73
  # Let Q = [x0, infty) X [y0, infty) be the northeast quadrant defined by the point (x0, y0) and let P be the points in this data
70
74
  # structure. Define p* as
71
75
  #
72
- # - (infty, -infty) f Q \intersect P is empty and
76
+ # - (infty, -infty) if Q \intersect P is empty and
73
77
  # - the highest (max-x) point in Q \intersect P otherwise.
74
78
  #
75
79
  # This method returns p* in O(log n) time and O(1) extra space.
@@ -82,7 +86,7 @@ class MaxPrioritySearchTreeInternal
82
86
  # Let Q = (-infty, x0] X [y0, infty) be the northwest quadrant defined by the point (x0, y0) and let P be the points in this data
83
87
  # structure. Define p* as
84
88
  #
85
- # - (-infty, -infty) f Q \intersect P is empty and
89
+ # - (-infty, -infty) if Q \intersect P is empty and
86
90
  # - the highest (max-y) point in Q \intersect P otherwise.
87
91
  #
88
92
  # This method returns p* in O(log n) time and O(1) extra space.
@@ -186,7 +190,7 @@ class MaxPrioritySearchTreeInternal
186
190
  # Let Q = [x0, infty) X [y0, infty) be the northeast quadrant defined by the point (x0, y0) and let P be the points in this data
187
191
  # structure. Define p* as
188
192
  #
189
- # - (infty, infty) f Q \intersect P is empty and
193
+ # - (infty, infty) if Q \intersect P is empty and
190
194
  # - the leftmost (min-x) point in Q \intersect P otherwise.
191
195
  #
192
196
  # This method returns p* in O(log n) time and O(1) extra space.
@@ -994,9 +998,14 @@ class MaxPrioritySearchTreeInternal
994
998
  # Build the initial stucture
995
999
 
996
1000
  private def construct_pst
997
- # We follow the algorithm in the paper by De, Maheshwari et al. Note that indexing is from 1 there. For now we pretend that that
998
- # is the case here, too.
1001
+ raise DataError, 'Duplicate x values are not supported' if contains_duplicates?(@data, by: :x)
1002
+ raise DataError, 'Duplicate y values are not supported' if contains_duplicates?(@data, by: :y)
1003
+
1004
+ # We follow the algorithm in the paper by De, Maheshwari et al.
999
1005
 
1006
+ # Since we are building an implicit binary tree, things are simpler if the array is 1-based. This probably requires a malloc and
1007
+ # data copy, which isn't great, but it's in the C layer so cheap compared to the O(n log^2 n) work we need to do for
1008
+ # construction. In fact, we are probably doing O(n^2) work because of all the calls to #index_with_largest_y_in.
1000
1009
  @data.unshift nil
1001
1010
 
1002
1011
  h = Math.log2(@size).floor
@@ -1106,13 +1115,11 @@ class MaxPrioritySearchTreeInternal
1106
1115
  (l..r).max_by { |idx| @data[idx].y }
1107
1116
  end
1108
1117
 
1109
- # Sort the subarray @data[l..r]. This is much faster than a Ruby-layer heapsort because it is mostly happening in C.
1118
+ # Sort the subarray @data[l..r].
1110
1119
  private def sort_subarray(l, r)
1111
- # heapsort_subarray(l, r)
1112
1120
  return if l == r # 1-array already sorted!
1113
1121
 
1114
- #l -= 1
1115
- #r -= 1
1122
+ # This slice-replacement is much faster than a Ruby-layer heapsort because it is mostly happening in C.
1116
1123
  @data[l..r] = @data[l..r].sort_by(&:x)
1117
1124
  end
1118
1125
 
@@ -40,7 +40,7 @@ require_relative 'shared'
40
40
  # [2] De, Maheshwari, Nandy, Smid, _An in-place priority search tree_, 23rd Annual Canadian Conference on Computational Geometry.
41
41
  # [3] De, Maheshwari, Nandy, Smid, _An in-place min-max priority search tree_, Computational Geometry, v46 (2013), pp 310-327.
42
42
  # [4] Atkinson, Sack, Santoro, Strothotte, _Min-max heaps and generalized priority queues_, Commun. ACM 29 (10) (1986), pp 996-1000.
43
- class MinmaxPrioritySearchTreeInternal
43
+ class DataStructuresRMolinari::MinmaxPrioritySearchTree
44
44
  include Shared
45
45
 
46
46
  # The array of pairs is turned into a minmax PST in-place without cloning. So clone before passing it in, if you care.
@@ -6,6 +6,7 @@ module Shared
6
6
 
7
7
  # @private
8
8
  class LogicError < StandardError; end
9
+ class DataError < StandardError; end
9
10
 
10
11
  # @private
11
12
  #
@@ -61,4 +62,22 @@ module Shared
61
62
  (i & 1).zero?
62
63
  end
63
64
  end
65
+
66
+ # Simple O(n) check for duplicates in an enumerable.
67
+ #
68
+ # It may be worse than O(n), depending on how close to constant set insertion is.
69
+ #
70
+ # @param enum the enumerable to check for duplicates
71
+ # @param by a method to call on each element of enum before checking. The results of these methods are checked for
72
+ # duplication. When nil we don't call anything and just use the elements themselves.
73
+ def contains_duplicates?(enum, by: nil)
74
+ seen = Set.new
75
+ enum.each do |v|
76
+ v = v.send(by) if by
77
+ return true if seen.include? v
78
+
79
+ seen << v
80
+ end
81
+ false
82
+ end
64
83
  end
@@ -1,35 +1,31 @@
1
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'
4
- require_relative 'data_structures_rmolinari/max_priority_search_tree_internal'
5
- require_relative 'data_structures_rmolinari/minmax_priority_search_tree_internal'
6
2
 
7
3
  module DataStructuresRMolinari
8
4
  Pair = Shared::Pair
5
+ end
9
6
 
10
- ########################################
11
- # Priority Search Trees
12
- #
13
- # Note that MinmaxPrioritySearchTree is only a fragment of what we need
14
- MaxPrioritySearchTree = MaxPrioritySearchTreeInternal
15
- MinmaxPrioritySearchTree = MinmaxPrioritySearchTreeInternal
7
+ # These define classes inside module DataStructuresRMolinari
8
+ require_relative 'data_structures_rmolinari/disjoint_union'
9
+ require_relative 'data_structures_rmolinari/generic_segment_tree'
10
+ require_relative 'data_structures_rmolinari/heap'
11
+ require_relative 'data_structures_rmolinari/max_priority_search_tree'
12
+ require_relative 'data_structures_rmolinari/minmax_priority_search_tree'
16
13
 
14
+ module DataStructuresRMolinari
17
15
  ########################################
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.
16
+ # Concrete instances of Segment Tree
23
17
  #
24
- # TODO:
25
- # - allow min val too
26
- # - add a flag to the initializer
27
- # - call it ExtremalValSegment tree or something similar
18
+ # @todo consider moving these into generic_segment_tree.rb
19
+
20
+ # Takes an array A(0...n) and tells us what the maximum value is on a subinterval A(i..j) in O(log n) time.
28
21
  class MaxValSegmentTree
29
22
  extend Forwardable
30
23
 
31
- def_delegator :@structure, :query_on, :max_on
24
+ # Tell the tree that the value at idx has changed
25
+ def_delegator :@structure, :update_at
32
26
 
27
+ # @param data an object that contains values at integer indices based at 0, via +data[i]+.
28
+ # - The usual use case will be an Array, but it could also be a hash or a proc of some sort.
33
29
  def initialize(data)
34
30
  @structure = GenericSegmentTree.new(
35
31
  combine: ->(a, b) { [a, b].max },
@@ -38,9 +34,43 @@ module DataStructuresRMolinari
38
34
  identity: -Float::INFINITY
39
35
  )
40
36
  end
37
+
38
+ # The maximum value in A(i..j)
39
+ #
40
+ # The arguments must be integers in 0...(A.size)
41
+ # @return the largest value in A(i..j).
42
+ # - Return +nil+ if i > j
43
+ def max_on(i, j)
44
+ @structure.query_on(i, j)
45
+ end
41
46
  end
42
47
 
43
- ########################################
44
- # Heap
45
- Heap = HeapInternal
48
+ # A segment tree that for an array A(0...n) efficiently answers questions of the form "what is the index of the maximal value in
49
+ # a subinterval A(i..j) in O(log n) time.
50
+ class IndexOfMaxValSegmentTree
51
+ extend Forwardable
52
+
53
+ # Tell the tree that the value at idx has changed
54
+ def_delegator :@structure, :update_at
55
+
56
+ # @param (see MaxValSegmentTree#initialize)
57
+ def initialize(data)
58
+ @structure = GenericSegmentTree.new(
59
+ combine: ->(p1, p2) { p1[1] >= p2[1] ? p1 : p2 },
60
+ single_cell_array_val: ->(i) { [i, data[i]] },
61
+ size: data.size,
62
+ identity: nil
63
+ )
64
+ end
65
+
66
+ # The index of the maximum value in A(i..j)
67
+ #
68
+ # The arguments must be integers in 0...(A.size)
69
+ # @return (Integer, nil) the index of the largest value in A(i..j).
70
+ # - If there is more than one entry with that value, return one the indices. There is no guarantee as to which one.
71
+ # - Return +nil+ if i > j
72
+ def index_of_max_val_on(i, j)
73
+ @structure.query_on(i, j)&.first # discard the value part of the pair
74
+ end
75
+ end
46
76
  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.2.0
4
+ version: 0.2.2
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-05 00:00:00.000000000 Z
11
+ date: 2023-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: must_be
@@ -67,21 +67,25 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.22.0
69
69
  description: |
70
- This small gem contains several data structures that I have implemented to learn how they work.
70
+ This small gem contains several data structures that I have implemented in Ruby to learn how they work.
71
71
 
72
72
  Sometimes it is not enough to read the description of a data structure and accompanying pseudo-code.
73
- Actually implementing the structure is often helpful in understanding what is going on. It is also
73
+ Actually implementing it is often helpful in understanding what is going on. It is also
74
74
  usually fun.
75
- email: rorymolinari+rubygems@gmail.com
75
+
76
+ The gem contains basic implementions of Disjoint Union, Heap, Priority Search Tree, and Segment Tree.
77
+ See the homepage for more details.
78
+ email: rorymolinari@gmail.com
76
79
  executables: []
77
80
  extensions: []
78
81
  extra_rdoc_files: []
79
82
  files:
80
83
  - lib/data_structures_rmolinari.rb
81
- - lib/data_structures_rmolinari/generic_segment_tree_internal.rb
82
- - lib/data_structures_rmolinari/heap_internal.rb
83
- - lib/data_structures_rmolinari/max_priority_search_tree_internal.rb
84
- - lib/data_structures_rmolinari/minmax_priority_search_tree_internal.rb
84
+ - lib/data_structures_rmolinari/disjoint_union.rb
85
+ - lib/data_structures_rmolinari/generic_segment_tree.rb
86
+ - lib/data_structures_rmolinari/heap.rb
87
+ - lib/data_structures_rmolinari/max_priority_search_tree.rb
88
+ - lib/data_structures_rmolinari/minmax_priority_search_tree.rb
85
89
  - lib/data_structures_rmolinari/shared.rb
86
90
  homepage: https://github.com/rmolinari/data_structures
87
91
  licenses: