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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +72 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +127 -0
- data/README.md +145 -0
- data/bench/containers_bench.rb +122 -0
- data/lib/lite/containers/abstract/collection.rb +25 -0
- data/lib/lite/containers/abstract/deque.rb +23 -0
- data/lib/lite/containers/abstract/implicit_key.rb +23 -0
- data/lib/lite/containers/abstract/queue.rb +24 -0
- data/lib/lite/containers/abstract/sorted_map.rb +29 -0
- data/lib/lite/containers/avl_tree/balance.rb +74 -0
- data/lib/lite/containers/avl_tree/delete.rb +32 -0
- data/lib/lite/containers/avl_tree/find.rb +99 -0
- data/lib/lite/containers/avl_tree/implementation.rb +88 -0
- data/lib/lite/containers/avl_tree/insert.rb +27 -0
- data/lib/lite/containers/avl_tree/interfaces/bracket_assign.rb +33 -0
- data/lib/lite/containers/avl_tree/interfaces/key_extraction_strategy/abstract.rb +48 -0
- data/lib/lite/containers/avl_tree/interfaces/key_extraction_strategy/explicit.rb +34 -0
- data/lib/lite/containers/avl_tree/interfaces/key_extraction_strategy/implicit.rb +60 -0
- data/lib/lite/containers/avl_tree/node.rb +35 -0
- data/lib/lite/containers/avl_tree/traversal.rb +43 -0
- data/lib/lite/containers/avl_tree.rb +159 -0
- data/lib/lite/containers/error.rb +7 -0
- data/lib/lite/containers/heap.rb +169 -0
- data/lib/lite/containers/helpers/comparison.rb +106 -0
- data/lib/lite/containers/helpers/key_extractor.rb +29 -0
- data/lib/lite/containers/helpers/merge.rb +49 -0
- data/lib/lite/containers/sorted_array/binary_search.rb +42 -0
- data/lib/lite/containers/sorted_array.rb +188 -0
- data/lib/lite/containers/top_n/abstract.rb +56 -0
- data/lib/lite/containers/top_n/avl_tree.rb +25 -0
- data/lib/lite/containers/top_n/deque.rb +33 -0
- data/lib/lite/containers/top_n/error.rb +11 -0
- data/lib/lite/containers/top_n/heap.rb +32 -0
- data/lib/lite/containers/top_n.rb +31 -0
- data/lib/lite/containers/version.rb +7 -0
- data/lib/lite/containers.rb +3 -0
- data/lite_containers.gemspec +25 -0
- metadata +83 -0
@@ -0,0 +1,169 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'helpers/comparison'
|
4
|
+
require_relative 'abstract/collection'
|
5
|
+
require_relative 'abstract/queue'
|
6
|
+
require_relative 'abstract/implicit_key'
|
7
|
+
|
8
|
+
module Lite
|
9
|
+
module Containers
|
10
|
+
class Heap # rubocop:disable Metrics/ClassLength
|
11
|
+
include Abstract::Collection
|
12
|
+
include Abstract::ImplicitKey
|
13
|
+
include Abstract::Queue
|
14
|
+
|
15
|
+
class Error < StandardError; end
|
16
|
+
|
17
|
+
def self.instance(type, key_extractor: nil)
|
18
|
+
comparison = Helpers::Comparison.instance(type, key_extractor: key_extractor)
|
19
|
+
with_comparison(comparison)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.with_comparison(comparison)
|
23
|
+
new(comparison)
|
24
|
+
end
|
25
|
+
|
26
|
+
private_class_method :new
|
27
|
+
|
28
|
+
def initialize(comparison)
|
29
|
+
@comparison = comparison
|
30
|
+
reset!
|
31
|
+
freeze
|
32
|
+
end
|
33
|
+
|
34
|
+
def size
|
35
|
+
@array.size
|
36
|
+
end
|
37
|
+
|
38
|
+
def top
|
39
|
+
@array.first
|
40
|
+
end
|
41
|
+
|
42
|
+
def front
|
43
|
+
top
|
44
|
+
end
|
45
|
+
|
46
|
+
def push(value)
|
47
|
+
@array.push value
|
48
|
+
sift_up(length - 1)
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def replace_top(value)
|
53
|
+
sift_down(0, value)
|
54
|
+
end
|
55
|
+
|
56
|
+
def pop
|
57
|
+
return if length.zero?
|
58
|
+
|
59
|
+
if length == 1
|
60
|
+
@array.pop
|
61
|
+
else
|
62
|
+
value = top
|
63
|
+
sift_down(0, @array.pop)
|
64
|
+
value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def pop_front
|
69
|
+
pop
|
70
|
+
end
|
71
|
+
|
72
|
+
def drain!
|
73
|
+
result = []
|
74
|
+
result << pop while length.positive?
|
75
|
+
result
|
76
|
+
end
|
77
|
+
|
78
|
+
def reset!
|
79
|
+
if frozen?
|
80
|
+
@array.clear
|
81
|
+
else
|
82
|
+
@array = []
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def parent_index(index)
|
89
|
+
((index - 1) / 2)
|
90
|
+
end
|
91
|
+
|
92
|
+
def left_child_value(parent_index)
|
93
|
+
index = left_child_index(parent_index)
|
94
|
+
@array[index]
|
95
|
+
end
|
96
|
+
|
97
|
+
def left_child_index(index)
|
98
|
+
(2 * index) + 1
|
99
|
+
end
|
100
|
+
|
101
|
+
def right_child_value(parent_index)
|
102
|
+
index = right_child_index(parent_index)
|
103
|
+
@array[index]
|
104
|
+
end
|
105
|
+
|
106
|
+
def right_child_index(index)
|
107
|
+
(2 * index) + 2
|
108
|
+
end
|
109
|
+
|
110
|
+
def sift_up(index)
|
111
|
+
continue = true
|
112
|
+
while continue && index.positive?
|
113
|
+
parent_index = parent_index(index)
|
114
|
+
if greater? @array[index], @array[parent_index]
|
115
|
+
@array[parent_index], @array[index] = @array[index], @array[parent_index]
|
116
|
+
index = parent_index
|
117
|
+
else
|
118
|
+
continue = false
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def sift_down(index, replacement)
|
124
|
+
not_leaf_index = (length / 2) - 1
|
125
|
+
while index <= not_leaf_index
|
126
|
+
child_index = left_greater?(index) ? left_child_index(index) : right_child_index(index)
|
127
|
+
@array[index] = @array[child_index]
|
128
|
+
index = child_index
|
129
|
+
end
|
130
|
+
sift_up_replacement(index, replacement)
|
131
|
+
end
|
132
|
+
|
133
|
+
def sift_up_replacement(index, replacement)
|
134
|
+
@array[index] = replacement
|
135
|
+
sift_up(index)
|
136
|
+
end
|
137
|
+
|
138
|
+
def balanced_for_left?(parent_value, parent_index)
|
139
|
+
value = left_child_value(parent_index)
|
140
|
+
return true if value.nil?
|
141
|
+
|
142
|
+
less_than_or_equal?(value, parent_value)
|
143
|
+
end
|
144
|
+
|
145
|
+
def balanced_for_right?(parent_value, parent_index)
|
146
|
+
value = right_child_value(parent_index)
|
147
|
+
return true if value.nil?
|
148
|
+
|
149
|
+
less_than_or_equal?(value, parent_value)
|
150
|
+
end
|
151
|
+
|
152
|
+
def left_greater?(index)
|
153
|
+
left_value = left_child_value(index)
|
154
|
+
right_value = right_child_value(index)
|
155
|
+
return true if right_value.nil?
|
156
|
+
|
157
|
+
greater? left_value, right_value
|
158
|
+
end
|
159
|
+
|
160
|
+
def less_than_or_equal?(a, b)
|
161
|
+
@comparison.compare(a, b) < 1
|
162
|
+
end
|
163
|
+
|
164
|
+
def greater?(a, b)
|
165
|
+
@comparison.compare(a, b).positive?
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../error'
|
4
|
+
|
5
|
+
module Lite
|
6
|
+
module Containers
|
7
|
+
module Helpers
|
8
|
+
module Comparison
|
9
|
+
class Error < Containers::Error; end
|
10
|
+
|
11
|
+
def self.instance(type, key_extractor: nil)
|
12
|
+
type = type.to_sym
|
13
|
+
case type
|
14
|
+
when :max then Max.instance(key_extractor)
|
15
|
+
when :min then Min.instance(key_extractor)
|
16
|
+
else raise Error, "Unexpected comparison type: '#{type}'"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Abstract
|
21
|
+
def initialize(key_extractor)
|
22
|
+
@key_extractor = key_extractor
|
23
|
+
freeze
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_key(element)
|
27
|
+
@key_extractor.call(element)
|
28
|
+
end
|
29
|
+
|
30
|
+
def for_item(item)
|
31
|
+
method(:compare).curry.call(item)
|
32
|
+
end
|
33
|
+
|
34
|
+
def for_key(key)
|
35
|
+
comparison = self.class
|
36
|
+
proc do |b|
|
37
|
+
comparison.compare(key, to_key(b))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def compare(a, b)
|
42
|
+
self.class.compare(to_key(a), to_key(b))
|
43
|
+
end
|
44
|
+
|
45
|
+
class << self
|
46
|
+
def to_key(element)
|
47
|
+
element
|
48
|
+
end
|
49
|
+
|
50
|
+
def for_item(item)
|
51
|
+
method(:compare).curry.call(item)
|
52
|
+
end
|
53
|
+
|
54
|
+
alias for_key for_item
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Max < Abstract
|
59
|
+
def invert
|
60
|
+
Comparison.instance :min, key_extractor: @key_extractor
|
61
|
+
end
|
62
|
+
|
63
|
+
class << self
|
64
|
+
def instance(key_extractor)
|
65
|
+
key_extractor ? new(key_extractor) : self
|
66
|
+
end
|
67
|
+
|
68
|
+
def compare(a, b)
|
69
|
+
result = a <=> b
|
70
|
+
raise Error, "No meaningful comparison between #{a} <=> #{b}" if result.nil?
|
71
|
+
|
72
|
+
result
|
73
|
+
end
|
74
|
+
|
75
|
+
def invert
|
76
|
+
Min
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class Min < Abstract
|
82
|
+
def invert
|
83
|
+
Comparison.instance :max, key_extractor: @key_extractor
|
84
|
+
end
|
85
|
+
|
86
|
+
class << self
|
87
|
+
def instance(key_extractor)
|
88
|
+
key_extractor ? new(key_extractor) : self
|
89
|
+
end
|
90
|
+
|
91
|
+
def compare(a, b)
|
92
|
+
result = b <=> a
|
93
|
+
raise Error, "No meaningful comparison between #{a} <=> #{b}" if result.nil?
|
94
|
+
|
95
|
+
result
|
96
|
+
end
|
97
|
+
|
98
|
+
def invert
|
99
|
+
Max
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../error'
|
4
|
+
|
5
|
+
module Lite
|
6
|
+
module Containers
|
7
|
+
module Helpers
|
8
|
+
class KeyExtractor
|
9
|
+
def self.instance(input)
|
10
|
+
return if input.nil?
|
11
|
+
return input if input.respond_to? :to_key
|
12
|
+
return new input if input.is_a? Proc
|
13
|
+
|
14
|
+
raise Error, "Expected nil, key extractor or a proc, got #{input.inspect}"
|
15
|
+
end
|
16
|
+
|
17
|
+
private_class_method :new
|
18
|
+
|
19
|
+
def initialize(block)
|
20
|
+
@block = block
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_key(element)
|
24
|
+
@block.call(element)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../error'
|
4
|
+
|
5
|
+
module Lite
|
6
|
+
module Containers
|
7
|
+
module Helpers
|
8
|
+
module Merge
|
9
|
+
class Error < Containers::Error; end
|
10
|
+
|
11
|
+
def self.instance(strategy)
|
12
|
+
case strategy
|
13
|
+
when nil, :replace then Replace
|
14
|
+
when :keep then Keep
|
15
|
+
when Proc then Custom.new(strategy)
|
16
|
+
else raise Error, "Unexpected strategy for merge: #{strategy}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Replace
|
21
|
+
def self.merge(_old, fresh)
|
22
|
+
fresh
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Keep
|
27
|
+
def self.merge(old, _fresh)
|
28
|
+
old
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Custom
|
33
|
+
def initialize(proc)
|
34
|
+
@proc = proc
|
35
|
+
freeze
|
36
|
+
end
|
37
|
+
|
38
|
+
def merge(old, fresh)
|
39
|
+
proc.call(old, fresh)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :proc
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../helpers/comparison'
|
4
|
+
|
5
|
+
module Lite
|
6
|
+
module Containers
|
7
|
+
class SortedArray
|
8
|
+
module BinarySearch
|
9
|
+
def self.position_of(sorted_array, key, comparison: Helpers::Comparison::Max)
|
10
|
+
comparator = comparison.for_key(key)
|
11
|
+
find_position(sorted_array, comparator)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.find_position(sorted_array, comparator)
|
15
|
+
upper = sorted_array.length
|
16
|
+
lower = 0
|
17
|
+
found = false
|
18
|
+
index = lower
|
19
|
+
|
20
|
+
while !found && upper > lower
|
21
|
+
index = midpoint(lower, upper)
|
22
|
+
candidate = sorted_array[index]
|
23
|
+
result = comparator.call(candidate)
|
24
|
+
if result.zero?
|
25
|
+
found = true
|
26
|
+
elsif result.negative?
|
27
|
+
upper = index
|
28
|
+
else
|
29
|
+
index += 1
|
30
|
+
lower = index
|
31
|
+
end
|
32
|
+
end
|
33
|
+
[found, index]
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.midpoint(lower, upper)
|
37
|
+
((upper - lower) / 2) + lower
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'error'
|
4
|
+
require_relative 'helpers/comparison'
|
5
|
+
require_relative 'helpers/merge'
|
6
|
+
require_relative 'sorted_array/binary_search'
|
7
|
+
require_relative 'abstract/collection'
|
8
|
+
require_relative 'abstract/deque'
|
9
|
+
require_relative 'abstract/implicit_key'
|
10
|
+
require_relative 'abstract/sorted_map'
|
11
|
+
|
12
|
+
module Lite
|
13
|
+
module Containers
|
14
|
+
class SortedArray # rubocop:disable Metrics/ClassLength
|
15
|
+
include Abstract::Collection
|
16
|
+
include Enumerable
|
17
|
+
include Abstract::ImplicitKey
|
18
|
+
include Abstract::Deque
|
19
|
+
include Abstract::SortedMap
|
20
|
+
|
21
|
+
class Error < Containers::Error; end
|
22
|
+
|
23
|
+
attr_reader :comparator
|
24
|
+
|
25
|
+
def self.from_unsorted(type, array, key_extractor: nil, merge: nil)
|
26
|
+
comparison = comparison(type, key_extractor)
|
27
|
+
sorted = array.sort { |a, b| comparison.compare(a, b) }
|
28
|
+
construct(sorted, comparison, merge)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.from_sorted(type, array, key_extractor: nil, merge: nil)
|
32
|
+
comparison = comparison(type, key_extractor)
|
33
|
+
sorted = ensure_sorted!(array, comparison)
|
34
|
+
construct(sorted, comparison, merge)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.from_sorted_unsafe(type, sorted, key_extractor: nil, merge: nil)
|
38
|
+
comparison = comparison(type, key_extractor)
|
39
|
+
construct(sorted, comparison, merge)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.comparison(type, key_extractor)
|
43
|
+
Helpers::Comparison.instance(type, key_extractor: key_extractor)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.construct(sorted, comparison, merge)
|
47
|
+
merge = Helpers::Merge.instance(merge)
|
48
|
+
deduplicated = handle_duplicates(sorted, comparison, merge)
|
49
|
+
new(deduplicated, comparison, merge)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.handle_duplicates(array, comparison, merge)
|
53
|
+
chunks = array.chunk_while { |a, b| comparison.compare(a, b).zero? }
|
54
|
+
chunks.map do |chunk|
|
55
|
+
first, *rest = chunk
|
56
|
+
next first if rest.empty?
|
57
|
+
|
58
|
+
rest.reduce(first) do |last, current|
|
59
|
+
merge.merge(last, current)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.ensure_sorted!(array, comparison)
|
65
|
+
array.each_cons(2) do |a, b|
|
66
|
+
raise Error, 'Input array is not sorted' unless comparison.compare(a, b) < 1
|
67
|
+
end
|
68
|
+
array
|
69
|
+
end
|
70
|
+
|
71
|
+
private_class_method :new, :comparison, :construct, :handle_duplicates, :ensure_sorted!
|
72
|
+
|
73
|
+
def initialize(array, comparison, merge)
|
74
|
+
@comparison = comparison
|
75
|
+
@merge = merge
|
76
|
+
@array = array.freeze
|
77
|
+
end
|
78
|
+
|
79
|
+
def drain!
|
80
|
+
array = @array
|
81
|
+
@array = []
|
82
|
+
array.reverse
|
83
|
+
end
|
84
|
+
|
85
|
+
def size
|
86
|
+
@array.size
|
87
|
+
end
|
88
|
+
|
89
|
+
def index_of(item)
|
90
|
+
found, index = position_of(item)
|
91
|
+
return unless found
|
92
|
+
|
93
|
+
index
|
94
|
+
end
|
95
|
+
|
96
|
+
def position_of(item)
|
97
|
+
BinarySearch.find_position(@array, comparison.for_item(item))
|
98
|
+
end
|
99
|
+
|
100
|
+
def each(&block)
|
101
|
+
if block
|
102
|
+
array.each(&block)
|
103
|
+
self
|
104
|
+
else
|
105
|
+
array.each
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_array
|
110
|
+
array
|
111
|
+
end
|
112
|
+
|
113
|
+
def front
|
114
|
+
array.last
|
115
|
+
end
|
116
|
+
|
117
|
+
def pop_front
|
118
|
+
*rest, item = @array
|
119
|
+
@array = rest.freeze
|
120
|
+
item
|
121
|
+
end
|
122
|
+
|
123
|
+
def back
|
124
|
+
array.first
|
125
|
+
end
|
126
|
+
|
127
|
+
def pop_back
|
128
|
+
item, *rest = @array
|
129
|
+
@array = rest.freeze
|
130
|
+
item
|
131
|
+
end
|
132
|
+
|
133
|
+
def [](index)
|
134
|
+
array[index]
|
135
|
+
end
|
136
|
+
|
137
|
+
def key?(key)
|
138
|
+
found, = BinarySearch.position_of(@array, key, comparison: comparison)
|
139
|
+
found
|
140
|
+
end
|
141
|
+
|
142
|
+
def find(key)
|
143
|
+
found, index = BinarySearch.position_of(@array, key, comparison: comparison)
|
144
|
+
self[index] if found
|
145
|
+
end
|
146
|
+
|
147
|
+
def find_or_nearest_backwards(key)
|
148
|
+
found, position = BinarySearch.position_of(@array, key, comparison: comparison)
|
149
|
+
return self[position] if found
|
150
|
+
return if position.zero?
|
151
|
+
|
152
|
+
self[position - 1]
|
153
|
+
end
|
154
|
+
|
155
|
+
def find_or_nearest_forwards(key)
|
156
|
+
_, position = BinarySearch.position_of(@array, key, comparison: comparison)
|
157
|
+
self[position]
|
158
|
+
end
|
159
|
+
|
160
|
+
def remove_at(index)
|
161
|
+
return if index.negative?
|
162
|
+
|
163
|
+
before = array[0...index]
|
164
|
+
to_remove = array[index]
|
165
|
+
after = array[(index + 1)..]
|
166
|
+
@array = [*before, *after].freeze
|
167
|
+
to_remove
|
168
|
+
end
|
169
|
+
|
170
|
+
def push(item)
|
171
|
+
found, position = position_of(item)
|
172
|
+
item = handle_duplicate(position, item) if found
|
173
|
+
before = array[0...position]
|
174
|
+
keep_from = found ? position + 1 : position
|
175
|
+
after = array[keep_from..]
|
176
|
+
@array = [*before, item, *after].freeze
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
attr_reader :array, :comparison, :merge
|
182
|
+
|
183
|
+
def handle_duplicate(index, fresh)
|
184
|
+
merge.merge(array[index], fresh)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require_relative 'error'
|
5
|
+
require_relative '../abstract/collection'
|
6
|
+
require_relative '../abstract/implicit_key'
|
7
|
+
require_relative '../top_n'
|
8
|
+
|
9
|
+
module Lite
|
10
|
+
module Containers
|
11
|
+
module TopN
|
12
|
+
module Abstract
|
13
|
+
include Containers::Abstract::Collection
|
14
|
+
include Containers::Abstract::ImplicitKey
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
def_delegator(:@backend, :size)
|
18
|
+
|
19
|
+
def initialize(backend, limit, filter)
|
20
|
+
@backend = backend
|
21
|
+
@limit = limit
|
22
|
+
@filter = filter
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :limit
|
26
|
+
|
27
|
+
def push(new_item)
|
28
|
+
return unless pass?(new_item)
|
29
|
+
|
30
|
+
@backend.push(new_item)
|
31
|
+
shrink
|
32
|
+
end
|
33
|
+
|
34
|
+
def drain!
|
35
|
+
@backend.drain!
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def pass?(item)
|
41
|
+
return true if @filter.nil?
|
42
|
+
|
43
|
+
@filter.call(item)
|
44
|
+
end
|
45
|
+
|
46
|
+
def shrink
|
47
|
+
return [] if @limit.nil?
|
48
|
+
|
49
|
+
drop = []
|
50
|
+
drop << pop while @backend.size > @limit
|
51
|
+
drop
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'abstract'
|
4
|
+
require_relative 'deque'
|
5
|
+
require_relative '../avl_tree'
|
6
|
+
|
7
|
+
module Lite
|
8
|
+
module Containers
|
9
|
+
module TopN
|
10
|
+
class AvlTree
|
11
|
+
include Deque
|
12
|
+
include Abstract
|
13
|
+
|
14
|
+
Backend = Containers::AvlTree::ImplicitKey
|
15
|
+
|
16
|
+
def self.instance(type, limit: nil, filter: nil, **backend_options)
|
17
|
+
backend = Backend.instance(type, **backend_options)
|
18
|
+
new backend, limit, filter
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
register_backend :avl_tree, AvlTree
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../abstract/deque'
|
4
|
+
|
5
|
+
module Lite
|
6
|
+
module Containers
|
7
|
+
module TopN
|
8
|
+
module Deque
|
9
|
+
include Containers::Abstract::Deque
|
10
|
+
|
11
|
+
def pop
|
12
|
+
pop_back
|
13
|
+
end
|
14
|
+
|
15
|
+
def front
|
16
|
+
@backend.front
|
17
|
+
end
|
18
|
+
|
19
|
+
def pop_front
|
20
|
+
@backend.pop_front
|
21
|
+
end
|
22
|
+
|
23
|
+
def back
|
24
|
+
@backend.back
|
25
|
+
end
|
26
|
+
|
27
|
+
def pop_back
|
28
|
+
@backend.pop_back
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|