[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

[QUIZ] Preferable Pairs (#165

Matthew Moss

6/6/2008 5:44:00 PM

[Note: parts of this message were removed to make it a legal post.]

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Quiz 2:

1. Please do not post any solutions or spoiler discussion for this
quiz until 48 hours have passed from the time on this message.

2. Support Ruby Quiz 2 by submitting ideas as often as you can! (A
permanent, new website is in the works for Ruby Quiz 2. Until then,
please visit the temporary website at

<http://splatbang.com/rub....

3. Enjoy!
Suggestion: A [QUIZ] in the subject of emails about the problem
helps everyone on Ruby Talk follow the discussion. Please reply to
the original quiz message, if you can.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

## Preferable Pairs (#156)

Imagine a pairs tournament: a competition where every player partners up
with another for the duration of the tournament. Everyone plays in a pair
and wins or loses in a pair.

But everyone arrives individually, and only pairs up at the start. Every
player has preferences for a partner; your task is to try and optimize those
preferences as best as possible.

The input is a series of lines containing names, such as:

David: Helen Vicki Joseph
Helen: David Vicki Joseph
Joseph: Vicki Helen David
Vicki: David Helen Joseph

Each line represents the preferences of the first named player (i.e. before
the colon). That player's preferred parters are listed after the colon, in
order of preference (i.e. most preferred first, least preferred last).

The output of your code should be the pairings, one pair per line. For the
example above, your output should look like this:

David Helen
Joseph Vicki

If an odd number of people were provided at the start, then one person will
be left out. That person's name should be output alone on the last line.

What determines the best pairing? A score will be calculated for your output
against the input. For each player, the partner is found in the player's
ordered list, as an index. So, for the above example:

David: 0
Helen: 0
Joseph: 0
Vicki: 2

Each individual's score is then squared, then all are added together. Here,
the final score is 4. The _lower_ your score, the better the match.

--
Matthew Moss <matthew.moss@gmail.com>

16 Answers

Matthew Moss

6/6/2008 6:01:00 PM

0

Here is some quick code to generate sample preferences. Takes a single
optional argument (defaults to 10) which is the number of people to
generate. (I've got 34 names in here; if you want to try larger sets,
you'll have to add more names.)



NAMES = %w(Adam Anna Bob Betty Chuck David Daria Evan Ellen Fred Faye
Greg Gail Hank Helen Irene John Janet Laura Matt Maria Nadine Oleg
Olivia Peter Pamela Ric
k Rosa Steve Susan Theo Tracy Vicki Walter)

def sample(n)
names = NAMES.sort_by { rand }[0, n].sort
names.each do |name|
others = names.dup.reject { |other| other == name }.sort_by
{ rand }
puts "#{name}: #{others.join(' ')}"
end
end

sample(Integer(ARGV[0] || 10))

Matthew Moss

6/6/2008 7:58:00 PM

0

In addition to the sample generation code, here are some examples with
output produced from the initial version of my solution. I *think* I
have a good algorithm, but I haven't proven that it generates the
optimal solution.

Example A input:
Bob: Ellen Evan Vicki Helen Theo David Tracy Daria Matt
Daria: Helen Evan Ellen David Bob Matt Tracy Theo Vicki
David: Helen Matt Bob Daria Vicki Theo Evan Tracy Ellen
Ellen: Bob Daria Helen Evan Matt Theo Tracy Vicki David
Evan: Bob David Tracy Ellen Daria Vicki Helen Matt Theo
Helen: Theo Daria Tracy Bob Evan Matt Vicki David Ellen
Matt: Helen Ellen David Bob Theo Daria Tracy Evan Vicki
Theo: Ellen Bob David Tracy Vicki Helen Matt Daria Evan
Tracy: Evan Daria Bob Matt Ellen Vicki Theo Helen David
Vicki: Tracy Daria Theo Helen Bob Ellen Matt Evan David

Example A output:
Ellen Bob
Daria Helen
Tracy Evan
David Matt
Vicki Theo

Example B input:
David: John Hank Evan Gail Walter
Evan: David Hank Gail Walter John
Gail: David John Walter Evan Hank
Hank: Gail John Walter David Evan
John: Hank David Evan Gail Walter
Walter: Evan John Hank Gail David

Example B output:
David John
Walter Evan
Hank Gail

Example C input:
Anna: Betty Rosa Daria Helen Ellen Theo
Betty: Rosa Daria Anna Helen Theo Ellen
Daria: Rosa Theo Ellen Helen Betty Anna
Ellen: Betty Helen Rosa Daria Theo Anna
Helen: Theo Daria Anna Rosa Ellen Betty
Rosa: Daria Ellen Helen Betty Theo Anna
Theo: Daria Ellen Helen Betty Anna Rosa

Example C output:
Rosa Daria
Betty Anna
Helen Theo
Ellen

ThoML

6/7/2008 6:21:00 AM

0

> What determines the best pairing? A score will be calculated for your output
> against the input. For each player, the partner is found in the player's
> ordered list, as an index. So, for the above example:
>
> David: 0
> Helen: 0
> Joseph: 0
> Vicki: 2

What is the penalty for "unwanted" pairings, e.g.

David: Helen Vicki
Helen: David
Joseph: Vicki Helen
Vicki: David

with output:

David Joseph
Helen Vicki

Or is the list of preferences always complete, i.e. it always contains
all other players?

Matthew Moss

6/7/2008 3:42:00 PM

0



On Jun 7, 1:23=A0am, ThoML <micat...@gmail.com> wrote:
> > What determines the best pairing? A score will be calculated for your ou=
tput
> > against the input. For each player, the partner is found in the player's=

> > ordered list, as an index. So, for the above example:
>
> > =A0 =A0 David: 0
> > =A0 =A0 Helen: 0
> > =A0 =A0 Joseph: 0
> > =A0 =A0 Vicki: 2
>
> What is the penalty for "unwanted" pairings, e.g.
>
> =A0 =A0 David: Helen Vicki
> =A0 =A0 Helen: David
> =A0 =A0 Joseph: Vicki Helen
> =A0 =A0 Vicki: David
>
> with output:
>
> =A0 =A0 David Joseph
> =A0 =A0 Helen Vicki
>
> Or is the list of preferences always complete, i.e. it always contains
> all other players?

Yes, always complete, the least preferred players will be toward the
end.

Eric I.

6/8/2008 3:06:00 AM

0

On Jun 6, 3:57 pm, Matthew Moss <matthew.m...@gmail.com> wrote:
> In addition to the sample generation code, here are some examples with
> output produced from the initial version of my solution. I *think* I
> have a good algorithm, but I haven't proven that it generates the
> optimal solution.

> Example B input:
> David: John Hank Evan Gail Walter
> Evan: David Hank Gail Walter John
> Gail: David John Walter Evan Hank
> Hank: Gail John Walter David Evan
> John: Hank David Evan Gail Walter
> Walter: Evan John Hank Gail David
>
> Example B output:
> David John
> Walter Evan
> Hank Gail

I think that scores 26. A score of 18 is achievable (see below),
although I don't know if it's optimal. With only 15 (I think)
potential partnership schemes when there are six people, it could be
verified, although I haven't done it.

David Evan
Hank John
Gail Walter

By the way, if there are n people, I believe the formula for the
number of partnership schemes is:

n! / (n / 2)! / 2**(n / 2)

Eric

====

LearnRuby.com offers Rails & Ruby HANDS-ON public & ON-SITE
workshops.
Ruby Fundamentals Wkshp June 16-18 Ann Arbor, Mich.
Ready for Rails Ruby Wkshp June 23-24 Ann Arbor, Mich.
Ruby on Rails Wkshp June 25-27 Ann Arbor, Mich.
Ruby Plus Rails Combo Wkshp June 23-27 Ann Arbor, Mich
Please visit http://Lea... for all the details.

Matthew Moss

6/8/2008 4:12:00 AM

0

> > Example B output:
> > David John
> > Walter Evan
> > Hank Gail
>
> I think that scores 26. =A0A score of 18 is achievable (see below),
> although I don't know if it's optimal. =A0With only 15 (I think)
> potential partnership schemes when there are six people, it could be
> verified, although I haven't done it.
>
> David Evan
> Hank John
> Gail Walter

Correct you are, sir. My algorithm is greedy, but after a certain
transform was done, which I had hoped would make greedy acceptable. I
guess not.

Back to the drawing board...

Eric I.

6/8/2008 6:11:00 PM

0

I used a genetic algorithm that tries to find the best solution
possible in a given number of iterations. It doesn't always find the
best, but generally does pretty well. And because there are
randomized aspects to each run, you can end up with different
solutions (of different scores) on consecutive runs. The code can be
found at:

http://learnruby.com/examples/ruby-quiz...

Eric

====

LearnRuby.com offers Rails & Ruby HANDS-ON public & ON-SITE
workshops.
Ruby Fundamentals Wkshp June 16-18 Ann Arbor, Mich.
Ready for Rails Ruby Wkshp June 23-24 Ann Arbor, Mich.
Ruby on Rails Wkshp June 25-27 Ann Arbor, Mich.
Ruby Plus Rails Combo Wkshp June 23-27 Ann Arbor, Mich
Please visit http://Lea... for all the details.

Eric I.

6/8/2008 6:14:00 PM

0

On Jun 7, 11:05 pm, "Eric I." <rubytrain...@gmail.com> wrote:
> By the way, if there are n people, I believe the formula for the
> number of partnership schemes is:
>
>     n! / (n / 2)! / 2**(n / 2)

An alternative (and more efficient) way to compute the number of
partnership schemes is to multiply all the odd values from 1..n. For
example, if there are eight people, then we have 1 * 3 * 5 * 7, or
105.

Eric

====

LearnRuby.com offers Rails & Ruby HANDS-ON public & ON-SITE
workshops.
   Ruby Fundamentals Wkshp          June 16-18     Ann Arbor, Mich.
   Ready for Rails Ruby Wkshp       June 23-24     Ann Arbor, Mich.
   Ruby on Rails Wkshp              June 25-27     Ann Arbor, Mich.
   Ruby Plus Rails Combo Wkshp      June 23-27     Ann Arbor, Mich
Please visithttp://LearnR... all the details.

ThoML

6/8/2008 6:30:00 PM

0

Stable roommates or not, I have two solutions. The second one is
supposed to be exact. No fancy genetic batteries included.

Regards,
Thomas.


======================================================== #1
#!/usr/bin/env ruby19

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 match_names(data)
names = data.keys

# sort pairings
mt = Hash.new {|h, k| h[k] = []}
names.each do |k|
data[k].each_with_index do |l, v|
mt[v ** 2 + data[l].index(k) ** 2] << [k, l]
end
end

# collect pairs
pairs = []
catch(:exit) do
mt.entries.sort.each do |pri, opairs|
opairs.each do |ab|
if ab.all? {|n| names.include?(n)}
pairs << ab
names -= ab
throw :exit if names.size < 2
end
end
end
end

# add remaining singles
pairs << names unless names.empty?

return pairs
end


if __FILE__ == $0
data = read_names(ARGV[0])
pairs = match_names(data)
puts pairs.map {|e| e.join(" ")}.join("\n")

if $DEBUG
def assess(data, pairs)
pairs.inject(0) do |s, e|
a, b = e
if a and b
va = data[a].index(b) ** 2
vb = data[b].index(a) ** 2
s + va + vb
else
s
end
end
end
puts assess(data, pairs)
end
end



======================================================== #2
#!/usr/bin/env ruby19

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(data, pairings, names, range, pairs=[], weight=0,
maxweight=nil)
bestpairs = nil
maxweight ||= pairings.keys.max ** 2 + 1
for pos in range
weight1 = weight + pos
break if weight1 >= maxweight
pairings[pos].each do |pair|
if names & pair == pair
pairings[pos].delete(pair)
pairs << pair
begin
names1 = names - pair
if names1.size < 2
bestpairs = pairs.dup
bestpairs << names1 unless names1.empty?
return [weight1, bestpairs]
elsif pos < maxweight - weight1
w1, p1 = optimize(data, pairings, names1,
pos..range.max, pairs, weight1, maxweight)
if p1 and w1 < maxweight
maxweight = w1
bestpairs = p1
end
end
ensure
pairs.pop
pairings[pos].unshift(pair)
end
end
end
end
return [maxweight, bestpairs]
end


def match_names(data)
names = data.keys

# 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
pairings[w] << [k, l]
end
end

# collect pairs
weight, pairs = optimize(data, pairings, names, 0..maxpos)

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


Dustin Barker

6/8/2008 8:40:00 PM

0

Here's my solution - I think I've got it, but haven't found a way to
prove whether or not it's optimal yet ;-)

-Dustin

# parse input file
# make a hash, key is player, value is array of preferred matches
# in order of preference
preferences = {}
File.open(ARGV[0]) do |file|
while line = file.gets
a = line.split
player = a[0][0...a[0].size-1]
preferences[player] = a[1...a.size]
end
end

# score each individual preference, first choice gets 0 score
# i.e. lower scores are better
keys = preferences.keys

matrix = keys.collect { |k| Array.new }
keys.each_with_index do |player, i|
keys.each_with_index do |oplayer, j|
next if j < i + 1 # process above main diagonal
# storing score, which is position of oplayer in preference list
s1 = preferences[player].index(oplayer)
s2 = preferences[oplayer].index(player)
score = s1 + s2
entry = [player, oplayer, score]
matrix[i].push(entry)
matrix[j].push(entry)
end
end

matrix.size.times do |i|
matrix[i] = matrix[i].sort { |a,b| a[2] <=> b[2] }
end

matches = []

(matrix.size - 2).downto(1) do |i|
matrix = matrix.sort { |a,b| b[i][2] <=> a[i][2] }
end

while matrix.size > 0
matrix = matrix.sort { |a,b| a[0][2] <=> b[0][2] }

match = matrix.first.first
matches.push([match[0], match[1]])

matrix.size.times do |i|
matrix[i].delete_if do |pmatch|
pmatch.include?(match[0]) || pmatch.include?(match[1])
end
end
matrix.delete_if { |row| row.empty? }

end

matches.each do |match|
puts "#{match[0]} #{match[1]}"
end

# find the odd one...
puts keys - matches.flatten.uniq