shadchan 0.0.2 → 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.
- data/README.textile +22 -0
- data/lib/shadchan.rb +3 -1
- data/lib/shadchan/not_solvable.rb +4 -0
- data/lib/shadchan/roomie.rb +158 -0
- data/lib/shadchan/version.rb +1 -1
- metadata +6 -3
data/README.textile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
h1. Shadchan
|
|
2
|
+
|
|
3
|
+
This gem provides a class for solving the "Stable Marriage problem":http://en.wikipedia.org/wiki/Stable_marriage_problem and "Stable Roommates problem":http://en.wikipedia.org/wiki/Stable_roommates_problem.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
h2. Installation
|
|
7
|
+
<pre>gem install shadchan</pre>
|
|
8
|
+
|
|
9
|
+
h2. Usage
|
|
10
|
+
|
|
11
|
+
<pre>require 'shadchan'
|
|
12
|
+
# stable marriage example
|
|
13
|
+
shadchan = Shadchan::Shadchan.new [0, 2, 1], [2, 0, 1], [0, 2, 1], [1, 0, 2], [0, 2, 1], [0, 1, 2]
|
|
14
|
+
shadchan.match #=> [[0, 2], [1, 0], [2, 1]]
|
|
15
|
+
shadchan.match_men #=> [1, 2, 0]
|
|
16
|
+
shadchan.match_women #=> [2, 0, 1]
|
|
17
|
+
|
|
18
|
+
# stable roommates example
|
|
19
|
+
roomie = Shadchan::Roomie.new [2,3,1,5,4],[5,4,3,0,2],[1,3,4,0,5],[4,1,2,5,0],[2,0,1,3,5],[4,0,2,3,1]
|
|
20
|
+
roomie.match #=> [5,4,3,1,2,0]
|
|
21
|
+
</pre>
|
|
22
|
+
|
data/lib/shadchan.rb
CHANGED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
module Shadchan
|
|
2
|
+
=begin rdoc
|
|
3
|
+
Solves Stable Roommates problem. From n 'roommates', who all have a preference about each other,
|
|
4
|
+
it creates such pairing that no roommate would be happier with other AND the other would be
|
|
5
|
+
happier with the roommate.
|
|
6
|
+
|
|
7
|
+
http://en.wikipedia.org/wiki/Stable_roommates_problem
|
|
8
|
+
|
|
9
|
+
Problem is not always solvable. In that case it raises Shadchan::NotSolvable exception.
|
|
10
|
+
=end
|
|
11
|
+
class Roomie
|
|
12
|
+
=begin rdoc
|
|
13
|
+
Initialize problem. Takes preference lists in form [most_prefered_index, second_most_prefered_index, ...].
|
|
14
|
+
Example:
|
|
15
|
+
Shachdan::Roomie.new [2,3,1,5,4],[5,4,3,0,2],[1,3,4,0,5],[4,1,2,5,0],[2,0,1,3,5],[4,0,2,3,1]
|
|
16
|
+
or
|
|
17
|
+
a = [[2,3,1,5,4],[5,4,3,0,2],[1,3,4,0,5],[4,1,2,5,0],[2,0,1,3,5],[4,0,2,3,1]]
|
|
18
|
+
Shachdan::Roomie.new *a
|
|
19
|
+
|
|
20
|
+
Will raise Shachdan::NotSolvable if solution cannot be found.
|
|
21
|
+
=end
|
|
22
|
+
def initialize *args
|
|
23
|
+
check_input args
|
|
24
|
+
@preferences = args.map(&:dup)
|
|
25
|
+
@size = @preferences.size
|
|
26
|
+
@match = []
|
|
27
|
+
stable_matching
|
|
28
|
+
end
|
|
29
|
+
=begin rdoc
|
|
30
|
+
Returns array of matches. Numbering corresponds to that used to initialize this instance.
|
|
31
|
+
shachdan = Shachdan::Roomie.new *a
|
|
32
|
+
shachdan.match #=> [5,4,3,1,2,0]
|
|
33
|
+
|
|
34
|
+
In this example, roommate 0 was matched with roommate 5, roommate 1 with roommate 4 etc.
|
|
35
|
+
=end
|
|
36
|
+
def match
|
|
37
|
+
@reversed_matches.map(&:dup)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
def stable_matching
|
|
42
|
+
propose
|
|
43
|
+
return true if is_matching?
|
|
44
|
+
reverse_matches
|
|
45
|
+
# puts @matches.inspect
|
|
46
|
+
# puts @reversed_matches.inspect
|
|
47
|
+
# puts @modified_preferences.inspect
|
|
48
|
+
rotate_solutions
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def reverse_matches
|
|
52
|
+
@reversed_matches = Array.new(@size)
|
|
53
|
+
@matches.each_with_index do |e, i|
|
|
54
|
+
@reversed_matches[e] = i
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def rotate_solutions
|
|
60
|
+
until is_matching?
|
|
61
|
+
rot = generate_rotation
|
|
62
|
+
#puts rot.inspect
|
|
63
|
+
apply_rotation(rot)
|
|
64
|
+
# puts @reversed_matches.inspect
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def apply_rotation(rot)
|
|
69
|
+
rot.each do |r|
|
|
70
|
+
@reversed_matches[r.first] = r.last
|
|
71
|
+
@matches[r.last] = r.first
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def generate_rotation
|
|
76
|
+
rotation = []
|
|
77
|
+
@reversed_matches.each_with_index do |e, i|
|
|
78
|
+
unless @reversed_matches[e] == i then
|
|
79
|
+
current = i
|
|
80
|
+
begin
|
|
81
|
+
# get second favorite
|
|
82
|
+
second_favorite = @modified_preferences[current].shift
|
|
83
|
+
rotation << [current, second_favorite]
|
|
84
|
+
current = @reversed_matches.find_index(second_favorite)
|
|
85
|
+
#puts rotation.inspect
|
|
86
|
+
# puts @modified_preferences.inspect
|
|
87
|
+
# puts "sf = #{second_favorite}"
|
|
88
|
+
# puts "e = #{e}"
|
|
89
|
+
# puts "i = #{i}"
|
|
90
|
+
# puts "current = #{current}"
|
|
91
|
+
end while second_favorite != e
|
|
92
|
+
return rotation
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
rotation
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def propose
|
|
99
|
+
@matches = Array.new(@size)
|
|
100
|
+
@modified_preferences = @preferences.map(&:dup)
|
|
101
|
+
|
|
102
|
+
while r = free_roommate
|
|
103
|
+
desired = @modified_preferences[r].shift
|
|
104
|
+
raise NotSolvable, 'Problem has no solution' if desired.nil?
|
|
105
|
+
if @matches[desired] then
|
|
106
|
+
if @preferences[desired].find_index(r) < @preferences[desired].find_index(@matches[desired]) then
|
|
107
|
+
@matches[desired] = r
|
|
108
|
+
end
|
|
109
|
+
else
|
|
110
|
+
@matches[desired] = r
|
|
111
|
+
next
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def is_matching?
|
|
117
|
+
@matches.each_with_index do |e, i|
|
|
118
|
+
return false unless @matches[e] == i
|
|
119
|
+
end
|
|
120
|
+
true
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def free_roommate
|
|
124
|
+
@size.times do |i|
|
|
125
|
+
not_proposed = !@matches.include?(i)
|
|
126
|
+
if not_proposed then
|
|
127
|
+
return i
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
return nil
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def check_input args
|
|
134
|
+
raise ArgumentError, 'No arguments given' if args.empty?
|
|
135
|
+
size = args.first.size
|
|
136
|
+
|
|
137
|
+
unless args.all?{|w| w.size == size} then
|
|
138
|
+
raise ArgumentError, 'Incorrectly specified input - inconsistent preference list size. Check your input.'
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
unless args.size == size+1 then
|
|
142
|
+
raise ArgumentError, 'Incorrectly specified input - preference lists too small or big. Check your input.'
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
unless args.size.even?
|
|
146
|
+
raise ArgumentError, 'Not valid problem - number of roommates must be even.'
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
args.each_with_index do |e, i|
|
|
150
|
+
size.times do |j|
|
|
151
|
+
unless i==j then
|
|
152
|
+
raise ArgumentError, 'Error in input - each roommate must state preference for all other except himself' unless e.include? j
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
data/lib/shadchan/version.rb
CHANGED
metadata
CHANGED
|
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
|
|
|
5
5
|
prerelease: false
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
|
+
- 1
|
|
8
9
|
- 0
|
|
9
|
-
|
|
10
|
-
version: 0.0.2
|
|
10
|
+
version: 0.1.0
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Vojtech Salbaba
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2011-01-
|
|
18
|
+
date: 2011-01-16 00:00:00 +01:00
|
|
19
19
|
default_executable:
|
|
20
20
|
dependencies: []
|
|
21
21
|
|
|
@@ -31,8 +31,11 @@ extra_rdoc_files: []
|
|
|
31
31
|
files:
|
|
32
32
|
- .gitignore
|
|
33
33
|
- Gemfile
|
|
34
|
+
- README.textile
|
|
34
35
|
- Rakefile
|
|
35
36
|
- lib/shadchan.rb
|
|
37
|
+
- lib/shadchan/not_solvable.rb
|
|
38
|
+
- lib/shadchan/roomie.rb
|
|
36
39
|
- lib/shadchan/shadchan.rb
|
|
37
40
|
- lib/shadchan/version.rb
|
|
38
41
|
- shadchan.gemspec
|