tcollier-sommelier 0.1.0
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 +2 -0
- data/.rspec +1 -0
- data/CHANGELOG.txt +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +53 -0
- data/LICENSE +22 -0
- data/README.md +66 -0
- data/Rakefile +6 -0
- data/lib/sommelier.rb +61 -0
- data/lib/sommelier/descending_insertion_sort_array.rb +22 -0
- data/lib/sommelier/match_catalog.rb +75 -0
- data/lib/sommelier/match_maker.rb +43 -0
- data/lib/sommelier/match_maker/decider.rb +39 -0
- data/lib/sommelier/match_maker/generator.rb +37 -0
- data/lib/sommelier/preference.rb +18 -0
- data/lib/sommelier/version.rb +3 -0
- data/sommelier.gemspec +25 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7aec6ab221e7b53607609c6a5f7b3f84945461bb
|
4
|
+
data.tar.gz: a759cd1e83e4933366ae5b28d2fffb806d5c8ace
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 43f4525771fec89eb6fc7703eb0503d15439c8c78c4ae5c7867eadcaae80c28ad208cc04818074b51908231de9a06068d58b1a83f0df9c21d06b8823d54c072e
|
7
|
+
data.tar.gz: c39b125857e32674f126970486399b3d9d98d47b44ab2960aade9c7cbcc920db8d6ab71a438094d1881a5ff29149c3ecfed166ae7c65d0b6b9ad306f9828b7af
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require 'spec_helper'
|
data/CHANGELOG.txt
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
tcollier-sommelier (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
byebug (10.0.2)
|
10
|
+
coderay (1.1.2)
|
11
|
+
diff-lcs (1.3)
|
12
|
+
docile (1.3.1)
|
13
|
+
json (2.1.0)
|
14
|
+
method_source (0.9.0)
|
15
|
+
pry (0.11.3)
|
16
|
+
coderay (~> 1.1.0)
|
17
|
+
method_source (~> 0.9.0)
|
18
|
+
pry-byebug (3.6.0)
|
19
|
+
byebug (~> 10.0)
|
20
|
+
pry (~> 0.10)
|
21
|
+
rake (10.5.0)
|
22
|
+
rspec (3.7.0)
|
23
|
+
rspec-core (~> 3.7.0)
|
24
|
+
rspec-expectations (~> 3.7.0)
|
25
|
+
rspec-mocks (~> 3.7.0)
|
26
|
+
rspec-core (3.7.1)
|
27
|
+
rspec-support (~> 3.7.0)
|
28
|
+
rspec-expectations (3.7.0)
|
29
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
30
|
+
rspec-support (~> 3.7.0)
|
31
|
+
rspec-mocks (3.7.0)
|
32
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
33
|
+
rspec-support (~> 3.7.0)
|
34
|
+
rspec-support (3.7.1)
|
35
|
+
simplecov (0.16.1)
|
36
|
+
docile (~> 1.1)
|
37
|
+
json (>= 1.8, < 3)
|
38
|
+
simplecov-html (~> 0.10.0)
|
39
|
+
simplecov-html (0.10.2)
|
40
|
+
|
41
|
+
PLATFORMS
|
42
|
+
ruby
|
43
|
+
|
44
|
+
DEPENDENCIES
|
45
|
+
bundler (~> 1.12)
|
46
|
+
pry-byebug (~> 3.3)
|
47
|
+
rake (~> 10.0)
|
48
|
+
rspec (~> 3.4)
|
49
|
+
simplecov (~> 0.16)
|
50
|
+
tcollier-sommelier!
|
51
|
+
|
52
|
+
BUNDLED WITH
|
53
|
+
1.16.1
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Tom Collier
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# Sommelier
|
2
|
+
|
3
|
+
Sommelier provides a solution to a variant of the Stable Marriage problem in
|
4
|
+
mathematics. In this gem, the classical suitors are replaced with dishes from a
|
5
|
+
restaurant menu and the brides-to-be are wines.
|
6
|
+
|
7
|
+
## Stable Marriage Problem
|
8
|
+
|
9
|
+
[](https://www.youtube.com/watch?v=Qcv1IqHWAzg)
|
10
|
+
|
11
|
+
The [Stable Marriage Problem](https://en.wikipedia.org/wiki/Stable_marriage_problem)
|
12
|
+
is a mathematical problem that attempts to uniquely match a set of _N_ items
|
13
|
+
(classically male suitors) with another set of _N_ items (classically females the
|
14
|
+
suitors wish to marry). The final matches are referred to as proposals.
|
15
|
+
|
16
|
+
## Gale-Shapely Algorithm
|
17
|
+
|
18
|
+
This gem provides a variant of the [Gale-Shapely algorithm](https://en.wikipedia.org/wiki/Stable_marriage_problem#Solution).
|
19
|
+
Gale-Shapely guarantees a complete matching (i.e. every suitor is paired with
|
20
|
+
exactly one female and vice versa). Though this algorithm assumes every member
|
21
|
+
of either group has a complete ranking of the other group. For large populations,
|
22
|
+
this is not always practical.
|
23
|
+
|
24
|
+
## Variation
|
25
|
+
|
26
|
+
The algorithm applied here has 2 primary differences from Gale-Shapely
|
27
|
+
|
28
|
+
1. Neither suitors nor suitees need to have a complete ranking of the other set.
|
29
|
+
2. The rankings are determined by a symmetrical match score. For example, the match
|
30
|
+
score for Alice and Steve is the same as for Steve and Alice. Though scores are
|
31
|
+
relative, so Alice's match score for Steve may be her highest scoring match, but
|
32
|
+
that same score may only be the fifth highest scoring match for Steve.
|
33
|
+
|
34
|
+
Because of these differences, not every suitor or suitee is guaranteed to have
|
35
|
+
a proposal. Swapping the suitor and suitee sets can have dramatic effects on the
|
36
|
+
final set of proposals.
|
37
|
+
|
38
|
+
## Usage
|
39
|
+
|
40
|
+
### Installation
|
41
|
+
|
42
|
+
```bash
|
43
|
+
gem install tcollier-sommelier
|
44
|
+
```
|
45
|
+
|
46
|
+
### Example
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
require 'sommelier'
|
50
|
+
sommelier = Sommelier.new
|
51
|
+
sommelier.add_match('Asparagus', 'Pinot Noir', 0.366)
|
52
|
+
sommelier.add_match('Asparagus', 'Sauvignon Blanc', 0.453)
|
53
|
+
sommelier.add_match('Asparagus', 'Chardonnay', 0.245)
|
54
|
+
sommelier.add_match('Tofu', 'Rose', 0.486)
|
55
|
+
sommelier.add_match('Tofu', 'Sauvignon Blanc', 0.304)
|
56
|
+
sommelier.add_match('Eggplant', 'Sauvignon Blanc', 0.299)
|
57
|
+
sommelier.add_match('Salmon', 'Sauvignon Blanc', 0.602)
|
58
|
+
puts sommelier.pairings
|
59
|
+
# {
|
60
|
+
# "Salmon" => "Sauvignon Blanc",
|
61
|
+
# "Tofu" => "Rose",
|
62
|
+
# "Asparagus" => "Pinot Noir"
|
63
|
+
# }
|
64
|
+
|
65
|
+
# Note: neither "Eggplant" nor "Chardonnay" were matched in the pairings map
|
66
|
+
```
|
data/Rakefile
ADDED
data/lib/sommelier.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative 'sommelier/match_maker'
|
2
|
+
require_relative 'sommelier/match_catalog'
|
3
|
+
|
4
|
+
# Pair off members of one set with members of another, optimizing for the total
|
5
|
+
# match score of all pairings. This is similar to how a sommelier may provide
|
6
|
+
# a complete set of wine pairings for each dish on a menu.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# sommelier = Sommelier.new
|
11
|
+
# sommelier.add_match('Asparagus', 'Pinot Noir', 0.366)
|
12
|
+
# sommelier.add_match('Asparagus', 'Sauvignon Blanc', 0.453)
|
13
|
+
# sommelier.add_match('Asparagus', 'Chardonnay', 0.245)
|
14
|
+
# sommelier.add_match('Tofu', 'Rose', 0.486)
|
15
|
+
# sommelier.add_match('Tofu', 'Sauvignon Blanc', 0.304)
|
16
|
+
# sommelier.add_match('Eggplant', 'Sauvignon Blanc', 0.299)
|
17
|
+
# sommelier.add_match('Salmon', 'Sauvignon Blanc', 0.602)
|
18
|
+
# puts sommelier.pairings
|
19
|
+
# # {
|
20
|
+
# # "Salmon" => "Sauvignon Blanc",
|
21
|
+
# # "Tofu" => "Rose",
|
22
|
+
# # "Asparagus" => "Pinot Noir"
|
23
|
+
# # }
|
24
|
+
#
|
25
|
+
# # Note: neither "Eggplant" nor "Chardonnay" were matched in the pairings map
|
26
|
+
#
|
27
|
+
class Sommelier
|
28
|
+
def initialize
|
29
|
+
@match_catalog = MatchCatalog.new
|
30
|
+
end
|
31
|
+
|
32
|
+
# Include a potential pairing between the dish and wine. The pairing has a
|
33
|
+
# score (a higher score means a better pairing) that is used the generate
|
34
|
+
# preferences of dish => wines and wine => dishes. These preferences are used
|
35
|
+
# in a variant of the Gale-Shapely algorithm.
|
36
|
+
#
|
37
|
+
# Since the dishes are acting as the suitors (in Gale-Shapely terminology),
|
38
|
+
# they tend to get better preferred wines than vice versa. However, since a
|
39
|
+
# pairing is symmetrical, passing the wines in as the dish and the dishes in
|
40
|
+
# as the wine will work, but yield potentially different pairings.
|
41
|
+
#
|
42
|
+
# @param dish [Object] the proposal maker in the Gale-Shapely algorithm.
|
43
|
+
# @param wine [Object] the proposal acceptor/rejector in the Gale-Shapely
|
44
|
+
# algorithm.
|
45
|
+
# @param score [Numeric] a numeric representation of the strength of the
|
46
|
+
# pairing. A higher score means a better paring.
|
47
|
+
def add_match(dish, wine, score)
|
48
|
+
match_catalog.add(dish, wine, score)
|
49
|
+
end
|
50
|
+
|
51
|
+
# A map with dishes as keys and wines as objects
|
52
|
+
#
|
53
|
+
# @return [Hash<Object, Object>] the final matching between dishes and wines.
|
54
|
+
def pairings
|
55
|
+
MatchMaker.new(match_catalog).pairings
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
attr_reader :match_catalog
|
61
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Sommelier
|
2
|
+
# An extension of the ruby built-in Array class with an optimization for
|
3
|
+
# insertion sort. This class assumes the array elements are sorted in
|
4
|
+
# descending order.
|
5
|
+
class DescendingInsertionSortArray < Array
|
6
|
+
# Insert `item` just before the element with the highest value that is lower
|
7
|
+
# than the value of `item`
|
8
|
+
#
|
9
|
+
# @param item [Object] any object that responds to `>`
|
10
|
+
def sorted_insert(item)
|
11
|
+
insertion_index = (0...size).bsearch(&search_proc(item))
|
12
|
+
insert(insertion_index || length, item)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def search_proc(item)
|
18
|
+
->(index) { item > self[index] }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
private_constant :DescendingInsertionSortArray
|
22
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require_relative 'descending_insertion_sort_array'
|
2
|
+
require_relative 'preference'
|
3
|
+
|
4
|
+
class Sommelier
|
5
|
+
# A collection of all matches to consider for pairing up dishes and wines.
|
6
|
+
class MatchCatalog
|
7
|
+
attr_reader :max_dish_preferences
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@dishes = {}
|
11
|
+
@wines = {}
|
12
|
+
@max_dish_preferences = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
# Add a symmetrical match for the dish and wine
|
16
|
+
#
|
17
|
+
# @param dish [Object] the dish
|
18
|
+
# @param wine [Object] the wine
|
19
|
+
# @param score [Number] a number dictating the strength of the match between
|
20
|
+
# the dish and wine (a higher number indicate a stronger match)
|
21
|
+
def add(dish, wine, score)
|
22
|
+
dish_prefs = add_dish_prefs(dish)
|
23
|
+
wine_prefs = add_wine_prefs(wine)
|
24
|
+
|
25
|
+
dish_prefs.sorted_insert(Preference.new(wine, score))
|
26
|
+
if dish_prefs.count > max_dish_preferences
|
27
|
+
@max_dish_preferences = dish_prefs.count
|
28
|
+
end
|
29
|
+
|
30
|
+
wine_prefs.sorted_insert(Preference.new(dish, score))
|
31
|
+
end
|
32
|
+
|
33
|
+
# @yield dish, preferences_count [Object, Integer] yield each dish and the
|
34
|
+
# number of wines in it's preference list.
|
35
|
+
def each_dish(&block)
|
36
|
+
dishes.each do |dish, prefs|
|
37
|
+
yield dish, prefs.count
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param dish [Object] the dish to find the wine for
|
42
|
+
# @param rank [Integer] the 0-based rank in the dish's preferences
|
43
|
+
# @return [Object] return the Nth ranked wine based on the given dishes
|
44
|
+
# preferences
|
45
|
+
def wine_preferred_at(dish, rank)
|
46
|
+
dishes[dish][rank].object
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return the highest ranked dish (for the wine) in the list of dishes
|
50
|
+
#
|
51
|
+
# @param wine [Object]
|
52
|
+
# @param dishes [Array<Object>]
|
53
|
+
# @return [Object] the highest ranked dish in the list
|
54
|
+
def most_preferred_dish(wine, dishes)
|
55
|
+
wines[wine].detect do |preference|
|
56
|
+
dishes.include?(preference.object)
|
57
|
+
end.object
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def add_dish_prefs(object)
|
63
|
+
dishes[object] ||= DescendingInsertionSortArray.new
|
64
|
+
dishes[object]
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_wine_prefs(object)
|
68
|
+
wines[object] ||= DescendingInsertionSortArray.new
|
69
|
+
wines[object]
|
70
|
+
end
|
71
|
+
|
72
|
+
attr_reader :dishes, :wines
|
73
|
+
end
|
74
|
+
private_constant :MatchCatalog
|
75
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative 'match_maker/generator'
|
2
|
+
require_relative 'match_maker/decider'
|
3
|
+
|
4
|
+
class Sommelier
|
5
|
+
# Implementation of a variation of the Gale-Shapely algorithm. In this
|
6
|
+
# variation, every dish that isn't currently in a pairing and hasn't exhausted
|
7
|
+
# its list of preferred wines will attempt a pairing with its highest
|
8
|
+
# matching wine that it has not yet attempt to pair with.
|
9
|
+
#
|
10
|
+
# Once all of the pairings for a round are made, then wines will accept
|
11
|
+
# only the highest matching dish that has attempted to pair with it. It will
|
12
|
+
# reject all others, even if it had previously accepted the pairing in a prior
|
13
|
+
# round.
|
14
|
+
class MatchMaker
|
15
|
+
def initialize(match_catalog)
|
16
|
+
@match_catalog = match_catalog
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return a set of pairings. Given that not all of the Gale-Shapely
|
20
|
+
# constraints must've been met, this is not guaranteed to include every
|
21
|
+
# dish or every wine.
|
22
|
+
#
|
23
|
+
# @return [Hash<Object, Object>] a mapping of dish to wine pairings
|
24
|
+
def pairings
|
25
|
+
accepted = {}
|
26
|
+
reversed = {}
|
27
|
+
|
28
|
+
generator = Generator.new(match_catalog)
|
29
|
+
decider = Decider.new(match_catalog)
|
30
|
+
(0...match_catalog.max_dish_preferences).each do |round|
|
31
|
+
requests = generator.requests(round, accepted)
|
32
|
+
decider.decide!(requests, accepted, reversed)
|
33
|
+
end
|
34
|
+
|
35
|
+
accepted
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :match_catalog
|
41
|
+
end
|
42
|
+
private_constant :MatchMaker
|
43
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Sommelier
|
2
|
+
class MatchMaker
|
3
|
+
class Decider
|
4
|
+
def initialize(match_catalog)
|
5
|
+
@match_catalog = match_catalog
|
6
|
+
end
|
7
|
+
|
8
|
+
# Decide a single dish's pairing request to accept for each wine
|
9
|
+
#
|
10
|
+
# @param requests [Hash<Object, Array<Object>>] mapping of wines to
|
11
|
+
# the list of dishes that have requested pairing in the current round
|
12
|
+
# @param accepted [Hash<Object, Object>] mapping of dishes to the wine
|
13
|
+
# that has accepted its pairing request
|
14
|
+
# @param reversed [Hash<Object, Object>] mapping of wines to the dish
|
15
|
+
# that has its pairing request accepted by wine
|
16
|
+
def decide!(requests, accepted, reversed)
|
17
|
+
requests.each do |wine, current_dishes|
|
18
|
+
# Be sure to consider the full set of current dishes and potentially
|
19
|
+
# a dish from a prior round who had its pairing request accepted by
|
20
|
+
# the wine.
|
21
|
+
prior_accepted_dish = reversed[wine]
|
22
|
+
dishes = current_dishes + [*prior_accepted_dish]
|
23
|
+
winning_dish = match_catalog.most_preferred_dish(wine, dishes)
|
24
|
+
|
25
|
+
if prior_accepted_dish != winning_dish
|
26
|
+
accepted.delete(prior_accepted_dish)
|
27
|
+
accepted[winning_dish] = wine
|
28
|
+
reversed[wine] = winning_dish
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :match_catalog
|
36
|
+
end
|
37
|
+
private_constant :Decider
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Sommelier
|
2
|
+
class MatchMaker
|
3
|
+
class Generator
|
4
|
+
def initialize(match_catalog)
|
5
|
+
@match_catalog = match_catalog
|
6
|
+
end
|
7
|
+
|
8
|
+
# Generate all of the requested pairings for a single round of the
|
9
|
+
# algorithm. Each dish that isn't currently locked in a pairing from a
|
10
|
+
# prior round and hasn't exhausted its list of preferred wines will
|
11
|
+
# request a pairing with the highest matching wine it has not yet
|
12
|
+
# requested.
|
13
|
+
#
|
14
|
+
# @param round [Integer] the round number
|
15
|
+
# @param accepted [Hash<Object, Object>] mapping of dishes to the wine
|
16
|
+
# that has accepted his pairing
|
17
|
+
# @return [Hash<Object, Array<Object>>] mapping of wines to the list of
|
18
|
+
# dishes that have requested pairings in the current round
|
19
|
+
def requests(round, accepted)
|
20
|
+
requests = {}
|
21
|
+
match_catalog.each_dish do |dish, preferences_count|
|
22
|
+
if round < preferences_count && !accepted.key?(dish)
|
23
|
+
wine = match_catalog.wine_preferred_at(dish, round)
|
24
|
+
requests[wine] ||= []
|
25
|
+
requests[wine] << dish
|
26
|
+
end
|
27
|
+
end
|
28
|
+
requests
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :match_catalog
|
34
|
+
end
|
35
|
+
private_constant :Generator
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Sommelier
|
2
|
+
# Simple class to bind a score (useable for ranking/sorting) to a generic
|
3
|
+
# object.
|
4
|
+
class Preference
|
5
|
+
attr_reader :object, :score
|
6
|
+
|
7
|
+
def initialize(object, score)
|
8
|
+
@object = object
|
9
|
+
@score = score
|
10
|
+
end
|
11
|
+
|
12
|
+
# Items used in DescendingInsertionSortArray must implement `>`
|
13
|
+
def >(other)
|
14
|
+
score > other.score
|
15
|
+
end
|
16
|
+
end
|
17
|
+
private_constant :Preference
|
18
|
+
end
|
data/sommelier.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 'sommelier/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'tcollier-sommelier'
|
8
|
+
spec.version = Sommelier::VERSION
|
9
|
+
spec.authors = ['Tom Collier']
|
10
|
+
spec.email = ['tcollier@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'Ruby implementation of a Stable Marriage solver'
|
13
|
+
spec.homepage = 'https://github.com/tcollier/sommelier'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_development_dependency 'bundler', '~> 1.12'
|
21
|
+
spec.add_development_dependency 'pry-byebug', '~> 3.3'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
+
spec.add_development_dependency 'rspec', '~> 3.4'
|
24
|
+
spec.add_development_dependency 'simplecov', '~> 0.16'
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tcollier-sommelier
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Collier
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-07-21 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.12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry-byebug
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.4'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.16'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.16'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- tcollier@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- CHANGELOG.txt
|
93
|
+
- Gemfile
|
94
|
+
- Gemfile.lock
|
95
|
+
- LICENSE
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- lib/sommelier.rb
|
99
|
+
- lib/sommelier/descending_insertion_sort_array.rb
|
100
|
+
- lib/sommelier/match_catalog.rb
|
101
|
+
- lib/sommelier/match_maker.rb
|
102
|
+
- lib/sommelier/match_maker/decider.rb
|
103
|
+
- lib/sommelier/match_maker/generator.rb
|
104
|
+
- lib/sommelier/preference.rb
|
105
|
+
- lib/sommelier/version.rb
|
106
|
+
- sommelier.gemspec
|
107
|
+
homepage: https://github.com/tcollier/sommelier
|
108
|
+
licenses:
|
109
|
+
- MIT
|
110
|
+
metadata: {}
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
requirements: []
|
126
|
+
rubyforge_project:
|
127
|
+
rubygems_version: 2.6.11
|
128
|
+
signing_key:
|
129
|
+
specification_version: 4
|
130
|
+
summary: Ruby implementation of a Stable Marriage solver
|
131
|
+
test_files: []
|