stable_marriage 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: 75eb013fa666c7e0bf2f44c2e566be9038cdc043
4
+ data.tar.gz: c048c9b0fea7f68473d6f7d54183c8f1effa8923
5
+ SHA512:
6
+ metadata.gz: 05e7d654750796ea8b08b762b9f284f9f0ff93410171c73590c163784a0dd51e2d7e423e5593145342af584216d209f11b0048f552f2155f973052f03a3ce9a3
7
+ data.tar.gz: 0403e2c0dfd5b57a06646661d5b3027f255b9cf9dbdae3d4cf94b45ac215974de59592814395989b83a1cdf80d1cacd48500a0ebd640ec00c54fbcd93f0afa60
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,45 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ stable_marriage (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
+ method_source (0.9.0)
13
+ pry (0.11.3)
14
+ coderay (~> 1.1.0)
15
+ method_source (~> 0.9.0)
16
+ pry-byebug (3.6.0)
17
+ byebug (~> 10.0)
18
+ pry (~> 0.10)
19
+ rake (10.5.0)
20
+ rspec (3.7.0)
21
+ rspec-core (~> 3.7.0)
22
+ rspec-expectations (~> 3.7.0)
23
+ rspec-mocks (~> 3.7.0)
24
+ rspec-core (3.7.1)
25
+ rspec-support (~> 3.7.0)
26
+ rspec-expectations (3.7.0)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.7.0)
29
+ rspec-mocks (3.7.0)
30
+ diff-lcs (>= 1.2.0, < 2.0)
31
+ rspec-support (~> 3.7.0)
32
+ rspec-support (3.7.1)
33
+
34
+ PLATFORMS
35
+ ruby
36
+
37
+ DEPENDENCIES
38
+ bundler (~> 1.12)
39
+ pry-byebug (~> 3.3)
40
+ rake (~> 10.0)
41
+ rspec (~> 3.4)
42
+ stable_marriage!
43
+
44
+ BUNDLED WITH
45
+ 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,56 @@
1
+ # Stable Marriage
2
+
3
+ [![Numberphile: Stable Marriage Problem](https://img.youtube.com/vi/Qcv1IqHWAzg/0.jpg)](https://www.youtube.com/watch?v=Qcv1IqHWAzg)
4
+
5
+ The [Stable Marriage Problem](https://en.wikipedia.org/wiki/Stable_marriage_problem)
6
+ is a mathematical problem that attempts to uniquely match a set of _N_ items
7
+ (classically male suitors) with another set of _N_ items (classically females the
8
+ suitors wish to marry). The final matches are referred to as proposals.
9
+
10
+ ## Gale-Shapely Algorithm
11
+
12
+ This gem provides a variant of the [Gale-Shapely algorithm](https://en.wikipedia.org/wiki/Stable_marriage_problem#Solution).
13
+ Gale-Shapely guarantees a complete matching (i.e. every suitor is paired with
14
+ exactly one female and vice versa). Though this algorithm assumes every member
15
+ of either group has a complete ranking of the other group. For large populations,
16
+ this is not always practical.
17
+
18
+ ## Variation
19
+
20
+ The algorithm applied here has 2 primary differences from Gale-Shapely
21
+
22
+ 1. Neither suitors nor suitees need to have a complete ranking of the other set.
23
+ 2. The rankings are determined by a symmetrical match score. For example, the match
24
+ score for Alice and Steve is the same as for Steve and Alice. Though scores are
25
+ relative, so Alice's match score for Steve may be her highest scoring match, but
26
+ that same score may only be the fifth highest scoring match for Steve.
27
+
28
+ Because of these differences, not every suitor or suitee is guaranteed to have
29
+ a proposal. Swapping the suitor and suitee sets can have dramatic effects on the
30
+ final set of proposals.
31
+
32
+ ## Usage
33
+
34
+ ```bash
35
+ gem install stable-marriage
36
+ ```
37
+
38
+ ```ruby
39
+ require 'stable_marriage'
40
+ sm = StableMarriage.new
41
+ sm.add_match('Alice', 'Marcus', 0.366)
42
+ sm.add_match('Alice', 'Steve', 0.453)
43
+ sm.add_match('Alice', 'Will', 0.245)
44
+ sm.add_match('Janice', 'Phil', 0.486)
45
+ sm.add_match('Janice', 'Steve', 0.304)
46
+ sm.add_match('Lily', 'Steve', 0.299)
47
+ sm.add_match('Maria', 'Steve', 0.602)
48
+ puts sm.proposals
49
+ # {
50
+ # "Maria" => "Steve",
51
+ # "Janice" => "Phil",
52
+ # "Alice" => "Marcus"
53
+ # }
54
+
55
+ # Note: neither Lily nor Will were matched in the proposals map
56
+ ```
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
@@ -0,0 +1,17 @@
1
+ class StableMarriage
2
+ class DescendingInsertionSortArray < Array
3
+ # @param item [Object] any object that responds to `>`
4
+ def sorted_insert(item)
5
+ insertion_index = (0...size).bsearch(&search_proc(item))
6
+ insert(insertion_index || length, item)
7
+ end
8
+
9
+ private
10
+
11
+ def search_proc(item)
12
+ ->(index) { item > self[index] }
13
+ end
14
+ end
15
+
16
+ private_constant :DescendingInsertionSortArray
17
+ end
@@ -0,0 +1,36 @@
1
+ class StableMarriage
2
+ class MatchMaker
3
+ def initialize(match_set)
4
+ @match_set = match_set
5
+ end
6
+
7
+ def proposals
8
+ proposals = {}
9
+ (0...match_set.max_suitor_preferences).each do |round|
10
+ suitee_proposals = {}
11
+ match_set.each_suitor_prefs do |suitor, suitor_prefs|
12
+ if round < suitor_prefs.count && !proposals.key?(suitor)
13
+ suitee = match_set.suitee_preferred_at(suitor, round)
14
+ suitee_proposals[suitee] ||= []
15
+ suitee_proposals[suitee] << suitor
16
+ end
17
+ end
18
+
19
+ suitee_proposals.each do |suitee, suitors|
20
+ winning_suitor = match_set.most_preferred(suitee, suitors)
21
+ proposals[winning_suitor] = suitee
22
+ suitors.each do |suitor|
23
+ proposals.delete(suitor) unless suitor == winning_suitor
24
+ end
25
+ end
26
+ end
27
+
28
+ proposals
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :match_set
34
+ end
35
+ private_constant :MatchMaker
36
+ end
@@ -0,0 +1,55 @@
1
+ require_relative 'descending_insertion_sort_array'
2
+ require_relative 'preference'
3
+
4
+ class StableMarriage
5
+ class MatchSet
6
+ attr_reader :max_suitor_preferences
7
+
8
+ def initialize
9
+ @suitors = {}
10
+ @suitees = {}
11
+ @max_suitor_preferences = 0
12
+ end
13
+
14
+ def add(suitor, suitee, score)
15
+ suitor_prefs = add_suitor_prefs(suitor)
16
+ suitee_prefs = add_suitee_prefs(suitee)
17
+
18
+ suitor_prefs.sorted_insert(Preference.new(suitee, score))
19
+ if suitor_prefs.count > max_suitor_preferences
20
+ @max_suitor_preferences = suitor_prefs.count
21
+ end
22
+
23
+ suitee_prefs.sorted_insert(Preference.new(suitor, score))
24
+ end
25
+
26
+ def each_suitor_prefs(&block)
27
+ suitors.each(&block)
28
+ end
29
+
30
+ def suitee_preferred_at(suitor, round)
31
+ suitors[suitor][round].object
32
+ end
33
+
34
+ def most_preferred(suitee, suitors)
35
+ suitees[suitee].detect do |preference|
36
+ suitors.include?(preference.object)
37
+ end.object
38
+ end
39
+
40
+ private
41
+
42
+ def add_suitor_prefs(object)
43
+ suitors[object] ||= DescendingInsertionSortArray.new
44
+ suitors[object]
45
+ end
46
+
47
+ def add_suitee_prefs(object)
48
+ suitees[object] ||= DescendingInsertionSortArray.new
49
+ suitees[object]
50
+ end
51
+
52
+ attr_reader :suitors, :suitees
53
+ end
54
+ private_constant :MatchSet
55
+ end
@@ -0,0 +1,27 @@
1
+ class StableMarriage
2
+ class Preference
3
+ attr_reader :object, :score
4
+
5
+ def initialize(object, score)
6
+ @object = object
7
+ @score = score
8
+ end
9
+
10
+ def hash
11
+ object.hash
12
+ end
13
+
14
+ def ==(other)
15
+ return false unless other.is_a?(self.class)
16
+ hash == other.hash
17
+ end
18
+ alias_method :eql?, :==
19
+
20
+ # Items used in DescendingInsertionSortArray must implement `>`
21
+ def >(other)
22
+ score > other.score
23
+ end
24
+ end
25
+
26
+ private_constant :Preference
27
+ end
@@ -0,0 +1,3 @@
1
+ class StableMarriage
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'stable_marriage/match_maker'
2
+ require_relative 'stable_marriage/match_set'
3
+
4
+ class StableMarriage
5
+ def initialize
6
+ @match_set = MatchSet.new
7
+ end
8
+
9
+ # The the matching between the suitor and suitee with the match score.
10
+ #
11
+ # @param suitor [Object] the proposal maker in the Gale-Shapely algorithm.
12
+ # This party tends to fare better than the suitee in the algorithm.
13
+ # @param suitee [Object] the proposal acceptor/rejector in the Gale-Shapely
14
+ # algorithm.
15
+ # @param score [Numeric] a numeric representation of the strength of the
16
+ # match. A higher score means a better match.
17
+ def add_match(suitor, suitee, score)
18
+ match_set.add(suitor, suitee, score)
19
+ end
20
+
21
+ # A map with suitors as keys and suitees as objects
22
+ #
23
+ # @return [Hash<Object, Object>] the final matching between suitors and
24
+ # suitees.
25
+ def proposals
26
+ MatchMaker.new(match_set).proposals
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :match_set
32
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'stable_marriage/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'stable_marriage'
8
+ spec.version = StableMarriage::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/stable-marriage'
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
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stable_marriage
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
+ description:
70
+ email:
71
+ - tcollier@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - Gemfile
77
+ - Gemfile.lock
78
+ - LICENSE
79
+ - README.md
80
+ - Rakefile
81
+ - lib/stable_marriage.rb
82
+ - lib/stable_marriage/descending_insertion_sort_array.rb
83
+ - lib/stable_marriage/match_maker.rb
84
+ - lib/stable_marriage/match_set.rb
85
+ - lib/stable_marriage/preference.rb
86
+ - lib/stable_marriage/version.rb
87
+ - stable-marriage.gemspec
88
+ homepage: https://github.com/tcollier/stable-marriage
89
+ licenses:
90
+ - MIT
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.6.11
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: Ruby implementation of a Stable Marriage solver
112
+ test_files: []