garoupa 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +179 -0
- data/Rakefile +6 -0
- data/garoupa.gemspec +25 -0
- data/lib/garoupa/version.rb +3 -0
- data/lib/garoupa.rb +122 -0
- data/spec/garoupa_spec.rb +181 -0
- data/spec/spec_helper.rb +6 -0
- metadata +113 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f05bd1da55dd8ace5e6f7417b7892bc250e062ac
|
4
|
+
data.tar.gz: 2944585ad2dee367cdc5ffbd258f0b1ef12a37d8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 260e13489af06ad5f8044f2a4cc01d74390276f990199ca780fb1f300aa12b4924e37430a0628109ffd2567a155ad751af3c36d281fda1ff8dc975566f0e5aee
|
7
|
+
data.tar.gz: c7676caf8c9e86175017aa916b369ad1916e9e58677614836d212752e291ef2c54f3deff52c96e459b59acb3a1210dd77ab431931f16e85300c26c90f72c6ad6
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Torey Hickman
|
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,179 @@
|
|
1
|
+
# Garoupa
|
2
|
+
|
3
|
+
![Travis CI Build Status](https://api.travis-ci.org/toreyhickman/garoupa.svg?branch=master)
|
4
|
+
|
5
|
+
Garoupa was written to facilitate the assignment of groups at Dev Bootcamp where students are assigned to weekly groups. It should be generalizable to other contexts, taking an item list and returning a new Garoupa object with the assigned groups. Options allow for specifying a target group size, a maximum difference in group sizes if the number of list items is not evenly divisible by the target group size, and providing past groups so that list items can be grouped with new groupmates.
|
6
|
+
|
7
|
+
[Garoupa is apparently a Portugese name, from which the name of the grouper fish is believed to be derived](http://en.wikipedia.org/wiki/Grouper#Name_origin). The things you learn when the desired name for your gem is taken ...
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'garoupa'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install garoupa
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
The Garoupa library consists of one class: `Garoupa`. The class has two publicly defined class methods:
|
26
|
+
|
27
|
+
- `.make_groups`
|
28
|
+
- `.past_groupmates`
|
29
|
+
|
30
|
+
### `.make_groups`
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
groups = Garoupa.make_groups [:a, :b, :c, :d, :e, :f, :g, :h]
|
34
|
+
|
35
|
+
=> #<Garoupa:0x007fb523552938
|
36
|
+
@groups=[[:a, :h, :b, :g], [:f, :d, :c, :e]],
|
37
|
+
@list=[:a, :b, :c, :d, :e, :f, :g, :h],
|
38
|
+
@past_groupmates={:a=>[], :b=>[], :c=>[],
|
39
|
+
:d=>[], :e=>[], :f=>[],
|
40
|
+
:g=>[], :h=>[]}
|
41
|
+
>
|
42
|
+
```
|
43
|
+
*Figure 1*. Making groups from a list.
|
44
|
+
|
45
|
+
`.make_groups` takes a list of arguments (i.e., an array) and returns the groups within an instance of the `Garoupa` class (see Figure 1).
|
46
|
+
|
47
|
+
The `.make_groups` methods also accepts an options hash where you can specify
|
48
|
+
|
49
|
+
- target group size
|
50
|
+
- a max difference in group sizes
|
51
|
+
- past groups
|
52
|
+
|
53
|
+
#### :target_size
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
list = [:a, :b, :c, :d, :e, :f, :g, :h]
|
57
|
+
|
58
|
+
groups = Garoupa.make_groups list, { target_size: 3 }
|
59
|
+
=> #<Garoupa:0x007fb523572f80
|
60
|
+
@groups=[[:c, :d, :h], [:f, :a, :e], [:b, :g]],
|
61
|
+
@list=[:a, :b, :c, :d, :e, :f, :g, :h],
|
62
|
+
@past_groupmates={:a=>[], ... }>
|
63
|
+
```
|
64
|
+
*Figure 2*. Making groups with a target size.
|
65
|
+
|
66
|
+
The `Garoupa` class has a constant, `DEFAULT_GROUP_SIZE`, that is used to determine the size of each group. This can be overwritten by providing a target size when calling `.make_groups` (see Figure 2). As many groups as possible will be make with the target group size. If the number of list items is not evenly divisible by the target size, the last group will have less members than the target size. This is evident in Figure 2 where the eight list items were divided into two groups of three, the target size, and one group of two.
|
67
|
+
|
68
|
+
#### :max_difference
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
list = [:a, :b, :c, :d, :e, :f, :g, :h, :i, :j]
|
72
|
+
|
73
|
+
groups = Garoupa.make_groups list, { target_size: 3, max_difference: 1 }
|
74
|
+
=> #<Garoupa:0x007fb5235900a8
|
75
|
+
@groups=[[:d, :g, :c, :b], [:f, :a, :e], [:i, :j, :h]],
|
76
|
+
@list=[:a, :b, :c, :d, :e, :f, :g, :h, :i, :j],
|
77
|
+
@past_groupmates={:a=>[], ... }>
|
78
|
+
```
|
79
|
+
|
80
|
+
*Figure 3*. Making groups with a target size and maximum difference in group size.
|
81
|
+
|
82
|
+
If the groups will not be even, a maximum difference in group size can be specified. In Figure 3, the target size is three, but with ten items in the list, the groups will include three groups of three and one group of one. By specifying a maximum difference of one, the last groups will be dispersed into the three groups of three, resulting in one group of four and two groups of three.
|
83
|
+
|
84
|
+
#### :past_groups
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
list = [:a, :b, :c, :d]
|
88
|
+
past_groups = [[:a, :b],[:a, :c]]
|
89
|
+
|
90
|
+
groups = Garoupa.make_groups list, { target_size: 2, past_groups: past_groups }
|
91
|
+
=> #<Garoupa:0x007f86c4273088
|
92
|
+
@groups=[[:a, :d], [:b, :c]],
|
93
|
+
@list=[:a, :b, :c, :d],
|
94
|
+
@past_groupmates={:a=>[:b, :c], :b=>[:a], :c=>[:a], :d=>[]}>
|
95
|
+
```
|
96
|
+
|
97
|
+
*Figure 4*. Making groups based on past groups.
|
98
|
+
|
99
|
+
When we pass in the optional past groups, `.make_groups` will try to place items in groups with new groupmates. In the `past_groups` defined in Figure 4, Item `:a` has already been grouped with Items `:b` and `:c`. Item `:a` has not been grouped with Item `:d`; therefore, the ideal groups would be Items `:a` and `:d` together and Items `:b` and `:c` together. In these groups, no item would have a repear groupmate.
|
100
|
+
|
101
|
+
`Garoupa` attempts to make groups with no repeat pairs by calculating the past groupmates for each list item and then placing the items with the most past groupmates into groups first. Essentially, trying to place items with more constraints before items with less constraints.
|
102
|
+
|
103
|
+
Certainly, calculating all possible groupings and selecting one without any repeat pairs is possible, but it becomes impractical with larger group sizes or when repeat groupmates are inevitable.
|
104
|
+
|
105
|
+
### `.past_groupmates`
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
list = [:a, :b, :c, :d]
|
109
|
+
past_groups = [[:a, :b],[:a, :c]]
|
110
|
+
|
111
|
+
Garoupa.past_groupmates list, past_groups
|
112
|
+
=> {:a=>[:b, :c], :b=>[:a], :c=>[:a], :d=>[]}
|
113
|
+
```
|
114
|
+
|
115
|
+
*Figure 5*. Finding the past groupmates for list items.
|
116
|
+
|
117
|
+
The second class method, `.past_groupmates`, takes a list and past groups and returns a map of the list items and their past_groupmates, as seen in Figure 5.
|
118
|
+
|
119
|
+
### `Garoupa` instances
|
120
|
+
|
121
|
+
Instances of the `Garoupa` class are instantiated with groups, a list of items, and past groupmates. New instances are returned from `Garoupa.make_groups`, but they can also be made by providing all three required arguments. Each argument is saved as an instance variable and retrievable through getter methods.
|
122
|
+
|
123
|
+
Additionally, there are three public instance methods:
|
124
|
+
|
125
|
+
- `#repeat_pairs`
|
126
|
+
- `#to_json`
|
127
|
+
- `#to_s`
|
128
|
+
|
129
|
+
#### `#repeat_pairs`
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
list = [:a, :b, :c, :d]
|
133
|
+
past_groups = [[:a, :b],[:a, :c], [:a, :d]]
|
134
|
+
|
135
|
+
groups = Garoupa.make_groups list, target_size: 2, past_groups: past_groups
|
136
|
+
=> #<Garoupa:0x007f86c414e4a0
|
137
|
+
@groups=[[:a, :d], [:c, :b]],
|
138
|
+
@list=[:a, :b, :c, :d],
|
139
|
+
@past_groupmates={:a=>[:b, :c, :d], :b=>[:a], :c=>[:a], :d=>[:a]}>
|
140
|
+
|
141
|
+
groups.repeat_pairs
|
142
|
+
=> {:a=>[:d], :b=>[], :c=>[], :d=>[:a]}
|
143
|
+
```
|
144
|
+
|
145
|
+
*Figure 6*. Repeat pairs reported for a groups.
|
146
|
+
|
147
|
+
An instance of the `Garoupa` class is able to report which list items are in groups with repeat pairs. `#repeat_pairs` returns a hash with each list item mapped to the repeat pairs in its group (see Figure 6).
|
148
|
+
|
149
|
+
#### `#to_json`
|
150
|
+
|
151
|
+
The `#to_json` method returns a JSON formatted string representing the `Garoupa` instance. It includes, the groups, list, past groupmates, and repeat pairs.
|
152
|
+
|
153
|
+
#### `#to_s`
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
list = [:a, :b, :c, :d]
|
157
|
+
past_groups = [[:a, :b],[:a, :c]]
|
158
|
+
|
159
|
+
groups = Garoupa.make_groups list, target_size: 2, past_groups: past_groups
|
160
|
+
=> #<Garoupa:0x007f86c414e4a0
|
161
|
+
@groups=[[:a, :d], [:b, :c]],
|
162
|
+
@list=[:a, :b, :c, :d],
|
163
|
+
@past_groupmates={:a=>[:b, :c], :b=>[:a], :c=>[:a], :d=>[]}>
|
164
|
+
|
165
|
+
puts groups
|
166
|
+
1. a, d
|
167
|
+
2. b, c
|
168
|
+
=> nil
|
169
|
+
```
|
170
|
+
|
171
|
+
*Figure 7*. A `Garoupa` is printed as a numbered list.
|
172
|
+
|
173
|
+
|
174
|
+
The `#to_s` methods returns a numbered list of the groups (see Figure 7).
|
175
|
+
|
176
|
+
|
177
|
+
## Contributing
|
178
|
+
|
179
|
+
If you'd like to help improve Garoupa by adding a new feature, please fork the repository and submit a pull request for your feature branch. Also, please report any bugs that you find.
|
data/Rakefile
ADDED
data/garoupa.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'garoupa/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "garoupa"
|
8
|
+
spec.version = Garoupa::VERSION
|
9
|
+
spec.authors = ["Torey Hickman"]
|
10
|
+
spec.email = ["torey@toreyhickman.com"]
|
11
|
+
spec.summary = %q{Make groups from a list}
|
12
|
+
spec.homepage = "https://github.com/toreyhickman/garoupa"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
21
|
+
spec.add_development_dependency "rake"
|
22
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
23
|
+
|
24
|
+
spec.add_dependency "json"
|
25
|
+
end
|
data/lib/garoupa.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require "garoupa/version"
|
2
|
+
|
3
|
+
class Garoupa
|
4
|
+
|
5
|
+
DEFAULT_GROUP_SIZE = 4
|
6
|
+
|
7
|
+
def self.make_groups(list, options = {})
|
8
|
+
list_items_past_groupmates = past_groupmates(list, options[:past_groups])
|
9
|
+
group_structure = make_empty_group_structure(list.size, options[:target_size])
|
10
|
+
corrected_group_structure = correct_for_group_size_difference(group_structure, options[:max_difference])
|
11
|
+
sorted_list = sort(list, list_items_past_groupmates)
|
12
|
+
groups = fill_group_structure(group_structure, sorted_list, list_items_past_groupmates)
|
13
|
+
|
14
|
+
self.new(groups, list, list_items_past_groupmates)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.past_groupmates(list, past_groups)
|
18
|
+
list.each_with_object( Hash.new ) do |list_item, previous_pairs|
|
19
|
+
previous_pairs[list_item] = past_groupmates_for(list_item, past_groups)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
attr_reader :groups, :list, :past_groupmates
|
25
|
+
|
26
|
+
def initialize(groups, list, past_groupmates)
|
27
|
+
@groups = groups
|
28
|
+
@list = list
|
29
|
+
@past_groupmates = past_groupmates
|
30
|
+
end
|
31
|
+
|
32
|
+
def repeat_pairs
|
33
|
+
list.each_with_object( Hash.new ) do |list_item, repeat_pairs|
|
34
|
+
repeat_pairs[list_item] = group_for(list_item) & past_groupmates[list_item]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_json
|
39
|
+
{ :groups => groups,
|
40
|
+
:list => list,
|
41
|
+
:past_groupmates => past_groupmates,
|
42
|
+
:repeat_pairs => repeat_pairs }.to_json
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
groups.map.with_index(1) { |group, index| "#{index}. " + group.join(", ") }.join("\n")
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def self.divide_list(list, group_size = nil)
|
51
|
+
list.each_slice(group_size || DEFAULT_GROUP_SIZE).to_a
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.correct_for_group_size_difference(groups, max_difference = nil)
|
55
|
+
return groups unless max_difference
|
56
|
+
|
57
|
+
if difference_in_group_sizes(groups) > max_difference
|
58
|
+
groups = disperse_last_group(groups)
|
59
|
+
end
|
60
|
+
groups
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.difference_in_group_sizes(groups)
|
64
|
+
sizes = groups.map(&:size)
|
65
|
+
sizes.max - sizes.min
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.disperse_last_group(groups)
|
69
|
+
last_group = groups.pop
|
70
|
+
|
71
|
+
last_group.each_with_index do |element, index|
|
72
|
+
groups[index] << element
|
73
|
+
end
|
74
|
+
|
75
|
+
groups
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.make_empty_group_structure(list_size, target_size = nil)
|
79
|
+
empty_list = Array.new(list_size) { nil }
|
80
|
+
empty_groups = divide_list(empty_list, target_size)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.past_groupmates_for(element, past_groups = nil)
|
84
|
+
return [] unless past_groups
|
85
|
+
|
86
|
+
past_groups = past_groups.select { |group| group.include? element }
|
87
|
+
past_groupmates = past_groups.flatten.uniq - [element]
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.fill_group_structure(group_structure, list, previous_pairs = {})
|
91
|
+
list.each do |list_item|
|
92
|
+
place_in_best_group(list_item, group_structure, previous_pairs)
|
93
|
+
end
|
94
|
+
|
95
|
+
group_structure
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.place_in_best_group(list_item, group_structure, previous_pairs)
|
99
|
+
available_groups = group_structure.select { |group| group.include? nil }
|
100
|
+
group_with_least_repeats = available_groups.min_by { |group| group & previous_pairs[list_item] }
|
101
|
+
|
102
|
+
index_of_nil = group_with_least_repeats.index nil
|
103
|
+
group_with_least_repeats[index_of_nil] = list_item
|
104
|
+
|
105
|
+
return nil
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.sort(list, past_groupmates = nil)
|
109
|
+
return list.shuffle unless past_groupmates
|
110
|
+
|
111
|
+
list.shuffle.sort do |item_a, item_b|
|
112
|
+
item_a_number_of_past_groupmates = past_groupmates.fetch(item_a, []).size
|
113
|
+
item_b_number_of_past_groupmates = past_groupmates.fetch(item_b, []).size
|
114
|
+
|
115
|
+
item_b_number_of_past_groupmates <=> item_a_number_of_past_groupmates
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def group_for(list_item)
|
120
|
+
groups.find { |group| group.include? list_item }
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Garoupa do
|
4
|
+
|
5
|
+
let(:long_list) { [:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l] }
|
6
|
+
let(:garoupa_no_options) { Garoupa.make_groups(long_list) }
|
7
|
+
|
8
|
+
let(:garoupa) { Garoupa.new(groups, list, past_groupmates) }
|
9
|
+
let(:groups) { [[:a, :b], [:c, :d]] }
|
10
|
+
let(:list) { [:a, :b, :c, :d] }
|
11
|
+
let(:past_groupmates) do
|
12
|
+
{ :a => [:b, :c, :d],
|
13
|
+
:b => [:a],
|
14
|
+
:c => [],
|
15
|
+
:d => [] }
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
describe ".make_groups" do
|
20
|
+
it "returns a Groupa object" do
|
21
|
+
expect(garoupa_no_options).to be_instance_of Garoupa
|
22
|
+
end
|
23
|
+
|
24
|
+
it "places each list item into one group" do
|
25
|
+
list_items_in_groups = garoupa_no_options.groups.flatten
|
26
|
+
|
27
|
+
expect(list_items_in_groups - long_list).to be_empty
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "group_sizes" do
|
31
|
+
context "no target size given" do
|
32
|
+
it "attemtps to make groups of the default group size" do
|
33
|
+
|
34
|
+
expect(garoupa_no_options.groups.first.size).to eq Garoupa::DEFAULT_GROUP_SIZE
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "target size given" do
|
39
|
+
context "list size evenly divisible by target size" do
|
40
|
+
it "makes groups of a specified size" do
|
41
|
+
options = { target_size: 3 }
|
42
|
+
groups = Garoupa.make_groups(long_list, options)
|
43
|
+
groups_sizes = groups.groups.map(&:size)
|
44
|
+
|
45
|
+
expect(groups_sizes.all? { |s| s == 3 }).to be true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "list size not evenly divisible by target size" do
|
50
|
+
context "no max size difference specified" do
|
51
|
+
it "has one group with a different size" do
|
52
|
+
options = { target_size: 5 }
|
53
|
+
groups = Garoupa.make_groups(long_list, options)
|
54
|
+
groups_sizes = groups.groups.map(&:size)
|
55
|
+
|
56
|
+
expect(groups_sizes).to match_array [5, 5, 2]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "max size difference specified" do
|
61
|
+
context "max size difference not exceeded" do
|
62
|
+
it "leaves the smaller group" do
|
63
|
+
options = { target_size: 5, max_difference: 3 }
|
64
|
+
groups = Garoupa.make_groups(long_list, options)
|
65
|
+
groups_sizes = groups.groups.map(&:size)
|
66
|
+
|
67
|
+
expect(groups_sizes).to match_array [5, 5, 2]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
context "max size difference exceeded" do
|
71
|
+
it "disperses the smaller group" do
|
72
|
+
options = { target_size: 5, max_difference: 2 }
|
73
|
+
groups = Garoupa.make_groups(long_list, options)
|
74
|
+
groups_sizes = groups.groups.map(&:size)
|
75
|
+
|
76
|
+
expect(groups_sizes).to match_array [6, 6]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "group make up" do
|
85
|
+
context "no past groups provided" do
|
86
|
+
it "divides shuffled list into groups" do
|
87
|
+
allow(long_list).to receive(:shuffle) do
|
88
|
+
[:a, :l, :c, :k, :e, :j, :g, :h, :i, :f, :d, :b]
|
89
|
+
end
|
90
|
+
expected_groups = [[:a, :l, :c, :k], [:e, :j, :g, :h], [:i, :f, :d, :b]]
|
91
|
+
|
92
|
+
expect(garoupa_no_options.groups).to match_array expected_groups
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "past groups provided" do
|
97
|
+
let(:past_groups) { [[:a, :b], [:a, :c], [:a, :d], [:a, :e], [:a, :f], [:a, :g], [:a, :h], [:a, :i]] }
|
98
|
+
|
99
|
+
it "attempts to make groups with minimal repeats" do
|
100
|
+
allow(long_list).to receive(:shuffle) do
|
101
|
+
[:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l]
|
102
|
+
end
|
103
|
+
|
104
|
+
options = { past_groups: past_groups }
|
105
|
+
groups = Garoupa.make_groups(long_list, options).groups
|
106
|
+
|
107
|
+
a_group = groups.find { |group| group.include? :a }
|
108
|
+
expect(a_group).to match_array [:a, :j, :k, :l]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe ".past_groupmates" do
|
115
|
+
let(:list) { [:a, :b, :c, :d] }
|
116
|
+
let(:past_groups) { [[:a, :b], [:a, :b], [:a, :c]] }
|
117
|
+
|
118
|
+
it "maps list items to their unique previous groupmates" do
|
119
|
+
past_group_map = Garoupa.past_groupmates(list, past_groups)
|
120
|
+
|
121
|
+
expect(past_group_map[:a]).to match_array [:b, :c]
|
122
|
+
expect(past_group_map[:b]).to match_array [:a]
|
123
|
+
expect(past_group_map[:c]).to match_array [:a]
|
124
|
+
expect(past_group_map[:d]).to be_empty
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "accessor methods" do
|
129
|
+
let(:groups) { :groups_argument }
|
130
|
+
let(:list) { :list_argument }
|
131
|
+
let(:past_groupmates) { :past_groupmates_argument }
|
132
|
+
|
133
|
+
let(:garoupa) { Garoupa.new(groups, list, past_groupmates) }
|
134
|
+
|
135
|
+
it "returns the list" do
|
136
|
+
expect(garoupa.list).to eq list
|
137
|
+
end
|
138
|
+
|
139
|
+
it "returns the groups" do
|
140
|
+
expect(garoupa.groups).to eq groups
|
141
|
+
end
|
142
|
+
|
143
|
+
it "returns the past groupmates" do
|
144
|
+
expect(garoupa.past_groupmates).to eq past_groupmates
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "#repeat_pairs" do
|
149
|
+
it "returns the repeat pairs for each list item" do
|
150
|
+
expected_repeat_pairs = { :a => [:b],
|
151
|
+
:b => [:a],
|
152
|
+
:c => [],
|
153
|
+
:d => [] }
|
154
|
+
|
155
|
+
expect(garoupa.repeat_pairs).to eq expected_repeat_pairs
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "#to_json" do
|
160
|
+
it "returns a JSON formatted string" do
|
161
|
+
expect { JSON.parse(garoupa.to_json) }.to_not raise_error
|
162
|
+
end
|
163
|
+
|
164
|
+
it "includes groups, list, past groupmates, and repeat pairs" do
|
165
|
+
expected_json = { :groups => garoupa.groups,
|
166
|
+
:list => garoupa.list,
|
167
|
+
:past_groupmates => garoupa.past_groupmates,
|
168
|
+
:repeat_pairs => garoupa.repeat_pairs }.to_json
|
169
|
+
|
170
|
+
expect(garoupa.to_json).to eq expected_json
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
describe "#to_s" do
|
175
|
+
it "returns a numbered list of the groups" do
|
176
|
+
expected_string = "1. a, b\n2. c, d"
|
177
|
+
|
178
|
+
expect(garoupa.to_s).to eq expected_string
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: garoupa
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Torey Hickman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-09-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: json
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- torey@toreyhickman.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- .travis.yml
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- garoupa.gemspec
|
83
|
+
- lib/garoupa.rb
|
84
|
+
- lib/garoupa/version.rb
|
85
|
+
- spec/garoupa_spec.rb
|
86
|
+
- spec/spec_helper.rb
|
87
|
+
homepage: https://github.com/toreyhickman/garoupa
|
88
|
+
licenses:
|
89
|
+
- MIT
|
90
|
+
metadata: {}
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 2.2.2
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: Make groups from a list
|
111
|
+
test_files:
|
112
|
+
- spec/garoupa_spec.rb
|
113
|
+
- spec/spec_helper.rb
|