stable-matching 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.gitlab-ci.yml +28 -0
- data/.rspec +4 -0
- data/.rubocop.yml +31 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +154 -0
- data/Rakefile +6 -0
- data/circle.yml +3 -0
- data/lib/stable-matching/logging_helper.rb +16 -0
- data/lib/stable-matching/marriage.rb +135 -0
- data/lib/stable-matching/marriage/phase_i_runner.rb +122 -0
- data/lib/stable-matching/marriage/preference_table.rb +64 -0
- data/lib/stable-matching/marriage/validator.rb +51 -0
- data/lib/stable-matching/member.rb +82 -0
- data/lib/stable-matching/phase_runner.rb +44 -0
- data/lib/stable-matching/preference_list.rb +18 -0
- data/lib/stable-matching/preference_table.rb +80 -0
- data/lib/stable-matching/roommate.rb +124 -0
- data/lib/stable-matching/roommate/phase_i_runner.rb +111 -0
- data/lib/stable-matching/roommate/phase_ii_runner.rb +105 -0
- data/lib/stable-matching/roommate/phase_iii_runner.rb +159 -0
- data/lib/stable-matching/roommate/preference_table.rb +20 -0
- data/lib/stable-matching/roommate/validator.rb +26 -0
- data/lib/stable-matching/stable_matching.rb +11 -0
- data/lib/stable-matching/validator.rb +93 -0
- data/lib/version.rb +3 -0
- data/stable-matching.gemspec +29 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9f33d15fb7603874a410eac7e811c303aef3dec6
|
4
|
+
data.tar.gz: 32087ed764d51a24b8c791b8d68b933590388088
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5ec63ba206e500f249d7b69c4cde8d7938e3a0447332c8533b67d181760e9b03189d33bc70033c5887aa124fe3476af2c064e9e361d717591497a309fcc7edf0
|
7
|
+
data.tar.gz: f7b2ce792d35b420fad9bc2d8fc39b48a1db369bb0bb30763250944245dd5f75ad12b2f133ff6257a8c0478270333b3746633ea8c3464413d9c3a95ef9c7078a
|
data/.gitignore
ADDED
data/.gitlab-ci.yml
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
variables:
|
2
|
+
RUNNING_ON_CI_SERVER: 1
|
3
|
+
|
4
|
+
cache:
|
5
|
+
key: "$CI_BUILD_REF_NAME"
|
6
|
+
paths:
|
7
|
+
- /cache
|
8
|
+
|
9
|
+
before_script:
|
10
|
+
# System
|
11
|
+
- apt-get update -qq
|
12
|
+
|
13
|
+
# Ruby
|
14
|
+
- ruby -v
|
15
|
+
- which ruby
|
16
|
+
|
17
|
+
# Ruby Gems
|
18
|
+
- 'echo ''gem: --no-ri --no-rdoc'' > ~/.gemrc'
|
19
|
+
- gem install bundler
|
20
|
+
- bundle install --path=/cache --without production --jobs $(nproc) "${FLAGS[@]}"
|
21
|
+
|
22
|
+
rspec:
|
23
|
+
script:
|
24
|
+
- bundle exec rspec --color --require spec_helper --format progress --no-profile
|
25
|
+
|
26
|
+
rubocop:
|
27
|
+
script:
|
28
|
+
- bundle exec rubocop --display-cop-names
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
AllCops:
|
2
|
+
Exclude:
|
3
|
+
- "stable-matching.gemspec"
|
4
|
+
- "bin/*"
|
5
|
+
Metrics/AbcSize:
|
6
|
+
Enabled: false
|
7
|
+
Layout/DotPosition:
|
8
|
+
EnforcedStyle: leading
|
9
|
+
Layout/MultilineOperationIndentation:
|
10
|
+
Enabled: true
|
11
|
+
EnforcedStyle: indented
|
12
|
+
Style/BlockComments:
|
13
|
+
Enabled: false
|
14
|
+
Style/Documentation:
|
15
|
+
Enabled: false
|
16
|
+
Style/EmptyCaseCondition:
|
17
|
+
Enabled: false
|
18
|
+
Style/StringLiterals:
|
19
|
+
EnforcedStyle: double_quotes
|
20
|
+
Metrics/BlockLength:
|
21
|
+
ExcludedMethods:
|
22
|
+
- context
|
23
|
+
- describe
|
24
|
+
- it
|
25
|
+
- feature
|
26
|
+
- shared_examples
|
27
|
+
- shared_examples_for
|
28
|
+
- namespace
|
29
|
+
- task
|
30
|
+
- proc
|
31
|
+
- draw
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2018 Abhishek Chandrasekhar
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
# Stable Matching
|
4
|
+
|
5
|
+
![Stable Matching](https://gitlab.com/abhchand/stable-matching/raw/master/meta/logo.png)
|
6
|
+
|
7
|
+
[![Build Status](https://gitlab.com/abhchand/stable-matching/badges/master/build.svg)](https://gitlab.com/abhchand/stable-matching/pipelines)
|
8
|
+
|
9
|
+
A ruby implementation of various stable matching algorithms.
|
10
|
+
|
11
|
+
# Background
|
12
|
+
|
13
|
+
This gem provides ruby implementations of algorithms that solve the following matching problems:
|
14
|
+
|
15
|
+
- [Stable Roommates Problem](https://en.wikipedia.org/wiki/Stable_roommates_problem)
|
16
|
+
- [Stable Marriage Problem](https://en.wikipedia.org/wiki/Stable_marriage_problem)
|
17
|
+
|
18
|
+
# Install
|
19
|
+
|
20
|
+
In your Gemfile:
|
21
|
+
|
22
|
+
```
|
23
|
+
source "https://rubygems.org"
|
24
|
+
|
25
|
+
...
|
26
|
+
|
27
|
+
gem "stable-matching"
|
28
|
+
```
|
29
|
+
|
30
|
+
Then run:
|
31
|
+
|
32
|
+
```
|
33
|
+
bundle install
|
34
|
+
```
|
35
|
+
|
36
|
+
# Quick Start
|
37
|
+
|
38
|
+
## Stable Roommates
|
39
|
+
|
40
|
+
See or run `bin/stable-roommates-example` for an example usage.
|
41
|
+
|
42
|
+
Specify an input of ordered preferences as a hash of arrays. Keys may be `String` or `Integer` and preference table must include an even number of members.
|
43
|
+
|
44
|
+
``` ruby
|
45
|
+
preference_table = {
|
46
|
+
1 => [3, 4, 2, 6, 5],
|
47
|
+
2 => [6, 5, 4, 1, 3],
|
48
|
+
3 => [2, 4, 5, 1, 6],
|
49
|
+
4 => [5, 2, 3, 6, 1],
|
50
|
+
5 => [3, 1, 2, 4, 6],
|
51
|
+
6 => [5, 1, 3, 4, 2]
|
52
|
+
}
|
53
|
+
|
54
|
+
StableRoommate.solve!(preference_table)
|
55
|
+
#=> {1=>6, 2=>4, 3=>5, 4=>2, 5=>3, 6=>1}
|
56
|
+
```
|
57
|
+
|
58
|
+
The implementation of this algorithm is *not* guranteed to return a mathematically stable solution and may raise an error if no solution is found (see Errors below).
|
59
|
+
|
60
|
+
## Stable Marriage
|
61
|
+
|
62
|
+
See or run `bin/stable-marriage-example` for an example usage
|
63
|
+
|
64
|
+
Specify an input of ordered preferences as a hash of arrays for two groups. Keys may be `String` or `Integer`
|
65
|
+
|
66
|
+
```
|
67
|
+
alpha_preferences = {
|
68
|
+
"A" => ["O", "M", "N", "L", "P"],
|
69
|
+
"B" => ["P", "N", "M", "L", "O"],
|
70
|
+
"C" => ["M", "P", "L", "O", "N"],
|
71
|
+
"D" => ["P", "M", "O", "N", "L"],
|
72
|
+
"E" => ["O", "L", "M", "N", "P"],
|
73
|
+
}
|
74
|
+
|
75
|
+
beta_preferences = {
|
76
|
+
"L" => ["D", "B", "E", "C", "A"],
|
77
|
+
"M" => ["B", "A", "D", "C", "E"],
|
78
|
+
"N" => ["A", "C", "E", "D", "B"],
|
79
|
+
"O" => ["D", "A", "C", "B", "E"],
|
80
|
+
"P" => ["B", "E", "A", "C", "D"],
|
81
|
+
}
|
82
|
+
|
83
|
+
puts StableMarriage.solve!(alpha_preferences, beta_preferences,)
|
84
|
+
#=> {"A"=>"O", "B"=>"P", "C"=>"N", "D"=>"M", "E"=>"L", "L"=>"E", "M"=>"D", "N"=>"C", "O"=>"A", "P"=>"B"}
|
85
|
+
```
|
86
|
+
|
87
|
+
The implementation of this algorithm is always guranteed to return a mathematically stable solution.
|
88
|
+
|
89
|
+
# Errors
|
90
|
+
|
91
|
+
Your process should be prepared to handle the following errors when calling the stable matching library
|
92
|
+
|
93
|
+
```
|
94
|
+
StableMatching::Error
|
95
|
+
|- StableMatching::NoStableSolutionError
|
96
|
+
|- StableMatching::InvalidPreferences
|
97
|
+
```
|
98
|
+
|
99
|
+
# Logging
|
100
|
+
|
101
|
+
You may optionally pass a logger that will output the progress of the algorithm.
|
102
|
+
|
103
|
+
To utilize this option you'll have to instantiate an algorithm object yourself with a `:logger` option and then call `#solve!`.
|
104
|
+
|
105
|
+
``` ruby
|
106
|
+
logger = Logger.new(STDOUT)
|
107
|
+
logger.level = Logger::DEBUG
|
108
|
+
|
109
|
+
StableRoommate.new(preference_table, logger: logger).solve!
|
110
|
+
```
|
111
|
+
|
112
|
+
# Benchmark / Performance
|
113
|
+
|
114
|
+
Below are some benchmarks for runtimes captured on a machine running OS X 10.11.5 (El Capitan) / 2.5 GHz Intel Core i7.
|
115
|
+
|
116
|
+
You can run `bin/benchmark` on any machine to regenerate these benchmarks
|
117
|
+
|
118
|
+
Note: Many combinatorics algorithms run in quadratic time (`O(n^2)`) and therfore performance degrades significantly when processing a large number of members.
|
119
|
+
|
120
|
+
### Stable Roommates
|
121
|
+
|
122
|
+
```
|
123
|
+
N | Avg Runtime (sec)
|
124
|
+
-----|------------------
|
125
|
+
10 | 0.103
|
126
|
+
100 | 1.075
|
127
|
+
250 | 17.372
|
128
|
+
```
|
129
|
+
|
130
|
+
### Stable Marriage
|
131
|
+
|
132
|
+
```
|
133
|
+
N | Avg Runtime (sec)
|
134
|
+
------|------------------
|
135
|
+
10 | 0.004
|
136
|
+
100 | 0.053
|
137
|
+
1000 | 0.334
|
138
|
+
```
|
139
|
+
|
140
|
+
# Issues
|
141
|
+
|
142
|
+
Feel free to submit issues and enhancement requests.
|
143
|
+
|
144
|
+
# Contributing
|
145
|
+
|
146
|
+
All contributions to this project are welcome.
|
147
|
+
|
148
|
+
Code changes follow the "fork-and-pull" Git workflow.
|
149
|
+
|
150
|
+
1. **Fork** the repository
|
151
|
+
2. **Clone** the project to your own machine
|
152
|
+
3. **Commit** changes to your own branch
|
153
|
+
4. **Push** your work back up to your fork
|
154
|
+
5. Submit a **Pull request** so that your changes can be reviewed!
|
data/Rakefile
ADDED
data/circle.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Provides a ruby implementation of several common matching algorithms
|
2
|
+
#
|
3
|
+
# Author:: Abhishek Chandrasekhar (mailto:me@abhchand.me)
|
4
|
+
# License:: MIT
|
5
|
+
|
6
|
+
require "logger"
|
7
|
+
|
8
|
+
class StableMatching
|
9
|
+
module LoggingHelper
|
10
|
+
# rubocop:disable Style/AccessorMethodName
|
11
|
+
def set_logger(opts = {})
|
12
|
+
@logger = opts.key?(:logger) ? opts[:logger] : Logger.new(nil)
|
13
|
+
end
|
14
|
+
# rubocop:enable Style/AccessorMethodName
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# Provides a ruby implementation of several common matching algorithms
|
2
|
+
#
|
3
|
+
# Author:: Abhishek Chandrasekhar (mailto:me@abhchand.me)
|
4
|
+
# License:: MIT
|
5
|
+
|
6
|
+
require_relative "stable_matching"
|
7
|
+
require_relative "logging_helper"
|
8
|
+
|
9
|
+
require_relative "marriage/validator"
|
10
|
+
require_relative "marriage/preference_table"
|
11
|
+
require_relative "marriage/phase_i_runner"
|
12
|
+
|
13
|
+
class StableMatching
|
14
|
+
# Provides a solution to the Stable Marriage problem by implementing
|
15
|
+
# the Gale-Shapley algorithm
|
16
|
+
#
|
17
|
+
# Takes as input the preferences of two groups - alpha and beta - and
|
18
|
+
# produces a mathematically optimal matching/pairing between the two groups.
|
19
|
+
#
|
20
|
+
# Example Input:
|
21
|
+
#
|
22
|
+
# alpha_preferences = {
|
23
|
+
# "A" => ["M", "N", "L"],
|
24
|
+
# "B" => ["N", "M", "L"],
|
25
|
+
# "C" => ["M", "L", "N"],
|
26
|
+
# }
|
27
|
+
#
|
28
|
+
# beta_preferences = {
|
29
|
+
# "L" => ["B", "C", "A"],
|
30
|
+
# "M" => ["B", "A", "C"],
|
31
|
+
# "N" => ["A", "C", "B"],
|
32
|
+
# }
|
33
|
+
#
|
34
|
+
# Example Output:
|
35
|
+
#
|
36
|
+
# {"A"=>"M", "B"=>"N", "C"=>"L", "L"=>"C", "M"=>"A", "N"=>"B"}
|
37
|
+
class Marriage
|
38
|
+
include StableMatching::LoggingHelper
|
39
|
+
|
40
|
+
# Runs the algorithm with the specified inputs.
|
41
|
+
#
|
42
|
+
# This is a class level shortcut to initialize a new
|
43
|
+
# +StableMatching::Marriage+ instance and calls +solve!+ on it.
|
44
|
+
#
|
45
|
+
# <b>Inputs:</b>
|
46
|
+
#
|
47
|
+
# <tt>alpha_preferences</tt>::
|
48
|
+
# A +Hash+ of +Array+ values specifying the preferences of the alpha
|
49
|
+
# group
|
50
|
+
# <tt>beta_preferences</tt>::
|
51
|
+
# A +Hash+ of +Array+ values specifying the preferences of the beta
|
52
|
+
# group
|
53
|
+
#
|
54
|
+
# <b>Output:</b>
|
55
|
+
# A +Hash+ mapping alpha members to beta and beta members to alpha members.
|
56
|
+
def self.solve!(alpha_preferences, beta_preferences)
|
57
|
+
new(alpha_preferences, beta_preferences).solve!
|
58
|
+
end
|
59
|
+
|
60
|
+
# Initializes a `StableMatching::Marriage` object.
|
61
|
+
#
|
62
|
+
#
|
63
|
+
# <b>Inputs:</b>
|
64
|
+
#
|
65
|
+
# <tt>alpha_preferences</tt>::
|
66
|
+
# A +Hash+ of +Array+ values specifying the preferences of the alpha
|
67
|
+
# group. +Array+ can contain +String+ or +Integer+ entries.
|
68
|
+
# <tt>beta_preferences</tt>::
|
69
|
+
# A +Hash+ of +Array+ values specifying the preferences of the beta
|
70
|
+
# group. +Array+ can contain +String+ or +Integer+ entries.
|
71
|
+
# <tt>opts[:logger]</tt>::
|
72
|
+
# +Logger+ instance to use for logging
|
73
|
+
#
|
74
|
+
# <b>Output:</b>
|
75
|
+
#
|
76
|
+
# +StableMatching::Marriage+ instance
|
77
|
+
def initialize(alpha_preferences, beta_preferences, opts = {})
|
78
|
+
@orig_alpha_preferences = alpha_preferences
|
79
|
+
@orig_beta_preferences = beta_preferences
|
80
|
+
|
81
|
+
@alpha_preferences, @beta_preferences =
|
82
|
+
PreferenceTable.initialize_pair(alpha_preferences, beta_preferences)
|
83
|
+
|
84
|
+
set_logger(opts)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Runs the algorithm on the alpha and beta preference sets.
|
88
|
+
# Also validates the preference sets and raises an error if invalid.
|
89
|
+
#
|
90
|
+
# <b>Output:</b>
|
91
|
+
#
|
92
|
+
# A +Hash+ mapping alpha members to beta and beta members to alpha members.
|
93
|
+
#
|
94
|
+
# <b>Raises:</b>
|
95
|
+
#
|
96
|
+
# <tt>StableMatching::InvalidPreferences</tt>::
|
97
|
+
# When alpha or beta preference groups are of invalid format
|
98
|
+
def solve!
|
99
|
+
validate!
|
100
|
+
|
101
|
+
@logger.info("Running Phase I")
|
102
|
+
PhaseIRunner.new(alpha_preferences, beta_preferences, logger: @logger).run
|
103
|
+
|
104
|
+
build_solution
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def validate!
|
110
|
+
Validator.validate_pair!(@orig_alpha_preferences, @orig_beta_preferences)
|
111
|
+
end
|
112
|
+
|
113
|
+
def alpha_preferences
|
114
|
+
@alpha_preferences ||= PreferenceTable.new(@orig_alpha_preferences)
|
115
|
+
end
|
116
|
+
|
117
|
+
def beta_preferences
|
118
|
+
@beta_preferences ||= PreferenceTable.new(@orig_beta_preferences)
|
119
|
+
end
|
120
|
+
|
121
|
+
def build_solution
|
122
|
+
solution = {}
|
123
|
+
|
124
|
+
@alpha_preferences.members.each do |partner|
|
125
|
+
solution[partner.name] = partner.current_acceptor.name
|
126
|
+
end
|
127
|
+
|
128
|
+
@beta_preferences.members.each do |partner|
|
129
|
+
solution[partner.name] = partner.current_proposer.name
|
130
|
+
end
|
131
|
+
|
132
|
+
solution
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# Provides a ruby implementation of several common matching algorithms
|
2
|
+
#
|
3
|
+
# Author:: Abhishek Chandrasekhar (mailto:me@abhchand.me)
|
4
|
+
# License:: MIT
|
5
|
+
|
6
|
+
# Implements the Gale-Shapley (1962) algorithm.
|
7
|
+
#
|
8
|
+
# Calculates a stable match between two groups - alpha and beta - given their
|
9
|
+
# individual preference lists for members of the other group
|
10
|
+
#
|
11
|
+
# See:
|
12
|
+
# https://en.wikipedia.org/wiki/Stable_marriage_problem#Solution
|
13
|
+
# https://www.youtube.com/watch?v=GsBf3fJFpSw
|
14
|
+
#
|
15
|
+
# Each member who has not had their proposal accepted "proposes" to their top
|
16
|
+
# remaining preference
|
17
|
+
#
|
18
|
+
# Each recipient of a proposal can take one of 3 actions -
|
19
|
+
#
|
20
|
+
# 1. The recipient has not received a previous proposal and immediately accepts
|
21
|
+
#
|
22
|
+
# 2. The recipient prefers this new proposal over an existing one.
|
23
|
+
# The recipient "rejects" it's initial proposl and accepts this new one
|
24
|
+
#
|
25
|
+
# 3. The recipient prefers the existing proposal over the new one.
|
26
|
+
# The recipient "rejects" the new proposal
|
27
|
+
#
|
28
|
+
# Note: Rejections are mutual. If `i` removes `j` from their preference list,
|
29
|
+
# then `j` must also remove `i` from its list
|
30
|
+
#
|
31
|
+
# This cycle continues until every alpha/beta has a match.
|
32
|
+
#
|
33
|
+
# Mathematically, every participant is guranteed a match so this algorithm
|
34
|
+
# always converges on a solution.
|
35
|
+
#
|
36
|
+
#
|
37
|
+
# === EXAMPLE
|
38
|
+
#
|
39
|
+
# Take the following preference lists
|
40
|
+
#
|
41
|
+
# alpha preferences:
|
42
|
+
# A => [O, M, N, L, P]
|
43
|
+
# B => [P, N, M, L, O]
|
44
|
+
# C => [M, P, L, O, N]
|
45
|
+
# D => [P, M, O, N, L]
|
46
|
+
# E => [O, L, M, N, P]
|
47
|
+
#
|
48
|
+
# beta preferences:
|
49
|
+
# L => [D, B, E, C, A]
|
50
|
+
# M => [B, A, D, C, E]
|
51
|
+
# N => [A, C, E, D, B]
|
52
|
+
# O => [D, A, C, B, E]
|
53
|
+
# P => [B, E, A, C, D]
|
54
|
+
#
|
55
|
+
# We always start with the first unmatched user. Initially this is "A" (We only
|
56
|
+
# cycle through alphas since they propose to the betas).
|
57
|
+
# The sequence of events are -
|
58
|
+
#
|
59
|
+
# 'A' proposes to 'O'
|
60
|
+
# 'O' accepts 'A'
|
61
|
+
# 'B' proposes to 'P'
|
62
|
+
# 'P' accepts 'B'
|
63
|
+
# 'C' proposes to 'M'
|
64
|
+
# 'M' accepts 'C'
|
65
|
+
# 'D' proposes to 'P'
|
66
|
+
# 'P' rejects 'D'
|
67
|
+
# 'E' proposes to 'O'
|
68
|
+
# 'O' rejects 'E'
|
69
|
+
# 'D' proposes to 'M'
|
70
|
+
# 'M' accepts 'D', rejects 'C'
|
71
|
+
# 'E' proposes to 'L'
|
72
|
+
# 'L' accepts 'E'
|
73
|
+
# 'C' proposes to 'P'
|
74
|
+
# 'P' rejects 'C'
|
75
|
+
# 'C' proposes to 'L'
|
76
|
+
# 'L' rejects 'C'
|
77
|
+
# 'C' proposes to 'O'
|
78
|
+
# 'O' rejects 'C'
|
79
|
+
# 'C' proposes to 'N'
|
80
|
+
# 'N' accepts 'C'
|
81
|
+
#
|
82
|
+
# At this point there are no alpha users left unmatched (and by definition, no
|
83
|
+
# corresponding beta users left unmatched). All alpha members have had their
|
84
|
+
# proposals accepted by a beta user.
|
85
|
+
#
|
86
|
+
# The resulting solution is
|
87
|
+
#
|
88
|
+
# A => O
|
89
|
+
# B => P
|
90
|
+
# C => N
|
91
|
+
# D => M
|
92
|
+
# E => L
|
93
|
+
# L => E
|
94
|
+
# M => D
|
95
|
+
# N => C
|
96
|
+
# O => A
|
97
|
+
# P => B
|
98
|
+
#
|
99
|
+
|
100
|
+
require_relative "../phase_runner"
|
101
|
+
|
102
|
+
class StableMatching
|
103
|
+
class Marriage
|
104
|
+
class PhaseIRunner < StableMatching::PhaseRunner
|
105
|
+
def initialize(alpha_preferences, beta_preferences, opts = {})
|
106
|
+
@alpha_preferences = alpha_preferences
|
107
|
+
@beta_preferences = beta_preferences
|
108
|
+
|
109
|
+
@logger = opts.fetch(:logger)
|
110
|
+
end
|
111
|
+
|
112
|
+
def run
|
113
|
+
while @alpha_preferences.unmatched.any?
|
114
|
+
@alpha_preferences.unmatched.each do |partner|
|
115
|
+
top_choice = partner.first_preference
|
116
|
+
simulate_proposal(partner, top_choice)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|