qfill 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +152 -0
- data/Rakefile +1 -0
- data/lib/qfill.rb +13 -0
- data/lib/qfill/filter.rb +18 -0
- data/lib/qfill/list.rb +18 -0
- data/lib/qfill/list_set.rb +29 -0
- data/lib/qfill/manager.rb +91 -0
- data/lib/qfill/origin.rb +19 -0
- data/lib/qfill/popper.rb +96 -0
- data/lib/qfill/pusher.rb +85 -0
- data/lib/qfill/result.rb +116 -0
- data/lib/qfill/version.rb +3 -0
- data/qfill.gemspec +36 -0
- data/spec/qfill/filter_spec.rb +44 -0
- data/spec/qfill/list_set_spec.rb +36 -0
- data/spec/qfill/list_spec.rb +44 -0
- data/spec/qfill/manager_spec.rb +418 -0
- data/spec/qfill/origin_spec.rb +55 -0
- data/spec/qfill/popper_spec.rb +44 -0
- data/spec/qfill/pusher_spec.rb +38 -0
- data/spec/qfill/result_spec.rb +57 -0
- data/spec/qfill_spec.rb +11 -0
- data/spec/spec_helper.rb +48 -0
- metadata +102 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Peter Boling
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
# Qfill - Advanced Queue Tranformations
|
2
|
+
|
3
|
+
This gem takes a dynamic number of queues (arrays) of things, and manages the transformation into a new set of queues,
|
4
|
+
according to a dynamic set of guidelines.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'qfill'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install qfill
|
19
|
+
|
20
|
+
## Usage & List Fill Methodology
|
21
|
+
|
22
|
+
There will be a dynamic number of origination queues each containing a set of snapshots which are grouped together by
|
23
|
+
some matching criteria.
|
24
|
+
There is a Popper which is called to pop the next object from the next origination queue.
|
25
|
+
There is a Filter which is optionally called to validate any object that is popped from the origin.
|
26
|
+
Origin keeps popping until an object is validated for as a result-worthy object.
|
27
|
+
|
28
|
+
Example:
|
29
|
+
|
30
|
+
filter1 = Qfill::Filter.new( -> (object, stuff, stank) { object.is_awesome_enough_to_be_in_results?(stuff, stank) }, stuff, stank)
|
31
|
+
filter2 = Qfill::Filter.new( -> (object, rank, bank) { object.is_awesome_enough_to_be_in_results?(rank, bank) }, rank, bank)
|
32
|
+
|
33
|
+
popper = Qfill::Popper.new(
|
34
|
+
Qfill::Origin.new( :name => "High List",
|
35
|
+
:elements => [Thing1, Thing3],
|
36
|
+
:backfill => "Medium List",
|
37
|
+
:filter => filter1),
|
38
|
+
Qfill::Origin.new( :name => "Medium List",
|
39
|
+
:elements => [Thing2, Thing6],
|
40
|
+
:backfill => "Low List",
|
41
|
+
:filter => filter2),
|
42
|
+
Qfill::Origin.new( :name => "Low List",
|
43
|
+
:elements => [Thing4, Thing5],
|
44
|
+
:backfill => nil,
|
45
|
+
:filter => filter1),
|
46
|
+
)
|
47
|
+
|
48
|
+
Or:
|
49
|
+
|
50
|
+
popper = Qfill::Popper.from_array_of_hashes([
|
51
|
+
{ :name => "High List",
|
52
|
+
:elements => [Thing1, Thing3, Thing7, Thing8, Thing12, Thing15, Thing17],
|
53
|
+
:backfill => "Medium List",
|
54
|
+
:filter => filter1},
|
55
|
+
{ :name => "Medium List",
|
56
|
+
:elements => [Thing2, Thing6, Thing11, Thing 16],
|
57
|
+
:backfill => "Low List",
|
58
|
+
:filter => filter2},
|
59
|
+
{ :name => "Low List",
|
60
|
+
:elements => [Thing4, Thing5, Thing9, Thing10, Thing13, Thing14, Thing18, Thing19, Thing20],
|
61
|
+
:backfill => false,
|
62
|
+
:filter => filter1}
|
63
|
+
])
|
64
|
+
|
65
|
+
There are a dynamic number of result queues that need to be filled with objects from the origination queues.
|
66
|
+
There is a Pusher which is called to add the object from the Popper to the next result queue.
|
67
|
+
A filter can be given to perform additional check to verify that the object should be added to a particular result queue.
|
68
|
+
At least one result queue should be left with no filter, or you risk a result set that is completely empty.
|
69
|
+
A filter_alternate can be given to indicate which alternate result queue objects failing the filter should be placed in.
|
70
|
+
A ratio can be given to indicate the portion of the total results which should go into the result queue.
|
71
|
+
A set of queue ratios can be defined to indicate the rate at which the result queue will be filled from each origin queue.
|
72
|
+
When queue ratios are not given an even split is assumed.
|
73
|
+
|
74
|
+
Example:
|
75
|
+
|
76
|
+
filter3 = Qfill::Filter.new( -> (object, stuff, stank) { object.can_be_best_results?(stuff, stank) }, stuff, stank)
|
77
|
+
|
78
|
+
pusher = Qfill::Pusher.new(
|
79
|
+
Qfill::Result.new( :name => "Best Results",
|
80
|
+
:filter => filter3,
|
81
|
+
:ratio => 0.5,
|
82
|
+
:list_ratios => {
|
83
|
+
"High List" => 0.4,
|
84
|
+
"Medium List" => 0.2,
|
85
|
+
"Low List" => 0.4
|
86
|
+
}
|
87
|
+
),
|
88
|
+
Qfill::Result.new( :name => "More Results",
|
89
|
+
:ratio => 0.5,
|
90
|
+
:list_ratios => {
|
91
|
+
"High List" => 0.2,
|
92
|
+
"Medium List" => 0.4,
|
93
|
+
"Low List" => 0.4
|
94
|
+
}
|
95
|
+
)
|
96
|
+
)
|
97
|
+
|
98
|
+
Or:
|
99
|
+
|
100
|
+
pusher = Qfill::Pusher.from_array_of_hashes([
|
101
|
+
{ :name => "First Result",
|
102
|
+
:ratio => 0.125,
|
103
|
+
:filter => filter3,
|
104
|
+
:ratios => {
|
105
|
+
"High List" => 0.4,
|
106
|
+
"Medium List" => 0.2,
|
107
|
+
"Low List" => 0.4
|
108
|
+
}
|
109
|
+
},
|
110
|
+
{ :name => "Second Result",
|
111
|
+
:ratio => 0.25 },
|
112
|
+
{ :name => "Third Result",
|
113
|
+
:ratio => 0.125 },
|
114
|
+
{ :name => "Fourth Result",
|
115
|
+
:ratio => 0.50 },
|
116
|
+
])
|
117
|
+
|
118
|
+
There is a Manager which maintains state: always knows which queue to pop from next, and which queue to push onto next.
|
119
|
+
|
120
|
+
manager = Qfill::Manager.new(
|
121
|
+
:all_list_max => 40,
|
122
|
+
:popper => popper,
|
123
|
+
:pusher => pusher,
|
124
|
+
)
|
125
|
+
manager.fill!
|
126
|
+
|
127
|
+
For the best usage please look in `spec/qfill/manager_spec.rb` and the other spec files.
|
128
|
+
|
129
|
+
## Contributing
|
130
|
+
|
131
|
+
1. Fork it
|
132
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
133
|
+
3. Commit your changes (`git commit -am ‘Added some feature’`)
|
134
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
135
|
+
5. Make sure to add tests for it. This is important so I don’t break it in a future version unintentionally.
|
136
|
+
6. Create new Pull Request
|
137
|
+
|
138
|
+
## Versioning
|
139
|
+
|
140
|
+
This library aims to adhere to [Semantic Versioning 2.0.0][semver].
|
141
|
+
Violations of this scheme should be reported as bugs. Specifically,
|
142
|
+
if a minor or patch version is released that breaks backward
|
143
|
+
compatibility, a new version should be immediately released that
|
144
|
+
restores compatibility. Breaking changes to the public API will
|
145
|
+
only be introduced with new major versions.
|
146
|
+
As a result of this policy, you can (and should) specify a
|
147
|
+
dependency on this gem using the [Pessimistic Version Constraint][pvc] with two digits of precision.
|
148
|
+
For example:
|
149
|
+
spec.add_dependency 'qfill', '~> 0.0'
|
150
|
+
|
151
|
+
[semver]: http://semver.org/
|
152
|
+
[pvc]: http://docs.rubygems.org/read/chapter/16#page74
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/qfill.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "qfill/version"
|
2
|
+
require "qfill/filter"
|
3
|
+
require "qfill/list"
|
4
|
+
require "qfill/list_set"
|
5
|
+
require "qfill/origin"
|
6
|
+
require "qfill/result"
|
7
|
+
require "qfill/popper"
|
8
|
+
require "qfill/pusher"
|
9
|
+
require "qfill/manager"
|
10
|
+
|
11
|
+
module Qfill
|
12
|
+
# Your code goes here...
|
13
|
+
end
|
data/lib/qfill/filter.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#filter1 = Qfill::Filter.new( -> (object, stuff, stank) { object.is_awesome_enough_to_be_in_results?(stuff, stank) }, stuff, stank)
|
2
|
+
#filter2 = Qfill::Filter.new( -> (object, rank, bank) { object.is_awesome_enough_to_be_in_results?(rank, bank) }, rank, bank)
|
3
|
+
#
|
4
|
+
# Filters are destructive. If an item is filtered from a Result list it is lost, since it has already been popped off the origin list, and won't be coming back
|
5
|
+
module Qfill
|
6
|
+
class Filter
|
7
|
+
attr_accessor :processor, :processor_arguments
|
8
|
+
|
9
|
+
def initialize(proc, *params)
|
10
|
+
@processor = proc
|
11
|
+
@processor_arguments = params
|
12
|
+
end
|
13
|
+
|
14
|
+
def run(*args)
|
15
|
+
self.processor.call(*args, *self.processor_arguments)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/qfill/list.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# This is the base queue class for Origin queues and Result queues.
|
2
|
+
#
|
3
|
+
#Qfill::List.new(:name => "High List",
|
4
|
+
# :elements => [Thing1, Thing3],
|
5
|
+
# :filter => filter1),
|
6
|
+
module Qfill
|
7
|
+
class List
|
8
|
+
attr_accessor :name, :elements, :filter
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
raise ArgumentError, "Missing required option :name for #{self.class}.new()" unless options && options[:name]
|
12
|
+
@name = options[:name]
|
13
|
+
@elements = options[:elements] || []
|
14
|
+
@filter = options[:filter]
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#popper = Qfill::ListSet.new(
|
2
|
+
# Qfill::List.new( :name => "High List",
|
3
|
+
# :elements => [Thing1, Thing3],
|
4
|
+
# :filter => filter1 ) )
|
5
|
+
module Qfill
|
6
|
+
class ListSet
|
7
|
+
|
8
|
+
attr_accessor :queues, :current_index
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
raise ArgumentError, "Missing required arguments for #{self.class}.new(queues)" unless args.length > 0
|
12
|
+
@queues = args
|
13
|
+
@current_index = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](key)
|
17
|
+
return self.queues.find { |queue| queue.name == key }
|
18
|
+
end
|
19
|
+
|
20
|
+
def reset!
|
21
|
+
self.current_index = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_total_elements
|
25
|
+
self.queues.inject(0) {|counter, queue| counter += queue.elements.length}
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
#Qfill::Manager.new(
|
2
|
+
# :all_list_max => 40,
|
3
|
+
# :popper => popper,
|
4
|
+
# :pusher => pusher,
|
5
|
+
#)
|
6
|
+
module Qfill
|
7
|
+
class Manager
|
8
|
+
attr_accessor :all_list_max, :popper, :pusher, :fill_count, :strategy
|
9
|
+
|
10
|
+
STRATEGY_OPTIONS = [:drain, :sample]
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
unless options[:popper] && options[:pusher]
|
14
|
+
raise ArgumentError, "#{self.class}: popper and pusher are required options for #{self.class}.new(options)"
|
15
|
+
end
|
16
|
+
unless options[:strategy].nil? || STRATEGY_OPTIONS.include?(options[:strategy])
|
17
|
+
raise ArgumentError, "#{self.class}: strategy is optional, but must be one of #{STRATEGY_OPTIONS.inspect} if provided"
|
18
|
+
end
|
19
|
+
@popper = options[:popper]
|
20
|
+
@pusher = options[:pusher]
|
21
|
+
# Provided by user, or defaults to the total number of primary elements in popper list set
|
22
|
+
@all_list_max = options[:all_list_max] ? [options[:all_list_max], self.popper.get_primary_elements].min : self.popper.get_primary_elements
|
23
|
+
@fill_count = 0
|
24
|
+
@strategy = options[:strategy] || :drain # or :sample
|
25
|
+
end
|
26
|
+
|
27
|
+
def fill!
|
28
|
+
while !is_full? && !self.popper.primary_empty? && (result = self.pusher.current_list)
|
29
|
+
self.fill_to_ratio!(result, self.all_list_max)
|
30
|
+
self.pusher.set_next_as_current!
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def fill_to_ratio!(result, all_list_max)
|
35
|
+
result.max = Qfill::Result.get_limit_from_max_and_ratio(all_list_max, result.ratio)
|
36
|
+
if !result.list_ratios.empty?
|
37
|
+
self.fill_according_to_list_ratios!(result)
|
38
|
+
else
|
39
|
+
self.fill_up_to_ratio!(result)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def fill_according_to_list_ratios!(result)
|
44
|
+
added = 0
|
45
|
+
if self.strategy == :drain
|
46
|
+
result.list_ratios.each do |list_name, list_ratio|
|
47
|
+
#puts "fill_according_to_list_ratios!, :drain, #{list_name}: Primary remaining => #{self.popper.get_primary_elements}"
|
48
|
+
max_from_list = Qfill::Result.get_limit_from_max_and_ratio(result.max, list_ratio)
|
49
|
+
array_to_push = self.popper.next_objects!(list_name, max_from_list)
|
50
|
+
added = result.push(array_to_push, list_name)
|
51
|
+
end
|
52
|
+
self.fill_count += added
|
53
|
+
elsif self.strategy == :sample
|
54
|
+
while !is_full? && !result.is_full? && !self.popper.totally_empty? && (list_ratio_tuple = result.current_list_ratio)
|
55
|
+
#puts "fill_according_to_list_ratios!, :sample, #{list_ratio_tuple[0]}: Primary remaining => #{self.popper.get_primary_elements}"
|
56
|
+
max_from_list = Qfill::Result.get_limit_from_max_and_ratio(result.max, list_ratio_tuple[1])
|
57
|
+
array_to_push = self.popper.next_objects!(list_ratio_tuple[0], max_from_list)
|
58
|
+
added = result.push(array_to_push, list_ratio_tuple[0])
|
59
|
+
self.fill_count += added
|
60
|
+
result.set_next_as_current!
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def fill_up_to_ratio!(result)
|
66
|
+
ratio = 1.0 / self.popper.primary.length
|
67
|
+
max_from_list = Qfill::Result.get_limit_from_max_and_ratio(result.max, ratio)
|
68
|
+
added = 0
|
69
|
+
if self.strategy == :drain
|
70
|
+
self.popper.primary.each do |queue|
|
71
|
+
#puts "fill_up_to_ratio!, :drain max #{max_from_list}, #{queue.name}: Primary remaining => #{self.popper.get_primary_elements}"
|
72
|
+
array_to_push = self.popper.next_objects!(queue.name, max_from_list)
|
73
|
+
added = result.push(array_to_push, queue.name)
|
74
|
+
end
|
75
|
+
self.fill_count += added
|
76
|
+
elsif self.strategy == :sample
|
77
|
+
while !is_full? && !result.is_full? && !self.popper.totally_empty? && (origin_list = self.popper.current_list)
|
78
|
+
#puts "fill_up_to_ratio!, :sample max #{max_from_list}, #{origin_list.name}: Primary remaining => #{self.popper.get_primary_elements}"
|
79
|
+
array_to_push = self.popper.next_objects!(origin_list.name, max_from_list)
|
80
|
+
added = result.push(array_to_push, origin_list.name)
|
81
|
+
self.fill_count += added
|
82
|
+
self.popper.set_next_as_current!
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def is_full?
|
88
|
+
self.fill_count >= self.all_list_max
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/qfill/origin.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#Qfill::Origin.new(:name => "High List",
|
2
|
+
# :elements => [Thing1, Thing3],
|
3
|
+
# :backfill => "Medium List",
|
4
|
+
# :filter => filter1),
|
5
|
+
module Qfill
|
6
|
+
class Origin < Qfill::List
|
7
|
+
attr_accessor :backfill
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
super(options)
|
11
|
+
@backfill = options[:backfill]
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_backfill?
|
15
|
+
!!self.backfill
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
data/lib/qfill/popper.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
#popper = Qfill::Popper.new(
|
2
|
+
# Qfill::Origin.new( :name => "High List",
|
3
|
+
# :elements => [Thing1, Thing3],
|
4
|
+
# :backfill => "Medium List",
|
5
|
+
# :filter => filter1),
|
6
|
+
# Qfill::Origin.new( :name => "Medium List",
|
7
|
+
# :elements => [Thing2, Thing6],
|
8
|
+
# :backfill => "Low List",
|
9
|
+
# :filter => filter2),
|
10
|
+
# Qfill::Origin.new( :name => "Low List",
|
11
|
+
# :elements => [Thing4, Thing5],
|
12
|
+
# :backfill => nil,
|
13
|
+
# :filter => filter1),
|
14
|
+
#)
|
15
|
+
#
|
16
|
+
#popper = Qfill::Popper.from_array_of_hashes([
|
17
|
+
# { :name => "High List",
|
18
|
+
# :elements => [Thing1, Thing3, Thing7, Thing8, Thing12, Thing15, Thing17],
|
19
|
+
# :backfill => "Medium List",
|
20
|
+
# :filter => filter1},
|
21
|
+
# { :name => "Medium List",
|
22
|
+
# :elements => [Thing2, Thing6, Thing11, Thing 16],
|
23
|
+
# :backfill => "Low List",
|
24
|
+
# :filter => filter2},
|
25
|
+
# { :name => "Low List",
|
26
|
+
# :elements => [Thing4, Thing5, Thing9, Thing10, Thing13, Thing14, Thing18, Thing19, Thing20],
|
27
|
+
# :backfill => nil,
|
28
|
+
# :filter => filter1},
|
29
|
+
#])
|
30
|
+
#
|
31
|
+
# Popper is made up of an array (called queues) of Origin objects.
|
32
|
+
module Qfill
|
33
|
+
class Popper < Qfill::ListSet
|
34
|
+
|
35
|
+
attr_accessor :total_elements
|
36
|
+
|
37
|
+
def initialize(*args)
|
38
|
+
super(*args)
|
39
|
+
@total_elements = get_total_elements
|
40
|
+
end
|
41
|
+
|
42
|
+
def primary
|
43
|
+
@primary ||= self.queues.select {|x| x.backfill != true}
|
44
|
+
end
|
45
|
+
|
46
|
+
def current_list
|
47
|
+
self.primary[self.current_index]
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_next_as_current!
|
51
|
+
next_index = self.current_index + 1
|
52
|
+
if (next_index) == self.primary.length
|
53
|
+
# If we have iterated through all the queues, then we reset
|
54
|
+
self.reset!
|
55
|
+
else
|
56
|
+
self.current_index = next_index
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def next_objects!(list_name, n = 1)
|
61
|
+
origin_list = self[list_name]
|
62
|
+
if origin_list.elements.length >= n
|
63
|
+
return origin_list.elements.pop(n)
|
64
|
+
else
|
65
|
+
result = origin_list.elements.pop(n)
|
66
|
+
while result.length < n && origin_list.has_backfill?
|
67
|
+
secondary_list = self[origin_list.backfill]
|
68
|
+
remaining = n - result.length
|
69
|
+
result += secondary_list.elements.pop(remaining)
|
70
|
+
origin_list = secondary_list
|
71
|
+
end
|
72
|
+
return result
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.from_array_of_hashes(array_of_hashes = [])
|
77
|
+
args = array_of_hashes.map do |hash|
|
78
|
+
Qfill::Origin.new(hash)
|
79
|
+
end
|
80
|
+
Qfill::Popper.new(*args)
|
81
|
+
end
|
82
|
+
|
83
|
+
def primary_empty?
|
84
|
+
self.get_primary_elements == 0
|
85
|
+
end
|
86
|
+
|
87
|
+
def totally_empty?
|
88
|
+
self.get_total_elements == 0
|
89
|
+
end
|
90
|
+
|
91
|
+
def get_primary_elements
|
92
|
+
self.primary.inject(0) {|counter, queue| counter += queue.elements.length}
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|