qfill 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.github/dependabot.yml +8 -0
  3. data/.github/workflows/style.yml +37 -0
  4. data/.github/workflows/test.yml +55 -0
  5. data/.rspec +3 -2
  6. data/.rubocop.yml +26 -0
  7. data/.rubocop_todo.yml +163 -0
  8. data/.simplecov +6 -0
  9. data/CODE_OF_CONDUCT.md +133 -0
  10. data/Gemfile +33 -0
  11. data/Guardfile +12 -0
  12. data/{LICENSE.txt → LICENSE} +1 -1
  13. data/README.md +2 -2
  14. data/Rakefile +24 -1
  15. data/lib/qfill.rb +13 -10
  16. data/lib/qfill/errors/invalid_index.rb +8 -0
  17. data/lib/qfill/filter.rb +5 -3
  18. data/lib/qfill/list.rb +4 -2
  19. data/lib/qfill/list_set.rb +13 -9
  20. data/lib/qfill/manager.rb +78 -124
  21. data/lib/qfill/origin.rb +4 -3
  22. data/lib/qfill/popper.rb +30 -25
  23. data/lib/qfill/pusher.rb +33 -22
  24. data/lib/qfill/result.rb +63 -42
  25. data/lib/qfill/strategy.rb +13 -0
  26. data/lib/qfill/strategy/base.rb +65 -0
  27. data/lib/qfill/strategy/drain_to_empty.rb +73 -0
  28. data/lib/qfill/strategy/drain_to_limit.rb +46 -0
  29. data/lib/qfill/strategy/sample.rb +42 -0
  30. data/lib/qfill/strategy/time_slice.rb +106 -0
  31. data/lib/qfill/version.rb +3 -1
  32. data/maintenance-branch +1 -0
  33. data/qfill.gemspec +15 -13
  34. data/spec/qfill/filter_spec.rb +35 -26
  35. data/spec/qfill/list_set_spec.rb +28 -23
  36. data/spec/qfill/list_spec.rb +35 -27
  37. data/spec/qfill/manager_spec.rb +670 -434
  38. data/spec/qfill/origin_spec.rb +45 -35
  39. data/spec/qfill/popper_spec.rb +36 -30
  40. data/spec/qfill/pusher_spec.rb +32 -26
  41. data/spec/qfill/result_spec.rb +49 -38
  42. data/spec/qfill_spec.rb +6 -5
  43. data/spec/spec_helper.rb +11 -38
  44. data/spec/support/helper.rb +13 -0
  45. data/spec/support/random_object.rb +30 -0
  46. metadata +52 -23
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qfill
4
+ module Strategy
5
+ class DrainToEmpty < Qfill::Strategy::Base
6
+ NAME = :drain_to_empty
7
+
8
+ def on_fill!
9
+ preferred_potential_ratio = 0
10
+ preferred_potential = 0
11
+ result.list_ratios.each do |list_name, list_ratio|
12
+ poppy = result.preferred.select { |x| x == list_name }
13
+ next unless poppy
14
+
15
+ preferred_potential_ratio += list_ratio
16
+ num = popper[list_name].elements.length
17
+ preferred_potential += num
18
+ result.max_tracker[list_name] = num
19
+ end
20
+ result.preferred_potential = preferred_potential
21
+ result.preferred_potential_ratio = preferred_potential_ratio
22
+ end
23
+
24
+ def result_max!
25
+ result.max = if result.preferred_potential_ratio.positive?
26
+ [
27
+ (result.preferred_potential / result.preferred_potential_ratio),
28
+ primary_list_total,
29
+ remaining
30
+ ].min
31
+ else
32
+ [
33
+ primary_list_total,
34
+ remaining
35
+ ].min
36
+ end
37
+ end
38
+
39
+ def fill_up_to_ratio!
40
+ popper.primary.each do |queue|
41
+ array_to_push = popper.next_objects!(queue.name, [result.max, remaining].min)
42
+ self.added = result.push(array_to_push, queue.name)
43
+ popper.current_index = popper.index_of(queue.name)
44
+ bump!
45
+ puts "[fill_up_to_ratio!]#{self}[Q:#{queue.name}][added:#{added}]" if Qfill::VERBOSE
46
+ break if is_full?
47
+ end
48
+ end
49
+
50
+ def fill_according_to_list_ratios!
51
+ # Are there any elements in preferred queues that we should add?
52
+ return unless result.preferred_potential.positive?
53
+
54
+ # Setup a ratio modifier for the non-preferred queues
55
+ result.list_ratios.each do |list_name, list_ratio|
56
+ max_from_list = if result.max_tracker[list_name]
57
+ [result.max_tracker[list_name], remaining].min
58
+ else
59
+ Qfill::Result.get_limit_from_max_and_ratio(
60
+ result.max, list_ratio, remaining
61
+ )
62
+ end
63
+ array_to_push = popper.next_objects!(list_name, max_from_list)
64
+ self.added = result.push(array_to_push, list_name)
65
+ popper.current_index = popper.index_of(list_name)
66
+ bump!
67
+ puts "[fill_according_to_list_ratios!]#{self}[#{list_name}][added:#{added}]" if Qfill::VERBOSE
68
+ break if is_full?
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qfill
4
+ module Strategy
5
+ class DrainToLimit < Qfill::Strategy::Base
6
+ NAME = :drain_to_limit
7
+
8
+ def on_fill!
9
+ # NOOP
10
+ end
11
+
12
+ def result_max!
13
+ result.max = Qfill::Result.get_limit_from_max_and_ratio(primary_list_total, result.ratio, remaining)
14
+ end
15
+
16
+ def fill_up_to_ratio!
17
+ num_primary = popper.primary.length
18
+ ratio = 1.0 / num_primary # 1 divided by the number of queues
19
+ max_from_list = Qfill::Result.get_limit_from_max_and_ratio(result.max, ratio, remaining)
20
+ popper.primary.each_with_index do |queue, idx|
21
+ # Are there leftovers that will be missed by a straight ratio'd iteration?
22
+ mod = result.max % num_primary
23
+ max_from_list += (mod / num_primary).ceil if idx.zero? && mod.positive?
24
+ array_to_push = popper.next_objects!(queue.name, [max_from_list, remaining].min)
25
+ self.added = result.push(array_to_push, queue.name)
26
+ popper.current_index = popper.index_of(queue.name)
27
+ puts "[fill_up_to_ratio!]#{self}[Q:#{queue.name}][added:#{added}]" if Qfill::VERBOSE
28
+ bump!
29
+ break if is_full?
30
+ end
31
+ end
32
+
33
+ def fill_according_to_list_ratios!
34
+ result.list_ratios.each do |list_name, list_ratio|
35
+ max_from_list = Qfill::Result.get_limit_from_max_and_ratio(result.max, list_ratio, remaining)
36
+ array_to_push = popper.next_objects!(list_name, max_from_list)
37
+ self.added = result.push(array_to_push, list_name)
38
+ popper.current_index = popper.index_of(list_name)
39
+ puts "[fill_according_to_list_ratios!]#{self}[#{list_name}][added:#{added}]" if Qfill::VERBOSE
40
+ bump!
41
+ break if is_full?
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qfill
4
+ module Strategy
5
+ class Sample < Qfill::Strategy::Base
6
+ NAME = :sample
7
+
8
+ def on_fill!
9
+ # NOOP
10
+ end
11
+
12
+ def result_max!
13
+ result.max = Qfill::Result.get_limit_from_max_and_ratio(primary_list_total, result.ratio, remaining)
14
+ end
15
+
16
+ def fill_up_to_ratio!
17
+ ratio = 1.0 / popper.primary.length # 1 divided by the number of queues
18
+ max_from_list = Qfill::Result.get_limit_from_max_and_ratio(result.max, ratio, remaining)
19
+ while !is_full? && !result.is_full? && !popper.totally_empty? && (origin_list = popper.current_list)
20
+ array_to_push = popper.next_objects!(origin_list.name, [max_from_list, remaining].min)
21
+ self.added = result.push(array_to_push, origin_list.name)
22
+ bump!
23
+ puts "[fill_up_to_ratio!]#{self}[Added:#{added}][Max List:#{max_from_list}][ratio:#{ratio}][added:#{added}]" if Qfill::VERBOSE
24
+ popper.set_next_as_current!
25
+ end
26
+ end
27
+
28
+ def fill_according_to_list_ratios!
29
+ # puts "#{!is_full?} && #{result.fill_count} >= #{result.max} && #{!self.popper.totally_empty?} && #{(list_ratio_tuple = result.current_list_ratio)}"
30
+ while !is_full? && !result.is_full? && !popper.totally_empty? && (list_ratio_tuple = result.current_list_ratio)
31
+ max_from_list = Qfill::Result.get_limit_from_max_and_ratio(result.max, list_ratio_tuple[1], remaining)
32
+ array_to_push = popper.next_objects!(list_ratio_tuple[0], max_from_list)
33
+ self.added = result.push(array_to_push, list_ratio_tuple[0])
34
+ bump!
35
+ puts "[fill_according_to_list_ratios!]#{self}[#{list_ratio_tuple[0]}][added:#{added}]" if Qfill::VERBOSE
36
+ result.set_next_as_current!
37
+ break if is_full?
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qfill
4
+ module Strategy
5
+ # Qfill::Manager.new(
6
+ # :popper => popper,
7
+ # :strategy_options => {
8
+ # :window_size => 20,
9
+ # :window_units => "minutes" # "days", "hours", "minutes", "seconds",
10
+ # # NOTE: pane_size/units can't be larger than the window_size/units
11
+ # :pane_size => 2
12
+ # :pane_units => "seconds" # "days", "hours", "minutes", "seconds",
13
+ # },
14
+ # )
15
+ class TimeSlice < Qfill::Strategy::Sample
16
+ NAME = :time_slice
17
+
18
+ def on_fill!
19
+ # NOOP
20
+ end
21
+
22
+ CONVERSIONS = {
23
+ %w[seconds seconds] => 1,
24
+ %w[seconds minutes] => 60,
25
+ %w[seconds hours] => 60 * 60,
26
+ %w[seconds days] => 60 * 60 * 24,
27
+ %w[minutes minutes] => 1,
28
+ %w[minutes hours] => 60,
29
+ %w[minutes days] => 60 * 24,
30
+ %w[hours hours] => 1,
31
+ %w[hours days] => 24,
32
+ %w[days days] => 1
33
+ }.freeze
34
+
35
+ # If window_units == "minutes" and pane_units == "seconds", and
36
+ # window_size == 20 and pane_size == 2
37
+ # Then there would be (20 * CONVERSIONS[[pane_units, window_units]]) / pane_size
38
+ # i.e. (20 * 60) / 2
39
+ # i.e. 600 individual panes in the (time) window, where each pane is a "result"
40
+ def default_pusher
41
+ ratio = 1 / num_panes.to_f
42
+ array = Range.new(1, num_panes).each_with_object([]) do |pane_num, arr|
43
+ arr << { name: pane_num.to_s, ratio: ratio }
44
+ end
45
+ Qfill::Pusher.from_array_of_hashes(array)
46
+ end
47
+
48
+ def window_size
49
+ strategy_options[:window_size]
50
+ end
51
+
52
+ def window_units
53
+ strategy_options[:window_units]
54
+ end
55
+
56
+ def pane_size
57
+ strategy_options[:pane_size]
58
+ end
59
+
60
+ def pane_units
61
+ strategy_options[:pane_units]
62
+ end
63
+
64
+ def conversion
65
+ conversion_idx = [pane_units, window_units]
66
+ conv = CONVERSIONS[conversion_idx]
67
+ raise ArgumentError, "pane_units: #{pane_units} must not be larger than window_units: #{window_units}" unless conv
68
+
69
+ conv
70
+ end
71
+
72
+ def num_panes
73
+ ((window_size * conversion) / pane_size)
74
+ end
75
+
76
+ def result_max!
77
+ result.max = Qfill::Result.get_limit_from_max_and_ratio(primary_list_total, result.ratio, remaining)
78
+ end
79
+
80
+ def fill_up_to_ratio!
81
+ ratio = 1.0 / popper.primary.length # 1 divided by the number of queues
82
+ max_from_list = Qfill::Result.get_limit_from_max_and_ratio(result.max, ratio, remaining)
83
+ while !is_full? && !result.is_full? && !popper.totally_empty? && (origin_list = popper.current_list)
84
+ array_to_push = popper.next_objects!(origin_list.name, [max_from_list, remaining].min)
85
+ self.added = result.push(array_to_push, origin_list.name)
86
+ bump!
87
+ puts "[fill_up_to_ratio!]#{self}[Added:#{added}][Max List:#{max_from_list}][ratio:#{ratio}][added:#{added}]" if Qfill::VERBOSE
88
+ popper.set_next_as_current!
89
+ end
90
+ end
91
+
92
+ def fill_according_to_list_ratios!
93
+ # puts "#{!is_full?} && #{result.fill_count} >= #{result.max} && #{!self.popper.totally_empty?} && #{(list_ratio_tuple = result.current_list_ratio)}"
94
+ while !is_full? && !result.is_full? && !popper.totally_empty? && (list_ratio_tuple = result.current_list_ratio)
95
+ max_from_list = Qfill::Result.get_limit_from_max_and_ratio(result.max, list_ratio_tuple[1], remaining)
96
+ array_to_push = popper.next_objects!(list_ratio_tuple[0], max_from_list)
97
+ self.added = result.push(array_to_push, list_ratio_tuple[0])
98
+ bump!
99
+ puts "[fill_according_to_list_ratios!]#{self}[#{list_ratio_tuple[0]}][added:#{added}]" if Qfill::VERBOSE
100
+ result.set_next_as_current!
101
+ break if is_full?
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
data/lib/qfill/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Qfill
2
- VERSION = "0.0.4"
4
+ VERSION = '0.1.0'
3
5
  end
@@ -0,0 +1 @@
1
+ master
data/qfill.gemspec CHANGED
@@ -1,15 +1,16 @@
1
- # -*- encoding: utf-8 -*-
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'qfill/version'
5
6
 
6
7
  Gem::Specification.new do |gem|
7
- gem.name = "qfill"
8
+ gem.name = 'qfill'
8
9
  gem.version = Qfill::VERSION
9
- gem.authors = ["Peter Boling"]
10
- gem.email = ["peter.boling@gmail.com"]
11
- gem.description = %q{Advanced Queue Transformation}
12
- gem.summary = %q{You have a set of arrays that need to be turned into a different set of arrays
10
+ gem.authors = ['Peter Boling']
11
+ gem.email = ['peter.boling@gmail.com']
12
+ gem.description = 'Advanced Queue Transformation'
13
+ gem.summary = 'You have a set of arrays that need to be turned into a different set of arrays
13
14
  according to a potentially non-uniform set of rules.
14
15
 
15
16
  Now you can easily turn this:
@@ -24,13 +25,14 @@ result_b # => [3,5,7,9]
24
25
  result_c # => [4,6,8]
25
26
 
26
27
  by specifying filters for handling each transformation.
27
- }
28
- gem.homepage = ""
28
+ '
29
+ gem.homepage = 'https://github.com/pboling/qfill'
29
30
 
30
- gem.files = `git ls-files`.split($/)
31
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
31
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
32
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
32
33
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
33
- gem.require_paths = ["lib"]
34
+ gem.require_paths = ['lib']
34
35
 
35
- gem.add_development_dependency 'rspec'
36
+ gem.add_development_dependency 'rake', '~> 13'
37
+ gem.add_development_dependency 'rspec', '~> 3'
36
38
  end
@@ -1,43 +1,52 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  describe Qfill::Filter do
3
- context "#new" do
4
- context "with processor" do
5
- before :each do
6
- @lambda = -> (object) { !object.nil? }
5
+ describe '#new' do
6
+ context 'with processor' do
7
+ before do
8
+ @lambda = ->(object) { !object.nil? }
7
9
  end
8
- it "should instantiate with processor" do
9
- Qfill::Filter.new(@lambda).should be_a(Qfill::Filter)
10
+
11
+ it 'instantiates with processor' do
12
+ expect(described_class.new(@lambda)).to be_a(described_class)
10
13
  end
11
14
  end
12
15
 
13
- context "with processor and arguments" do
14
- before :each do
15
- @lambda = -> (object, first, second) { !object.nil? && first == first && second == 'second' }
16
- @arguments = ['first','second']
16
+ context 'with processor and arguments' do
17
+ before do
18
+ @lambda = ->(object, first, second) { !object.nil? && first == first && second == 'second' }
19
+ @arguments = %w[first second]
17
20
  end
18
- it "should instantiate with processor" do
19
- Qfill::Filter.new(@lambda, *@arguments).should be_a(Qfill::Filter)
21
+
22
+ it 'instantiates with processor' do
23
+ expect(described_class.new(@lambda, *@arguments)).to be_a(described_class)
20
24
  end
21
25
  end
22
26
  end
23
27
 
24
- context "#run" do
25
- before :each do
26
- @lambda = -> (object, first, second) { !object.nil? && first == first && second == 'second' }
27
- @arguments = ['first','second']
28
- @filter = Qfill::Filter.new(@lambda, *@arguments)
28
+ describe '#run' do
29
+ before do
30
+ @lambda = ->(object, first, second) { !object.nil? && first == first && second == 'second' }
31
+ @arguments = %w[first second]
32
+ @filter = described_class.new(@lambda, *@arguments)
29
33
  end
30
- it "should return the correct result" do
31
- @filter.run('not nil').should == true
34
+
35
+ it 'returns the correct result' do
36
+ expect(@filter.run('not nil')).to eq(true)
32
37
  end
33
- context "with extra arguments" do
34
- before :each do
35
- @lambda = -> (object, special_arg1, special_arg2, first, second, third) { !object.nil? && first == first && second == 'second' && special_arg1 = 'this' && special_arg2 == 'thing' && third == 'third' }
36
- @arguments = ['first','second','third']
37
- @filter = Qfill::Filter.new(@lambda, *@arguments)
38
+
39
+ context 'with extra arguments' do
40
+ before do
41
+ @lambda = lambda { |object, _special_arg1, special_arg2, first, second, third|
42
+ !object.nil? && first == first && second == 'second' && special_arg1 = 'this' && special_arg2 == 'thing' && third == 'third'
43
+ }
44
+ @arguments = %w[first second third]
45
+ @filter = described_class.new(@lambda, *@arguments)
38
46
  end
39
- it "should properly use arity" do
40
- @filter.run('not nil', 'this', 'thing').should == true
47
+
48
+ it 'properlies use arity' do
49
+ expect(@filter.run('not nil', 'this', 'thing')).to eq(true)
41
50
  end
42
51
  end
43
52
  end
@@ -1,36 +1,41 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  describe Qfill::ListSet do
3
- context "#new" do
4
- context "with no arguments" do
5
- it "should raise ArgumentError" do
6
- expect { Qfill::ListSet.new() }.to raise_error(ArgumentError)
5
+ describe '#new' do
6
+ context 'with no arguments' do
7
+ it 'raises ArgumentError' do
8
+ expect { described_class.new }.to raise_error(ArgumentError)
7
9
  end
8
10
  end
9
- context "with arguments" do
10
- before :each do
11
- @filter = Qfill::Filter.new( -> (object) { object.is_a?(Numeric)} )
11
+
12
+ context 'with arguments' do
13
+ before do
14
+ @filter = Qfill::Filter.new(->(object) { object.is_a?(Numeric) })
12
15
  @origin_queues = [
13
16
  Qfill::List.new(
14
- :name => "High List",
15
- :elements => [1, 2, 3, 'c'],
16
- :filter => @filter),
17
- Qfill::List.new( :name => "Medium List",
18
- :elements => ['e', 'f', 4, 5],
19
- :filter => @filter),
20
- Qfill::List.new( :name => "Low List",
21
- :elements => [7, 8, 'd'],
22
- :filter => @filter)
17
+ name: 'High List',
18
+ elements: [1, 2, 3, 'c'],
19
+ filter: @filter
20
+ ),
21
+ Qfill::List.new(name: 'Medium List',
22
+ elements: ['e', 'f', 4, 5],
23
+ filter: @filter),
24
+ Qfill::List.new(name: 'Low List',
25
+ elements: [7, 8, 'd'],
26
+ filter: @filter)
23
27
  ]
24
28
  end
25
- it "should not raise any errors" do
26
- expect { Qfill::ListSet.new(*@origin_queues) }.to_not raise_error
29
+
30
+ it 'does not raise any errors' do
31
+ expect { described_class.new(*@origin_queues) }.not_to raise_error
27
32
  end
28
- it "should instantiate with name" do
29
- popper = Qfill::ListSet.new(*@origin_queues)
30
- popper.queues.first.elements.should == [1,2,3,'c']
31
- popper.queues.last.elements.should == [7,8,'d']
33
+
34
+ it 'instantiates with name' do
35
+ popper = described_class.new(*@origin_queues)
36
+ expect(popper.queues.first.elements).to eq([1, 2, 3, 'c'])
37
+ expect(popper.queues.last.elements).to eq([7, 8, 'd'])
32
38
  end
33
39
  end
34
40
  end
35
-
36
41
  end