stable_match 0.1.0 → 0.1.1

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.
data/README.md CHANGED
@@ -1,24 +1,45 @@
1
1
  # StableMatch
2
2
 
3
- TODO: Write a gem description
3
+ A generic implementation of the Stable Match class of algorithms.
4
4
 
5
5
  ## Installation
6
6
 
7
+ ### Without Bundler
8
+
9
+ Install it yourself:
10
+
11
+ ```
12
+ $ gem install stable_match
13
+ ```
14
+
15
+ ### With Bundler
16
+
7
17
  Add this line to your application's Gemfile:
8
18
 
9
- gem 'stable_match'
19
+ ```ruby
20
+ gem 'stable_match'
21
+ ```
10
22
 
11
23
  And then execute:
12
24
 
13
- $ bundle
25
+ ```
26
+ $ bundle
27
+ ```
14
28
 
15
- Or install it yourself as:
29
+ ## Usage
16
30
 
17
- $ gem install stable_match
31
+ * See: `examples/example_1.rb`
32
+ * Run: `rake example`
18
33
 
19
- ## Usage
34
+ TODO: Write more usage instructions here
35
+
36
+ ## References
20
37
 
21
- TODO: Write usage instructions here
38
+ * [http://halfamind.aghion.com/the-national-resident-matching-programs-nrmp](http://halfamind.aghion.com/the-national-resident-matching-programs-nrmp)
39
+ * [http://rosettacode.org/wiki/Stable_marriage_problem#Ruby](http://rosettacode.org/wiki/Stable_marriage_problem#Ruby)
40
+ * [http://en.wikipedia.org/wiki/Stable_marriage_problem#Algorithm](http://en.wikipedia.org/wiki/Stable_marriage_problem#Algorithm)
41
+ * [http://en.wikipedia.org/wiki/National_Resident_Matching_Program](http://en.wikipedia.org/wiki/National_Resident_Matching_Program)
42
+ * [http://www.nrmp.org/res_match/about_res/algorithms.html](http://www.nrmp.org/res_match/about_res/algorithms.html)
22
43
 
23
44
  ## Contributing
24
45
 
data/Rakefile CHANGED
@@ -13,11 +13,38 @@ task :example => "examples:any"
13
13
 
14
14
  namespace :test do
15
15
  desc "Run All The Tests"
16
- task :all => [ "test:unit" ]
16
+ task :all do
17
+ suites = %w(
18
+ test:functional
19
+ test:unit
20
+ )
21
+
22
+ suites.each do | suite |
23
+ puts "=========================================="
24
+ puts "Running: #{ suite }"
25
+ puts "=========================================="
26
+ begin
27
+ Rake::Task[ suite ].invoke
28
+ rescue
29
+ puts "=========================================="
30
+ puts "FAILED: #{ suite }"
31
+ ensure
32
+ puts "=========================================="
33
+ puts "\n"
34
+ end
35
+ end
36
+ end
37
+
38
+ desc "Run The Functional Tests"
39
+ Rake::TestTask.new( :functional ) do | t |
40
+ t.libs << [ "test" ]
41
+ t.pattern = "test/functional/**/*_test.rb"
42
+ t.verbose = true
43
+ end
17
44
 
18
45
  desc "Run The Unit Tests"
19
46
  Rake::TestTask.new( :unit ) do | t |
20
- t.libs = [ "test" ]
47
+ t.libs << [ "test" ]
21
48
  t.pattern = "test/unit/**/*_test.rb"
22
49
  t.verbose = true
23
50
  end
@@ -1,6 +1,8 @@
1
1
  require "stable_match"
2
2
 
3
3
  class Test
4
+ ## This is a modified version of the example NRMP case
5
+ #
4
6
  PROGRAMS = {
5
7
  'city' => { :match_positions => 1, :preferences => ['garcia', 'hassan', 'eastman', 'brown', 'chen', 'davis', 'ford']},
6
8
  'general' => { :match_positions => 3, :preferences => ['brown', 'eastman', 'hassan', 'anderson', 'chen', 'davis', 'garcia']},
@@ -91,12 +91,12 @@ module StableMatch
91
91
  require "yaml"
92
92
 
93
93
  {
94
- :target => target,
95
- :match_positions => match_positions,
96
- :matches => matches.map( &:target ),
97
- :preference_position => preference_position,
98
- :preferences => preferences.map( &:target ),
99
- :proposals => proposals.map( &:target )
94
+ 'target' => target,
95
+ 'match_positions' => match_positions,
96
+ 'matches' => matches.map( &:target ),
97
+ 'preference_position' => preference_position,
98
+ 'preferences' => preferences.map( &:target ),
99
+ 'proposals' => proposals.map( &:target )
100
100
  }.to_yaml
101
101
  end
102
102
 
@@ -24,23 +24,29 @@ module StableMatch
24
24
  fattr( :candidate_set2 ){ {} } # for Candidates
25
25
  fattr :set2 # raw data
26
26
 
27
+ ## The way in which the run loop executes
28
+ #
29
+ # Should be either: symmetric OR asymmetric
30
+ #
31
+ fattr( :strategy ){ :symmetric }
32
+
27
33
  ## Runner::run
28
34
  #
29
35
  # Class-level factory method to construct, check, build and run a Runner instance
30
36
  #
31
- def self.run( *args , &block )
32
- runner = new *args , &block
37
+ def self.run( *args )
38
+ runner = new *args
33
39
  runner.check!
34
40
  runner.build!
35
41
  runner.run
36
42
  end
37
43
 
38
- def initialize( *args , &block )
44
+ def initialize( *args )
39
45
  options = Map.opts args
40
46
  @set1 = options.set1 rescue args.shift or raise ArgumentError.new( "No `set1` provided!" )
41
47
  @set2 = options.set2 rescue args.shift or raise ArgumentError.new( "No `set2` provided!" )
42
48
 
43
- yield self if block_given?
49
+ @strategy = options.strategy if options.get( :strategy )
44
50
  end
45
51
 
46
52
  ## Runner#build!
@@ -130,6 +136,7 @@ module StableMatch
130
136
  end
131
137
 
132
138
  {
139
+ 'strategy' => strategy.to_s,
133
140
  'candidate_set1' => inspection[ candidate_set1 ],
134
141
  'candidate_set2' => inspection[ candidate_set2 ]
135
142
  }.to_yaml
@@ -137,12 +144,19 @@ module StableMatch
137
144
 
138
145
  ## Runner#remaining_candidates
139
146
  #
147
+ # This method respects the runner's strategy!
148
+ #
140
149
  # List the remaining candidates that:
141
150
  # -> have remaining slots available for matches AND
142
151
  # -> have not already proposed to all of their preferences
143
152
  #
144
153
  def remaining_candidates
145
- candidates.reject{ | candidate | candidate.full? || candidate.exhausted_preferences? }
154
+ case strategy.to_sym
155
+ when :symmetric
156
+ candidates.reject { | candidate | candidate.full? || candidate.exhausted_preferences? }
157
+ when :asymmetric
158
+ candidate_set1.values.reject { | candidate | candidate.full? || candidate.exhausted_preferences? }
159
+ end
146
160
  end
147
161
 
148
162
  ## Runner#run
@@ -1,3 +1,3 @@
1
1
  module StableMatch
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -0,0 +1,59 @@
1
+ require "test_helper"
2
+
3
+ class NationalResidentMatchingProgramFunctionalTest < MiniTest::Unit::TestCase
4
+ def test_case
5
+ programs = {
6
+ 'mercy' => { :match_positions => 2, :preferences => ['chen', 'garcia']},
7
+ 'city' => { :match_positions => 2, :preferences => ['garcia', 'hassan', 'eastman', 'anderson', 'brown', 'chen', 'davis', 'ford']},
8
+ 'general' => { :match_positions => 2, :preferences => ['brown', 'eastman', 'hassan', 'anderson', 'chen', 'davis', 'garcia']},
9
+ 'state' => { :match_positions => 2, :preferences => ['brown', 'eastman', 'anderson', 'chen', 'hassan', 'ford', 'davis', 'garcia']}
10
+ }
11
+
12
+ applicants = {
13
+ 'anderson' => { :match_positions => 1 , :preferences => ['city'] },
14
+ 'brown' => { :match_positions => 1 , :preferences => ['city', 'mercy'] },
15
+ 'chen' => { :match_positions => 1 , :preferences => ['city', 'mercy'] },
16
+ 'davis' => { :match_positions => 1 , :preferences => ['mercy', 'city', 'general', 'state'] },
17
+ 'eastman' => { :match_positions => 1 , :preferences => ['city', 'mercy', 'state', 'general'] },
18
+ 'ford' => { :match_positions => 1 , :preferences => ['city', 'general', 'mercy', 'state'] },
19
+ 'garcia' => { :match_positions => 1 , :preferences => ['city', 'mercy', 'state', 'general'] },
20
+ 'hassan' => { :match_positions => 1 , :preferences => ['state', 'city', 'mercy', 'general' ] }
21
+ }
22
+
23
+ programs_expectations = {
24
+ 'mercy' => [ 'chen' ],
25
+ 'city' => [ 'garcia' , 'eastman' ],
26
+ 'general' => [ 'davis' ],
27
+ 'state' => [ 'ford' , 'hassan' ]
28
+ }
29
+
30
+ applicants_expectations = {
31
+ 'anderson' => [],
32
+ 'brown' => [],
33
+ 'chen' => [ 'mercy' ],
34
+ 'davis' => [ 'general' ],
35
+ 'eastman' => [ 'city' ],
36
+ 'ford' => [ 'state' ],
37
+ 'garcia' => [ 'city' ],
38
+ 'hassan' => [ 'state' ]
39
+ }
40
+
41
+ runner = StableMatch.run applicants , programs , :strategy => :asymmetric
42
+
43
+ ## applicants
44
+ #
45
+ applicants_expectations.each do | applicant , expected_matches |
46
+ candidate = runner.candidate_set1[ applicant ]
47
+ actual_matches = candidate.matches.map &:target
48
+ assert{ expected_matches.sort == actual_matches.sort }
49
+ end
50
+
51
+ ## programs
52
+ #
53
+ programs_expectations.each do | program , expected_matches |
54
+ candidate = runner.candidate_set2[ program ]
55
+ actual_matches = candidate.matches.map &:target
56
+ assert{ expected_matches.sort == actual_matches.sort }
57
+ end
58
+ end
59
+ end
data/test/test_helper.rb CHANGED
@@ -4,7 +4,7 @@ gem "minitest"
4
4
  require "minitest/autorun"
5
5
  require "minitest/pride"
6
6
 
7
- support_dir = File.expand_path File.dirname( __FILE__ ) , "support"
8
- Dir[ "#{ support_dir }/**/*.rb" ].each { | f | require f }
9
-
10
7
  require "stable_match"
8
+
9
+ support_dir = File.join File.expand_path( File.dirname __FILE__ ) , "support"
10
+ Dir[ "#{ support_dir }/**/*.rb" ].each { | f | require f }
@@ -1,6 +1,6 @@
1
1
  require "test_helper"
2
2
 
3
- class StableMatch::CandidateTest < MiniTest::Unit::TestCase
3
+ class StableMatch::CandidateUnitTest < MiniTest::Unit::TestCase
4
4
  def setup
5
5
  @candidate1 = StableMatch::Candidate.new 1 , [ 2 , 3 ]
6
6
  @candidate2 = StableMatch::Candidate.new 2 , [ 1 , 3 ]
@@ -1,6 +1,6 @@
1
1
  require "test_helper"
2
2
 
3
- class StableMatch::RunnerTest < MiniTest::Unit::TestCase
3
+ class StableMatch::RunnerUnitTest < MiniTest::Unit::TestCase
4
4
  def setup
5
5
  @set1 = { 1 => { :preferences => [ 2 ] } }
6
6
  @set2 = { 2 => { :preferences => [ 1 ] } }
@@ -27,6 +27,15 @@ class StableMatch::RunnerTest < MiniTest::Unit::TestCase
27
27
  assert{ runner.set2 == @set2 }
28
28
  end
29
29
 
30
+ def test_accepts_a_strategy_option
31
+ runner = assert{ StableMatch::Runner.new @set1 , @set2 , :strategy => :asymmetric }
32
+ assert{ :asymmetric == runner.strategy }
33
+ end
34
+
35
+ def test_default_strategy_is_symmetric
36
+ assert{ StableMatch::Runner.new( @set1 , @set2 ).strategy == :symmetric }
37
+ end
38
+
30
39
  ## Runner#build!
31
40
  #
32
41
  def test_build_creates_candidate_sets_from_each_raw_set
@@ -100,6 +109,20 @@ class StableMatch::RunnerTest < MiniTest::Unit::TestCase
100
109
  assert{ original_size > runner.remaining_candidates.size }
101
110
  end
102
111
 
112
+ def test_remaining_candidates_respects_the_run_strategy
113
+ runner = build_prepared_runner
114
+ assert{ runner.strategy == :symmetric }
115
+
116
+ all = runner.candidates.map &:target
117
+ remaining = runner.remaining_candidates.map &:target
118
+ assert{ all.sort == remaining.sort }
119
+
120
+ runner.strategy :asymmetric
121
+ set1 = @set1.keys
122
+ remaining = runner.remaining_candidates.map &:target
123
+ assert{ set1.sort == remaining.sort }
124
+ end
125
+
103
126
  ## Runner#run
104
127
  #
105
128
  def test_has_a_run_loop
@@ -0,0 +1,7 @@
1
+ require "test_helper"
2
+
3
+ class StableMatchUnitTest < MiniTest::Unit::TestCase
4
+ def test_has_a_shortcut_method_create_and_run_a_runner
5
+ assert{ StableMatch.respond_to? :run }
6
+ end
7
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stable_match
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -14,7 +14,7 @@ date: 2012-05-01 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: fattr
17
- requirement: &70273026247540 !ruby/object:Gem::Requirement
17
+ requirement: &70346750299680 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ~>
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 2.2.1
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70273026247540
25
+ version_requirements: *70346750299680
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: map
28
- requirement: &70273026246260 !ruby/object:Gem::Requirement
28
+ requirement: &70346750298320 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ~>
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: 5.5.0
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *70273026246260
36
+ version_requirements: *70346750298320
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: minitest
39
- requirement: &70273026229780 !ruby/object:Gem::Requirement
39
+ requirement: &70346750281820 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ~>
@@ -44,10 +44,10 @@ dependencies:
44
44
  version: 2.12.1
45
45
  type: :development
46
46
  prerelease: false
47
- version_requirements: *70273026229780
47
+ version_requirements: *70346750281820
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: rake
50
- requirement: &70273026228300 !ruby/object:Gem::Requirement
50
+ requirement: &70346750280460 !ruby/object:Gem::Requirement
51
51
  none: false
52
52
  requirements:
53
53
  - - ~>
@@ -55,10 +55,10 @@ dependencies:
55
55
  version: 0.9.2.2
56
56
  type: :development
57
57
  prerelease: false
58
- version_requirements: *70273026228300
58
+ version_requirements: *70346750280460
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: rego
61
- requirement: &70273026227040 !ruby/object:Gem::Requirement
61
+ requirement: &70346750279080 !ruby/object:Gem::Requirement
62
62
  none: false
63
63
  requirements:
64
64
  - - ~>
@@ -66,7 +66,7 @@ dependencies:
66
66
  version: 1.0.0
67
67
  type: :development
68
68
  prerelease: false
69
- version_requirements: *70273026227040
69
+ version_requirements: *70346750279080
70
70
  description: A generic implementation of the stable match algorightm.
71
71
  email:
72
72
  - cookrn@gmail.com
@@ -86,10 +86,12 @@ files:
86
86
  - lib/stable_match/runner.rb
87
87
  - lib/stable_match/version.rb
88
88
  - stable_match.gemspec
89
+ - test/functional/nrmp_test.rb
89
90
  - test/support/minitest.rb
90
91
  - test/test_helper.rb
91
- - test/unit/stable_match/candidate_test.rb
92
- - test/unit/stable_match/runner_test.rb
92
+ - test/unit/lib/stable_match/candidate_test.rb
93
+ - test/unit/lib/stable_match/runner_test.rb
94
+ - test/unit/lib/stable_match_test.rb
93
95
  homepage: https://github.com/cookrn/stable_match
94
96
  licenses: []
95
97
  post_install_message:
@@ -104,7 +106,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
104
106
  version: '0'
105
107
  segments:
106
108
  - 0
107
- hash: 4207718686449127189
109
+ hash: 1686263240201859954
108
110
  required_rubygems_version: !ruby/object:Gem::Requirement
109
111
  none: false
110
112
  requirements:
@@ -113,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
115
  version: '0'
114
116
  segments:
115
117
  - 0
116
- hash: 4207718686449127189
118
+ hash: 1686263240201859954
117
119
  requirements: []
118
120
  rubyforge_project:
119
121
  rubygems_version: 1.8.16
@@ -121,7 +123,9 @@ signing_key:
121
123
  specification_version: 3
122
124
  summary: stable_match v0.1.0
123
125
  test_files:
126
+ - test/functional/nrmp_test.rb
124
127
  - test/support/minitest.rb
125
128
  - test/test_helper.rb
126
- - test/unit/stable_match/candidate_test.rb
127
- - test/unit/stable_match/runner_test.rb
129
+ - test/unit/lib/stable_match/candidate_test.rb
130
+ - test/unit/lib/stable_match/runner_test.rb
131
+ - test/unit/lib/stable_match_test.rb