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 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
@@ -0,0 +1,2 @@
1
+ coverage/
2
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require 'spec_helper'
data/CHANGELOG.txt ADDED
@@ -0,0 +1,3 @@
1
+ Version 0.1.0, 2018-07-20
2
+ -------------------------
3
+ e66dc58 Initial release with Gale-Shapely variant
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in amsi.gemspec
4
+ gemspec
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
+ [![Numberphile: Stable Marriage Problem](https://img.youtube.com/vi/Qcv1IqHWAzg/0.jpg)](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
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
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
@@ -0,0 +1,3 @@
1
+ class Sommelier
2
+ VERSION = '0.1.0'
3
+ 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: []