lite-containers 0.0.5

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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +72 -0
  4. data/Gemfile +14 -0
  5. data/Gemfile.lock +127 -0
  6. data/README.md +145 -0
  7. data/bench/containers_bench.rb +122 -0
  8. data/lib/lite/containers/abstract/collection.rb +25 -0
  9. data/lib/lite/containers/abstract/deque.rb +23 -0
  10. data/lib/lite/containers/abstract/implicit_key.rb +23 -0
  11. data/lib/lite/containers/abstract/queue.rb +24 -0
  12. data/lib/lite/containers/abstract/sorted_map.rb +29 -0
  13. data/lib/lite/containers/avl_tree/balance.rb +74 -0
  14. data/lib/lite/containers/avl_tree/delete.rb +32 -0
  15. data/lib/lite/containers/avl_tree/find.rb +99 -0
  16. data/lib/lite/containers/avl_tree/implementation.rb +88 -0
  17. data/lib/lite/containers/avl_tree/insert.rb +27 -0
  18. data/lib/lite/containers/avl_tree/interfaces/bracket_assign.rb +33 -0
  19. data/lib/lite/containers/avl_tree/interfaces/key_extraction_strategy/abstract.rb +48 -0
  20. data/lib/lite/containers/avl_tree/interfaces/key_extraction_strategy/explicit.rb +34 -0
  21. data/lib/lite/containers/avl_tree/interfaces/key_extraction_strategy/implicit.rb +60 -0
  22. data/lib/lite/containers/avl_tree/node.rb +35 -0
  23. data/lib/lite/containers/avl_tree/traversal.rb +43 -0
  24. data/lib/lite/containers/avl_tree.rb +159 -0
  25. data/lib/lite/containers/error.rb +7 -0
  26. data/lib/lite/containers/heap.rb +169 -0
  27. data/lib/lite/containers/helpers/comparison.rb +106 -0
  28. data/lib/lite/containers/helpers/key_extractor.rb +29 -0
  29. data/lib/lite/containers/helpers/merge.rb +49 -0
  30. data/lib/lite/containers/sorted_array/binary_search.rb +42 -0
  31. data/lib/lite/containers/sorted_array.rb +188 -0
  32. data/lib/lite/containers/top_n/abstract.rb +56 -0
  33. data/lib/lite/containers/top_n/avl_tree.rb +25 -0
  34. data/lib/lite/containers/top_n/deque.rb +33 -0
  35. data/lib/lite/containers/top_n/error.rb +11 -0
  36. data/lib/lite/containers/top_n/heap.rb +32 -0
  37. data/lib/lite/containers/top_n.rb +31 -0
  38. data/lib/lite/containers/version.rb +7 -0
  39. data/lib/lite/containers.rb +3 -0
  40. data/lite_containers.gemspec +25 -0
  41. metadata +83 -0
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lite
4
+ module Containers
5
+ class AvlTree
6
+ module Delete
7
+ def delete(key, node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
8
+ return [nil, nil] if node.nil?
9
+
10
+ case compare(key, node.key)
11
+ when -1
12
+ deleted, node.left = delete(key, node.left)
13
+ [deleted, rebalance(node)]
14
+ when 0
15
+ if node.left.nil? || node.right.nil?
16
+ new_root = node.left.nil? ? node.right : node.left
17
+ [node, new_root]
18
+ else
19
+ new_root = leftmost_child(node.right)
20
+ _, new_root.right = delete(new_root.key, node.right)
21
+ new_root.left = node.left
22
+ [node, rebalance(new_root)]
23
+ end
24
+ when 1
25
+ deleted, node.right = delete(key, node.right)
26
+ [deleted, rebalance(node)]
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../helpers/comparison'
4
+
5
+ module Lite
6
+ module Containers
7
+ class AvlTree
8
+ module Find
9
+ def leftmost_child(node)
10
+ return node if node.left.nil?
11
+
12
+ leftmost_child(node.left)
13
+ end
14
+
15
+ def rightmost_child(node)
16
+ return node if node.right.nil?
17
+
18
+ rightmost_child(node.right)
19
+ end
20
+
21
+ module Exact
22
+ def find(key, node)
23
+ return nil unless node
24
+
25
+ case compare(key, node.key)
26
+ when -1
27
+ find(key, node.left)
28
+ when 0
29
+ node
30
+ when 1
31
+ find(key, node.right)
32
+ end
33
+ end
34
+ end
35
+
36
+ module Inexact
37
+ def find(key, node)
38
+ exact, candidate = find_candidate(key, node)
39
+ exact || candidate
40
+ end
41
+
42
+ def find_candidate(key, node)
43
+ return [nil, nil] unless node
44
+
45
+ case lookup_direction(key, node.key)
46
+ when -1
47
+ find_candidate(key, lookup_path(node, -1))
48
+ when 0
49
+ [node, nil]
50
+ when 1
51
+ exact, candidate = find_candidate(key, lookup_path(node, 1))
52
+ if exact
53
+ [exact, nil]
54
+ elsif candidate
55
+ [nil, candidate]
56
+ else
57
+ [nil, node]
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ module ExactOrNearestBackwards
64
+ include Inexact
65
+
66
+ def lookup_direction(search_key, node_key)
67
+ compare(search_key, node_key)
68
+ end
69
+
70
+ def lookup_path(node, comparison_result)
71
+ case comparison_result
72
+ when -1 then node.left
73
+ when 1 then node.right
74
+ else
75
+ raise Helpers::Comparison::Error, "Unexpected comparison result: #{comparison_result}"
76
+ end
77
+ end
78
+ end
79
+
80
+ module ExactOrNearestForwards
81
+ include Inexact
82
+
83
+ def lookup_direction(search_key, node_key)
84
+ compare(node_key, search_key)
85
+ end
86
+
87
+ def lookup_path(node, comparison_result)
88
+ case comparison_result
89
+ when -1 then node.right
90
+ when 1 then node.left
91
+ else
92
+ raise Helpers::Comparison::Error, "Unexpected comparison result: #{comparison_result}"
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../error'
4
+ require_relative '../helpers/comparison'
5
+ require_relative 'insert'
6
+ require_relative 'delete'
7
+ require_relative 'balance'
8
+ require_relative 'traversal'
9
+ require_relative 'find'
10
+
11
+ module Lite
12
+ module Containers
13
+ class AvlTree
14
+ class Error < Containers::Error; end
15
+
16
+ module Implementation
17
+ def self.instance(type)
18
+ type = type.to_sym
19
+ case type
20
+ when :max then Max
21
+ when :min then Min
22
+ else raise Error, Error, "Unexpected AVL tree type: '#{type}'"
23
+ end
24
+ end
25
+
26
+ include Balance
27
+ include Delete
28
+ include Insert
29
+ include Find
30
+ include Traversal
31
+
32
+ module Max
33
+ extend Implementation
34
+
35
+ module Compare
36
+ def compare(a, b)
37
+ Helpers::Comparison::Max.compare(a, b)
38
+ end
39
+ end
40
+
41
+ module Exact
42
+ extend Find::Exact
43
+ extend Compare
44
+ end
45
+
46
+ module ExactOrNearestForwards
47
+ extend Find::ExactOrNearestForwards
48
+ extend Compare
49
+ end
50
+
51
+ module ExactOrNearestBackwards
52
+ extend Find::ExactOrNearestBackwards
53
+ extend Compare
54
+ end
55
+
56
+ extend Compare
57
+ end
58
+
59
+ module Min
60
+ extend Implementation
61
+
62
+ module Compare
63
+ def compare(a, b)
64
+ Helpers::Comparison::Min.compare(a, b)
65
+ end
66
+ end
67
+
68
+ module Exact
69
+ extend Find::Exact
70
+ extend Compare
71
+ end
72
+
73
+ module ExactOrNearestForwards
74
+ extend Find::ExactOrNearestForwards
75
+ extend Compare
76
+ end
77
+
78
+ module ExactOrNearestBackwards
79
+ extend Find::ExactOrNearestBackwards
80
+ extend Compare
81
+ end
82
+
83
+ extend Compare
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'node'
4
+
5
+ module Lite
6
+ module Containers
7
+ class AvlTree
8
+ module Insert
9
+ def insert(node, key, value, merge, factory)
10
+ return true, factory.instance(key, value) if node.nil?
11
+
12
+ case compare(key, node.key)
13
+ when -1
14
+ increment, node.left = insert(node.left, key, value, merge, factory)
15
+ [increment, rebalance(node)]
16
+ when 0
17
+ node.value = merge.merge(node.value, value)
18
+ [false, node]
19
+ when 1
20
+ increment, node.right = insert(node.right, key, value, merge, factory)
21
+ [increment, rebalance(node)]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'key_extraction_strategy/explicit'
4
+ require_relative '../../helpers/merge'
5
+
6
+ module Lite
7
+ module Containers
8
+ class AvlTree
9
+ module Interfaces
10
+ module BracketAssign
11
+ include KeyExtractionStrategy::Explicit
12
+
13
+ module Instance
14
+ def instance(type, **opts)
15
+ raise ArgumentError, 'Disallowed keyword argument: merge' if opts.key?(:merge)
16
+
17
+ super(type, merge: :replace, **opts)
18
+ end
19
+ end
20
+
21
+ def self.included(base)
22
+ KeyExtractionStrategy::Abstract.enforce_exclusion!(base, KeyExtractionStrategy::Explicit)
23
+ base.extend Instance
24
+ end
25
+
26
+ def []=(key, value)
27
+ insert_pair key, value
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../error'
4
+
5
+ module Lite
6
+ module Containers
7
+ class AvlTree
8
+ module Interfaces
9
+ module KeyExtractionStrategy
10
+ module Abstract
11
+ def self.included(base)
12
+ register(base)
13
+ enforce_exclusion(base, base)
14
+ end
15
+
16
+ def self.register(strategy)
17
+ registry << strategy
18
+ end
19
+
20
+ def self.registry
21
+ @registry ||= Set.new
22
+ end
23
+
24
+ def self.enforce_exclusion(mod, strategy)
25
+ mod.define_singleton_method :included do |base|
26
+ Abstract.enforce_exclusion!(base, strategy)
27
+ super(base)
28
+ end
29
+ end
30
+
31
+ def self.enforce_exclusion!(base, strategy)
32
+ conflicts = Abstract
33
+ .registry
34
+ .select { |candidate| strategy != candidate && base < candidate }
35
+ .map { |conflict| conflict.name.split('::').last }
36
+
37
+ if !conflicts.empty?
38
+ raise Error, "Key extraction strategy conflict: #{conflicts.join(', ')}"
39
+ elsif base.is_a?(Module)
40
+ Abstract.enforce_exclusion(base, strategy)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'abstract'
4
+ require_relative '../../node'
5
+
6
+ module Lite
7
+ module Containers
8
+ class AvlTree
9
+ module Interfaces
10
+ module KeyExtractionStrategy
11
+ module Explicit
12
+ class Node < AvlTree::Node
13
+ def out
14
+ [key, value]
15
+ end
16
+ end
17
+
18
+ include Abstract
19
+
20
+ def insert(key, value)
21
+ insert_pair(key, value)
22
+ end
23
+
24
+ private
25
+
26
+ def node_factory
27
+ Node
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../helpers/key_extractor'
4
+ require_relative '../../../abstract/implicit_key'
5
+ require_relative '../../node'
6
+ require_relative 'abstract'
7
+
8
+ module Lite
9
+ module Containers
10
+ class AvlTree
11
+ module Interfaces
12
+ module KeyExtractionStrategy
13
+ module Implicit
14
+ include Abstract
15
+ include Containers::Abstract::ImplicitKey
16
+
17
+ class Node < AvlTree::Node
18
+ def out
19
+ value
20
+ end
21
+ end
22
+
23
+ module Instance
24
+ def instance(type, key_extractor: nil, **opts)
25
+ super(type, key_extractor: Helpers::KeyExtractor.instance(key_extractor), **opts)
26
+ end
27
+ end
28
+
29
+ def self.included(base)
30
+ Abstract.enforce_exclusion!(base, self)
31
+ base.extend Instance
32
+ end
33
+
34
+ def initialize(*args, key_extractor:, **opts)
35
+ @key_extractor = key_extractor
36
+ super(*args, **opts)
37
+ end
38
+
39
+ def push(value)
40
+ key = extract_key(value)
41
+ insert_pair key, value
42
+ end
43
+
44
+ private
45
+
46
+ def extract_key(object)
47
+ return object if @key_extractor.nil?
48
+
49
+ @key_extractor.to_key(object)
50
+ end
51
+
52
+ def node_factory
53
+ Node
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lite
4
+ module Containers
5
+ class AvlTree
6
+ class Node
7
+ attr_reader :key
8
+ attr_accessor :value, :height, :left, :right
9
+
10
+ def self.instance(key, value)
11
+ new key, value
12
+ end
13
+
14
+ private_class_method :new
15
+
16
+ def initialize(key, value)
17
+ @key = key
18
+ @value = value
19
+ @height = 1
20
+ @left = nil
21
+ @right = nil
22
+ end
23
+
24
+ def out
25
+ raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
26
+ end
27
+
28
+ def inspect
29
+ lr = "#{left.nil? ? 0 : :L}|#{right.nil? ? 0 : :R}"
30
+ "#<#{self.class.name} @key: #{key}, @value: #{value}, @height: #{height} #{lr}"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lite
4
+ module Containers
5
+ class AvlTree
6
+ module Traversal
7
+ def traverse(node, yielder, from:, to:)
8
+ return unless node
9
+
10
+ after_start = after_back_guard?(node, from)
11
+ traverse(node.left, yielder, from: from, to: to) if after_start
12
+ return unless before_front_guard?(node, to)
13
+
14
+ yielder.yield node.out if after_start
15
+ traverse(node.right, yielder, from: from, to: to)
16
+ end
17
+
18
+ def reverse_traverse(node, yielder, from:, to:)
19
+ return unless node
20
+
21
+ after_start = before_front_guard?(node, from)
22
+ reverse_traverse(node.right, yielder, from: from, to: to) if after_start
23
+ return unless after_back_guard?(node, to)
24
+
25
+ yielder.yield node.out if after_start
26
+ reverse_traverse(node.left, yielder, from: from, to: to)
27
+ end
28
+
29
+ def after_back_guard?(node, guard)
30
+ return true if guard.nil?
31
+
32
+ compare(guard, node.key) < 1
33
+ end
34
+
35
+ def before_front_guard?(node, guard)
36
+ return true if guard.nil?
37
+
38
+ compare(node.key, guard) < 1
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helpers/merge'
4
+ require_relative 'avl_tree/implementation'
5
+ require_relative 'avl_tree/interfaces/key_extraction_strategy/explicit'
6
+ require_relative 'avl_tree/interfaces/key_extraction_strategy/implicit'
7
+ require_relative 'abstract/collection'
8
+ require_relative 'abstract/deque'
9
+ require_relative 'abstract/sorted_map'
10
+
11
+ module Lite
12
+ module Containers
13
+ class AvlTree # rubocop:disable Metrics/ClassLength
14
+ include Abstract::Collection
15
+ include Abstract::Deque
16
+ include Abstract::SortedMap
17
+ include Enumerable
18
+
19
+ class ExplicitKey < AvlTree
20
+ include Interfaces::KeyExtractionStrategy::Explicit
21
+ end
22
+
23
+ class ImplicitKey < AvlTree
24
+ include Interfaces::KeyExtractionStrategy::Implicit
25
+ end
26
+
27
+ attr_reader :size
28
+
29
+ def self.instance(type, merge: nil, **opts)
30
+ impl = Implementation.instance(type)
31
+ merge = Helpers::Merge.instance(merge)
32
+ new(impl, merge, **opts)
33
+ end
34
+
35
+ private_class_method :new
36
+
37
+ def initialize(impl, merge)
38
+ @impl = impl
39
+ @merge = merge
40
+ reset!
41
+ end
42
+
43
+ def reset!
44
+ @root = nil
45
+ @size = 0
46
+ end
47
+
48
+ def key?(key)
49
+ !find_with_finder(key, @impl::Exact).nil?
50
+ end
51
+
52
+ def [](key)
53
+ find_with_finder(key, @impl::Exact)&.value
54
+ end
55
+
56
+ def find(key)
57
+ find_with_finder(key, @impl::Exact)&.out
58
+ end
59
+
60
+ def find_or_nearest_backwards(key)
61
+ find_with_finder(key, @impl::ExactOrNearestBackwards)&.out
62
+ end
63
+
64
+ def find_or_nearest_forwards(key)
65
+ find_with_finder(key, @impl::ExactOrNearestForwards)&.out
66
+ end
67
+
68
+ def delete(key)
69
+ deleted, @root = @impl.delete(key, @root)
70
+ @size -= 1 if deleted
71
+ deleted&.out
72
+ end
73
+
74
+ def each(&block)
75
+ if block
76
+ traverse.each(&block)
77
+ self
78
+ else
79
+ traverse
80
+ end
81
+ end
82
+
83
+ def reverse_each(&block)
84
+ if block
85
+ reverse_traverse.each(&block)
86
+ self
87
+ else
88
+ reverse_traverse
89
+ end
90
+ end
91
+
92
+ def traverse(from: nil, to: nil)
93
+ Enumerator.new do |yielder|
94
+ @impl.traverse @root, yielder, from: from, to: to
95
+ end
96
+ end
97
+
98
+ def reverse_traverse(from: nil, to: nil)
99
+ Enumerator.new do |yielder|
100
+ @impl.reverse_traverse @root, yielder, from: from, to: to
101
+ end
102
+ end
103
+
104
+ def front
105
+ fetch_front&.out
106
+ end
107
+
108
+ def pop_front
109
+ node = fetch_front
110
+ return unless node
111
+
112
+ delete(node.key)
113
+ node.out
114
+ end
115
+
116
+ def back
117
+ fetch_back&.out
118
+ end
119
+
120
+ def pop_back
121
+ node = fetch_back
122
+ return unless node
123
+
124
+ delete(node.key)
125
+ node.out
126
+ end
127
+
128
+ def drain!
129
+ array = reverse_traverse.to_a
130
+ reset!
131
+ array
132
+ end
133
+
134
+ private
135
+
136
+ def fetch_front
137
+ return if @root.nil?
138
+
139
+ @impl.rightmost_child(@root)
140
+ end
141
+
142
+ def fetch_back
143
+ return if @root.nil?
144
+
145
+ @impl.leftmost_child(@root)
146
+ end
147
+
148
+ def insert_pair(key, value)
149
+ increment, @root = @impl.insert(@root, key, value, @merge, node_factory)
150
+ @size += 1 if increment
151
+ self
152
+ end
153
+
154
+ def find_with_finder(key, finder)
155
+ finder.find(key, @root)
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lite
4
+ module Containers
5
+ class Error < StandardError; end
6
+ end
7
+ end