Hi folks,
I'm terribly sorry for these repeated posts but I didn't like the
runtime behaviour of my second solution (especially in comparison to
Eric Mahurin's submission).
Here is a slightly modified version that makes cuts slightly earlier.
Up to 25 players appear acceptable on my laptop: 22 secs for 20 random
sets of 25 player each.
Regards,
Thomas.
#!/usr/bin/env ruby
def read_names(file)
data = {}
File.readlines(file).each do |l|
next if /^\s*%/ =~ l
name, *prefs = l.scan(/[^:[:space:]]+/)
data[name] = prefs if name
end
return data
end
def optimize(pairings, names, pos, posmax, maxweight=nil)
bestpairs = nil
maxweight ||= pairings.size ** 2 + 1
while pos < posmax
pair, weight1 = pairings[pos]
break unless weight1 * (names.size / 2).floor < maxweight
pos += 1
if names & pair == pair
names1 = names - pair
if names1.size < 2
bestpairs = [pair]
bestpairs << names1 unless names1.empty?
return [weight1, bestpairs]
elsif (rv = optimize(pairings, names1, pos, posmax,
maxweight - weight1))
maxweight, bestpairs = rv
maxweight += weight1
bestpairs << pair
end
end
end
return bestpairs && [maxweight, bestpairs]
end
def match_names(data)
names = data.keys.sort
# sort pairings
pairings = Hash.new {|h, k| h[k] = []}
maxpos = 0
names.each do |k|
data[k].each_with_index do |l, v|
w = v ** 2 + data[l].index(k) ** 2
maxpos = w if w > maxpos
pair = [k, l].sort
pairings[w] << pair unless pairings[w].include?(pair)
end
end
pairings1 = []
pairings.each do |pri, pairs|
pairings1.concat(pairs.zip([pri] * pairs.size))
end
pairings1 = pairings1.sort_by {|a, b| b}
# collect pairs
weight, pairs = optimize(pairings1, names, 0, pairings1.size)
return pairs
end
def assess(data, pairs)
pairs.inject(0) {|s, e| s + assess_pair(data, e)}
end
def assess_pair(data, pair)
a, b = pair
if a and b
va = data[a].index(b) ** 2
vb = data[b].index(a) ** 2
va + vb
else
0
end
end
if __FILE__ == $0
data = read_names(ARGV[0])
pairs = match_names(data)
puts pairs.map {|e| e.join(" ")}.join("\n")
puts assess(data, pairs) if $DEBUG
end