munkres 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/History.txt +6 -0
- data/README.txt +64 -0
- data/Rakefile +33 -0
- data/lib/munkres.rb +271 -0
- data/test/munkres_test.rb +234 -0
- metadata +59 -0
data/History.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
= munkres
|
2
|
+
|
3
|
+
http://github.com/pdamer/munkres
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
A ruby implementation of the kuhn-munkres or 'hungarian' algorithm for bipartite matching.
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
Match groups together 1 to 1
|
12
|
+
|
13
|
+
== SYNOPSIS:
|
14
|
+
|
15
|
+
Create a 2d matrix joining your two groups to match with each entry
|
16
|
+
being the cost of matching the corresponding members of the groups
|
17
|
+
|
18
|
+
require 'munkres'
|
19
|
+
|
20
|
+
cost_matrix = [[4,3],
|
21
|
+
[3,0]]
|
22
|
+
|
23
|
+
m = Munkres.new(cost_matrix)
|
24
|
+
p m.find_pairings
|
25
|
+
|
26
|
+
|
27
|
+
== REQUIREMENTS:
|
28
|
+
|
29
|
+
Uses test/spec for testing.
|
30
|
+
|
31
|
+
== INSTALL:
|
32
|
+
|
33
|
+
sudo gem install munkres
|
34
|
+
|
35
|
+
== DEVELOPERS:
|
36
|
+
|
37
|
+
After checking out the source, run:
|
38
|
+
|
39
|
+
$ rake test
|
40
|
+
|
41
|
+
== LICENSE:
|
42
|
+
|
43
|
+
(The MIT License)
|
44
|
+
|
45
|
+
Copyright (c) 2010
|
46
|
+
|
47
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
48
|
+
a copy of this software and associated documentation files (the
|
49
|
+
'Software'), to deal in the Software without restriction, including
|
50
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
51
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
52
|
+
permit persons to whom the Software is furnished to do so, subject to
|
53
|
+
the following conditions:
|
54
|
+
|
55
|
+
The above copyright notice and this permission notice shall be
|
56
|
+
included in all copies or substantial portions of the Software.
|
57
|
+
|
58
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
59
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
60
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
61
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
62
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
63
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
64
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
|
5
|
+
spec = Gem::Specification.new do |s|
|
6
|
+
s.name = "munkres"
|
7
|
+
s.version = "0.1.0"
|
8
|
+
s.author = "Paul Damer and Jim Wood"
|
9
|
+
s.email = 'pdamer@gmail.com'
|
10
|
+
s.homepage = "http://github.com/pdamer/munkres"
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.summary = "A Ruby implementation of the Hungarian Algorithm"
|
13
|
+
s.description = "A ruby implementation of the kuhn-munkres or 'hungarian' algorithm for bipartite matching."
|
14
|
+
s.rubyforge_project = "munkres"
|
15
|
+
s.test_files = ['test/munkres_test.rb']
|
16
|
+
s.files = %w[
|
17
|
+
lib/munkres.rb
|
18
|
+
test/munkres_test.rb
|
19
|
+
README.txt
|
20
|
+
Rakefile
|
21
|
+
History.txt
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
26
|
+
pkg.need_tar = true
|
27
|
+
end
|
28
|
+
|
29
|
+
Rake::TestTask.new() do |t|
|
30
|
+
t.libs << "test"
|
31
|
+
t.test_files = FileList['test/*_test.rb']
|
32
|
+
t.verbose = true
|
33
|
+
end
|
data/lib/munkres.rb
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
class Munkres
|
2
|
+
MODE_MINIMIZE_COST = 1
|
3
|
+
MODE_MAXIMIZE_UTIL = 2
|
4
|
+
|
5
|
+
def initialize(matrix=[], mode=MODE_MINIMIZE_COST)
|
6
|
+
@matrix = matrix
|
7
|
+
@mode = mode
|
8
|
+
@original = Marshal.load(Marshal.dump(@matrix))
|
9
|
+
validate_and_pad
|
10
|
+
@covered_columns = []
|
11
|
+
@covered_rows = []
|
12
|
+
@starred_zeros = []
|
13
|
+
@primed_zeros = []
|
14
|
+
|
15
|
+
class << @matrix
|
16
|
+
|
17
|
+
|
18
|
+
def row(index)
|
19
|
+
self[index]
|
20
|
+
end
|
21
|
+
|
22
|
+
def column(index)
|
23
|
+
self.collect do |row|
|
24
|
+
row[index]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def columns
|
29
|
+
result = []
|
30
|
+
self.first.each_index do |i|
|
31
|
+
result << self.column(i)
|
32
|
+
end
|
33
|
+
result
|
34
|
+
end
|
35
|
+
|
36
|
+
def each_column_index &block
|
37
|
+
column_indices.each &block
|
38
|
+
end
|
39
|
+
|
40
|
+
def column_indices
|
41
|
+
@column_indices ||= (0...self.first.size).to_a
|
42
|
+
end
|
43
|
+
|
44
|
+
def row_indices
|
45
|
+
@row_indices ||= (0...self.size).to_a
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
def find_pairings
|
52
|
+
create_zero_in_rows
|
53
|
+
star_zeros
|
54
|
+
cover_columns_with_stars
|
55
|
+
while not done?
|
56
|
+
p = cover_zeros_and_create_more
|
57
|
+
find_better_stars p
|
58
|
+
#create series
|
59
|
+
cover_columns_with_stars
|
60
|
+
end
|
61
|
+
@pairings = @starred_zeros.delete_if{|row_index,col_index| col_index >= @original.first.size || row_index >= @original.size}
|
62
|
+
end
|
63
|
+
|
64
|
+
def total_cost_of_pairing
|
65
|
+
@pairings.inject(0) {|total, star| total + @original[star[0]][star[1]]}
|
66
|
+
end
|
67
|
+
|
68
|
+
def pretty_print
|
69
|
+
print_col_row
|
70
|
+
@matrix.each_with_index do |row,row_index|
|
71
|
+
print @covered_rows.include?(row_index) ? "-" : " "
|
72
|
+
row.each_with_index do |value, col_index|
|
73
|
+
print value
|
74
|
+
print "*" if @starred_zeros.include? [row_index,col_index]
|
75
|
+
print "'" if @primed_zeros.include? [row_index,col_index]
|
76
|
+
print "\t"
|
77
|
+
end
|
78
|
+
print @covered_rows.include?(row_index) ? "-" : " "
|
79
|
+
print "\n"
|
80
|
+
end
|
81
|
+
print_col_row
|
82
|
+
end
|
83
|
+
|
84
|
+
def print_col_row
|
85
|
+
print " "
|
86
|
+
@matrix.column_indices.each do |col_index|
|
87
|
+
print "|" if @covered_columns.include? col_index
|
88
|
+
print "\t"
|
89
|
+
end
|
90
|
+
print "\n"
|
91
|
+
end
|
92
|
+
|
93
|
+
protected
|
94
|
+
|
95
|
+
attr_accessor :matrix, :covered_columns, :covered_rows, :starred_zeros, :primed_zeros, :primed_starred_series
|
96
|
+
|
97
|
+
def create_zero_in_rows
|
98
|
+
@matrix.each_with_index do |row,row_index|
|
99
|
+
min_val = min_or_zero(row)
|
100
|
+
row.each_with_index do |value, col_index|
|
101
|
+
@matrix[row_index][col_index] = value - min_val
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def star_zeros
|
107
|
+
unstarred_columns = @matrix.column_indices
|
108
|
+
|
109
|
+
@matrix.each_with_index do |row, row_index|
|
110
|
+
star = star_in_row?(row_index)
|
111
|
+
next if star
|
112
|
+
unstarred_columns.each do |col_index|
|
113
|
+
if (row[col_index] == 0 and !star_in_column?(col_index))
|
114
|
+
@starred_zeros << [row_index, col_index]
|
115
|
+
unstarred_columns -= [col_index]
|
116
|
+
break # go to next row
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def cover_columns_with_stars
|
123
|
+
cols = @starred_zeros.collect {|z| z[1]}
|
124
|
+
cols.uniq!
|
125
|
+
@covered_columns += cols
|
126
|
+
end
|
127
|
+
|
128
|
+
def prime_first_uncovered_zero
|
129
|
+
my_cols = uncovered_columns #silly workaround to cache the list
|
130
|
+
|
131
|
+
uncovered_rows.each do |row_index|
|
132
|
+
my_cols.each do |col_index|
|
133
|
+
if @matrix[row_index][col_index] == 0
|
134
|
+
@primed_zeros << [row_index, col_index]
|
135
|
+
return [row_index, col_index]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
nil
|
140
|
+
end
|
141
|
+
|
142
|
+
def smallest_uncovered_value
|
143
|
+
min_value = nil
|
144
|
+
my_cols = uncovered_columns #silly workaround to cache the list
|
145
|
+
uncovered_rows.each do |row_index|
|
146
|
+
my_cols.each do |col_index|
|
147
|
+
value = @matrix[row_index][col_index]
|
148
|
+
min_value ||= value
|
149
|
+
min_value = value if value < min_value
|
150
|
+
return 0 if min_value == 0
|
151
|
+
end
|
152
|
+
end
|
153
|
+
min_value
|
154
|
+
end
|
155
|
+
|
156
|
+
def add_and_subtract_for_step_6(delta=0)
|
157
|
+
covered_rows.each do |row_index|
|
158
|
+
covered_columns.each do |col_index|
|
159
|
+
@matrix[row_index][col_index] += delta
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
my_cols = uncovered_columns #silly workaround to cache the list
|
165
|
+
|
166
|
+
uncovered_rows.each do |row_index|
|
167
|
+
my_cols.each do |col_index|
|
168
|
+
@matrix[row_index][col_index] -= delta
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def find_better_stars(first_zero)
|
174
|
+
primes_series = [first_zero]
|
175
|
+
stars_series = []
|
176
|
+
while next_star = @starred_zeros.detect{|row, col| col == primes_series.last[1] }
|
177
|
+
stars_series << next_star
|
178
|
+
primes_series << @primed_zeros.detect{|row, col| row == stars_series.last[0] }
|
179
|
+
end
|
180
|
+
stars_series.each do |star|
|
181
|
+
@starred_zeros.delete(star)
|
182
|
+
end
|
183
|
+
|
184
|
+
primes_series.each do |prime|
|
185
|
+
@starred_zeros << prime
|
186
|
+
end
|
187
|
+
|
188
|
+
@primed_zeros = []
|
189
|
+
@covered_columns = []
|
190
|
+
@covered_rows = []
|
191
|
+
end
|
192
|
+
|
193
|
+
def done?
|
194
|
+
@matrix.column_indices.size == covered_columns.size
|
195
|
+
end
|
196
|
+
|
197
|
+
def min_or_zero(collection)
|
198
|
+
collection.index(0) ? 0 : collection.min
|
199
|
+
end
|
200
|
+
|
201
|
+
def star_in_row?(index)
|
202
|
+
@starred_zeros.any? {|row_index,col_index| row_index == index}
|
203
|
+
end
|
204
|
+
|
205
|
+
def star_in_row(index)
|
206
|
+
#@starred_zeros.detect {|row_index,col_index| row_index == index}
|
207
|
+
@starred_zeros.assoc index
|
208
|
+
end
|
209
|
+
|
210
|
+
def star_in_column?(index)
|
211
|
+
@starred_zeros.any? {|row_index,col_index| col_index == index}
|
212
|
+
end
|
213
|
+
|
214
|
+
def uncovered_rows
|
215
|
+
@matrix.row_indices - @covered_rows
|
216
|
+
end
|
217
|
+
|
218
|
+
def uncovered_columns
|
219
|
+
@matrix.column_indices - @covered_columns
|
220
|
+
end
|
221
|
+
|
222
|
+
#step 4
|
223
|
+
def cover_zeros_and_create_more
|
224
|
+
loop do
|
225
|
+
while prime = prime_first_uncovered_zero
|
226
|
+
if star = star_in_row(prime[0])
|
227
|
+
@covered_rows << prime[0]
|
228
|
+
@covered_columns -= [star[1]]
|
229
|
+
else
|
230
|
+
return prime
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
add_and_subtract_for_step_6(smallest_uncovered_value)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def validate_and_pad
|
239
|
+
raise(ArgumentError, "Munkres matrix is empty") unless @matrix.first
|
240
|
+
raise(ArgumentError, "Munkres first row is empty") unless @matrix.first.size > 0
|
241
|
+
raise(ArgumentError, "Munkres matrix is not rectangular", caller) if @matrix.any? {|row| row.size != @matrix.first.size }
|
242
|
+
raise(ArgumentError, "Munkres matrix is wider than it is tall", caller) if @matrix.size < @matrix.first.size
|
243
|
+
|
244
|
+
if @matrix.size > @matrix.first.size
|
245
|
+
number_of_cols = @matrix.first.size
|
246
|
+
@matrix.each_with_index do |row, row_index|
|
247
|
+
(number_of_cols...@matrix.size).each do |col_index|
|
248
|
+
@matrix[row_index][col_index] = 0
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
if MODE_MAXIMIZE_UTIL == @mode
|
254
|
+
max_value = @matrix.flatten.max
|
255
|
+
@matrix.each_with_index do |row, row_index|
|
256
|
+
row.each_with_index do |value, col_index|
|
257
|
+
@matrix[row_index][col_index] = max_value - value
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
#for backwards compatiability with another library.
|
267
|
+
class Munkres::Problem < Munkres
|
268
|
+
def solve
|
269
|
+
find_pairings
|
270
|
+
end
|
271
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require "test/spec"
|
3
|
+
require "munkres"
|
4
|
+
|
5
|
+
context "An empty Munkres instance" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@saved_protected_instance_methods = Munkres.protected_instance_methods
|
9
|
+
x = @saved_protected_instance_methods
|
10
|
+
Munkres.class_eval { public *x }
|
11
|
+
@m = Munkres.new [[0]]
|
12
|
+
end
|
13
|
+
|
14
|
+
teardown do
|
15
|
+
x = @saved_protected_instance_methods
|
16
|
+
Munkres.class_eval { protected *x }
|
17
|
+
end
|
18
|
+
|
19
|
+
specify "should track a matrix of values" do
|
20
|
+
@m.matrix.should == [[0]]
|
21
|
+
end
|
22
|
+
|
23
|
+
specify "should track covered columns" do
|
24
|
+
@m.covered_columns.should == []
|
25
|
+
end
|
26
|
+
|
27
|
+
specify "should track covered rows" do
|
28
|
+
@m.covered_rows.should == []
|
29
|
+
end
|
30
|
+
|
31
|
+
specify "should track starred zeros" do
|
32
|
+
@m.starred_zeros.should == []
|
33
|
+
end
|
34
|
+
|
35
|
+
specify "should track primed zeros" do
|
36
|
+
@m.primed_zeros.should == []
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "A Munkres solving instance" do
|
41
|
+
setup do
|
42
|
+
@saved_protected_instance_methods = Munkres.protected_instance_methods
|
43
|
+
x = @saved_protected_instance_methods
|
44
|
+
Munkres.class_eval { public *x }
|
45
|
+
@m = Munkres.new [[1,2,3],[2,4,6],[3,6,9]]
|
46
|
+
end
|
47
|
+
|
48
|
+
teardown do
|
49
|
+
x = @saved_protected_instance_methods
|
50
|
+
Munkres.class_eval { protected *x }
|
51
|
+
end
|
52
|
+
|
53
|
+
specify "create_zero_in_rows should create a zero in each row of the matrix" do
|
54
|
+
@m.create_zero_in_rows
|
55
|
+
@m.matrix.should == [[0,1,2],[0,2,4],[0,3,6]]
|
56
|
+
end
|
57
|
+
|
58
|
+
specify "should be able to retrieve any row of the matrix" do
|
59
|
+
@m.matrix.row(2).should == [3,6,9]
|
60
|
+
end
|
61
|
+
|
62
|
+
specify "should be able to retrieve any column of the matrix" do
|
63
|
+
@m.matrix.column(1).should == [2,4,6]
|
64
|
+
end
|
65
|
+
|
66
|
+
specify "should be able to find the min value of any collection" do
|
67
|
+
@m.min_or_zero([3,0,2,1]).should == 0
|
68
|
+
end
|
69
|
+
|
70
|
+
specify "star_zeros should star the first zero in this example" do
|
71
|
+
@m.create_zero_in_rows
|
72
|
+
@m.star_zeros
|
73
|
+
@m.starred_zeros.should == [[0,0]]
|
74
|
+
end
|
75
|
+
|
76
|
+
specify "star_in_column? should indicate a star in a column" do
|
77
|
+
@m.starred_zeros << [0,0]
|
78
|
+
@m.star_in_column?(0).should.be true
|
79
|
+
@m.star_in_column?(1).should.be false
|
80
|
+
end
|
81
|
+
|
82
|
+
specify "star_in_row? should indicate a star in a row" do
|
83
|
+
@m.starred_zeros << [0,1]
|
84
|
+
@m.star_in_row?(0).should.be true
|
85
|
+
@m.star_in_row?(1).should.be false
|
86
|
+
end
|
87
|
+
|
88
|
+
specify "cover_columns_with_stars should cover the first colum" do
|
89
|
+
@m.create_zero_in_rows
|
90
|
+
@m.star_zeros
|
91
|
+
@m.cover_columns_with_stars
|
92
|
+
@m.covered_columns.should == [0]
|
93
|
+
end
|
94
|
+
|
95
|
+
specify "should be done if all columns are covered" do
|
96
|
+
@m.done?.should.be false
|
97
|
+
@m.covered_columns = [0,1,2]
|
98
|
+
@m.done?.should.be true
|
99
|
+
end
|
100
|
+
|
101
|
+
specify "should be able to prime the first uncovered zero" do
|
102
|
+
@m.matrix[0][1] = 0
|
103
|
+
@m.matrix[1][1] = 0
|
104
|
+
@m.prime_first_uncovered_zero.should == [0,1]
|
105
|
+
@m.primed_zeros.should == [[0,1]]
|
106
|
+
end
|
107
|
+
|
108
|
+
specify "should be able to find the smallest uncovered value" do
|
109
|
+
@m.covered_columns = [0,1]
|
110
|
+
@m.covered_rows = [0]
|
111
|
+
@m.smallest_uncovered_value.should == 6
|
112
|
+
end
|
113
|
+
|
114
|
+
specify "should be able to add a value to covered rows an subtract it from uncovered columns" do
|
115
|
+
@m.covered_columns = [1]
|
116
|
+
@m.covered_rows = [0,2]
|
117
|
+
@m.add_and_subtract_for_step_6(2)
|
118
|
+
@m.matrix.should == [[1,4,3],[0,4,4],[3,8,9]]
|
119
|
+
end
|
120
|
+
|
121
|
+
specify "should construct a series of primed and starred zeros and reset things" do
|
122
|
+
@m.matrix = [[0,0,1],[0,1,3],[0,2,5]]
|
123
|
+
@m.covered_rows = [0]
|
124
|
+
@m.starred_zeros = [[0,0]]
|
125
|
+
@m.primed_zeros = [[0,1], [1,0]]
|
126
|
+
@m.find_better_stars [1,0]
|
127
|
+
[[0,1], [1,0]].each {|star| @m.starred_zeros.should.include star }
|
128
|
+
@m.primed_zeros.should.be.empty
|
129
|
+
@m.covered_rows.should.be.empty
|
130
|
+
@m.covered_columns.should.be.empty
|
131
|
+
end
|
132
|
+
|
133
|
+
specify "should return a list of optimal pairings" do
|
134
|
+
optimal_pairings = [[0,2],[1,1],[2,0]]
|
135
|
+
@m.find_pairings.sort.should == optimal_pairings.sort
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
context "An oddly shaped Munkres matrix" do
|
141
|
+
setup do
|
142
|
+
@saved_protected_instance_methods = Munkres.protected_instance_methods
|
143
|
+
x = @saved_protected_instance_methods
|
144
|
+
Munkres.class_eval { public *x }
|
145
|
+
end
|
146
|
+
|
147
|
+
teardown do
|
148
|
+
x = @saved_protected_instance_methods
|
149
|
+
Munkres.class_eval { protected *x }
|
150
|
+
end
|
151
|
+
|
152
|
+
specify "should zero pad a tall skinny on initialize" do
|
153
|
+
m = Munkres.new [[1,2],[2,4],[3,6]]
|
154
|
+
|
155
|
+
m.matrix.should == [[1,2,0],[2,4,0],[3,6,0]]
|
156
|
+
end
|
157
|
+
|
158
|
+
specify "should raise an error for wide inputs" do
|
159
|
+
should.raise(ArgumentError) { Munkres.new [[1,2,3],[4,5,6]] }
|
160
|
+
end
|
161
|
+
|
162
|
+
specify "should raise an error for irregular inputs" do
|
163
|
+
should.raise(ArgumentError) { Munkres.new [[1,2],[1,2,3]] }
|
164
|
+
end
|
165
|
+
|
166
|
+
specify "should raise an error for an empty matrix" do
|
167
|
+
should.raise(ArgumentError) { Munkres.new [] }
|
168
|
+
end
|
169
|
+
|
170
|
+
specify "should raise an error for an empty row" do
|
171
|
+
should.raise(ArgumentError) { Munkres.new [[],[]] }
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
context "Complex examples with know solutions" do
|
178
|
+
specify "should solve first example" do
|
179
|
+
optimal_pairings = [[0,5],[1,1],[2,2],[3,3],[4,4],[5,0]]
|
180
|
+
m = Munkres.new [[3,4,5,6,2,1],[3,0,1,2,3,4],[7,6,0,2,1,1],[4,4,5,0,1,2],[0,1,0,1,0,0],[0,3,2,2,2,0]]
|
181
|
+
|
182
|
+
m.find_pairings.sort.should == optimal_pairings.sort
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
specify "should solve a larger example" do
|
187
|
+
optimal_pairings = []
|
188
|
+
m = Munkres.new [[4,1,2,3],
|
189
|
+
[6,9,2,4],
|
190
|
+
[1,0,3,7],
|
191
|
+
[10,4,6,6]]
|
192
|
+
m.find_pairings
|
193
|
+
m.total_cost_of_pairing.should == 10
|
194
|
+
end
|
195
|
+
|
196
|
+
specify "should solve a non-square example" do
|
197
|
+
optimal_pairings = [[[0, 3], [1, 2], [2, 1], [5, 0]],
|
198
|
+
[[0, 1], [1, 2], [2, 0], [5, 3]]]
|
199
|
+
m = Munkres.new [[4,1,2,3],
|
200
|
+
[6,9,2,4],
|
201
|
+
[1,0,3,7],
|
202
|
+
[10,4,6,6],
|
203
|
+
[5,7,5,9],
|
204
|
+
[2,2,14,3]]
|
205
|
+
optimal_pairings.sort.should.include m.find_pairings.sort
|
206
|
+
m.total_cost_of_pairing.should == 7
|
207
|
+
end
|
208
|
+
|
209
|
+
specify "should solve a very large example" do
|
210
|
+
# Profile the code
|
211
|
+
#require 'ruby-prof'
|
212
|
+
|
213
|
+
arr = []
|
214
|
+
100.times do |i|
|
215
|
+
arr[i] = []
|
216
|
+
100.times do |j|
|
217
|
+
arr[i][j] = rand 20
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
#result = RubyProf.profile do
|
222
|
+
m = Munkres.new arr
|
223
|
+
m.find_pairings
|
224
|
+
#end
|
225
|
+
|
226
|
+
#printer = RubyProf::GraphHtmlPrinter.new(result)
|
227
|
+
#File.open('profile.html', 'w') do |file|
|
228
|
+
# printer.print(file, {:min_percent => 30, :print_file => true})
|
229
|
+
#end
|
230
|
+
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: munkres
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paul Damer and Jim Wood
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-26 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A ruby implementation of the kuhn-munkres or 'hungarian' algorithm for bipartite matching.
|
17
|
+
email: pdamer@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/munkres.rb
|
26
|
+
- test/munkres_test.rb
|
27
|
+
- README.txt
|
28
|
+
- Rakefile
|
29
|
+
- History.txt
|
30
|
+
has_rdoc: true
|
31
|
+
homepage: http://github.com/pdamer/munkres
|
32
|
+
licenses: []
|
33
|
+
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
requirements: []
|
52
|
+
|
53
|
+
rubyforge_project: munkres
|
54
|
+
rubygems_version: 1.3.5
|
55
|
+
signing_key:
|
56
|
+
specification_version: 3
|
57
|
+
summary: A Ruby implementation of the Hungarian Algorithm
|
58
|
+
test_files:
|
59
|
+
- test/munkres_test.rb
|