[lnkForumImage]
TotalShareware - Download Free Software

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


 

Forums >

comp.lang.ruby

[QUIZ] Splitting the Loot (#65

James Gray

2/3/2006 3:21:00 PM

The three rules of Ruby Quiz:

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 by submitting ideas as often as you can:

http://www.rub...

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion.

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

You, and your trusty band of adventurers, have stumbled upon a hidden cache of
rubies! (What luck, eh?) Not all gems are created equal, so you sneak them
home and take your time evaluating the stones. The find was an equal effort,
and you're all horribly greedy, so you must find a fair system for dividing up
the gems.

This week's Ruby Quiz is to write a program that fairly divides treasures, based
on worth.

The first command-line argument to the program will be the number of adventures.
All other arguments are the numerical values of treasures found. You're program
should output a fair split of the treasures, if possible, or a warning message
if a fair split cannot be found.

Examples:

$ ruby loot.rb 3 1 2 3 4
It is not possible to fairly split this treasure 3 ways.
$ ruby loot.rb 2 9 12 14 17 23 32 34 40 42 49
1: 9 12 32 34 49
2: 14 17 23 40 42


28 Answers

Luke Blanshard

2/5/2006 12:59:00 PM

0

Aditya Mahajan wrote:
>> The first command-line argument to the program will be the number of
>> adventures.
>> All other arguments are the numerical values of treasures found.
>> You're program
>> should output a fair split of the treasures, if possible, or a
>> warning message
>> if a fair split cannot be found.
>>
>> ...
> Can one assume that the treasure values are only integers?
I certainly hope so.


Patrick Hurley

2/5/2006 2:45:00 PM

0

On 2/5/06, Luke Blanshard <luke@blanshard.us> wrote:
> Aditya Mahajan wrote:
> >> The first command-line argument to the program will be the number of
> >> adventures.
> >> All other arguments are the numerical values of treasures found.
> >> You're program
> >> should output a fair split of the treasures, if possible, or a
> >> warning message
> >> if a fair split cannot be found.
> >>
> >> ...
> > Can one assume that the treasure values are only integers?
> I certainly hope so.
>
>

I just certainly hope there is not too many treasures, I am pretty
sure this is NP complete.


Christian Neukirchen

2/5/2006 3:05:00 PM

0

Patrick Hurley <phurley@gmail.com> writes:

> On 2/5/06, Luke Blanshard <luke@blanshard.us> wrote:
>> Aditya Mahajan wrote:
>> >> The first command-line argument to the program will be the number of
>> >> adventures.
>> >> All other arguments are the numerical values of treasures found.
>> >> You're program
>> >> should output a fair split of the treasures, if possible, or a
>> >> warning message
>> >> if a fair split cannot be found.
>> >>
>> >> ...
>> > Can one assume that the treasure values are only integers?
>> I certainly hope so.
>
> I just certainly hope there is not too many treasures, I am pretty
> sure this is NP complete.

Trying not to criticize the Quiz too harsh, but I think the amount of
NP-complete quizzes got pretty high... couldn't we have some quizzes
that can be solved without brute-forcing or heuristics?

--
Christian Neukirchen <chneukirchen@gmail.com> http://chneuk...


Antonio Cangiano

2/5/2006 3:27:00 PM

0

Patrick Hurley wrote:
> I just certainly hope there is not too many treasures, I am pretty
> sure this is NP complete.

It's the famous Subset Sum Problem (a special case of the knapsack
problem), and generally it's an NP-Complete problem.

Cheers,
Antonio
--
Zen and the Art of Ruby Programming
http://antonioca...

Patrick Deuster

2/5/2006 3:41:00 PM

0

Ok, the 48 hours passed just now and as I have to leave for a day or two
I post my solution early.

The splitting the loot problem is actually a problem known as the
"Knapsack problem" or the "Subset sum problem".
http://en.wikipedia.org/wiki/Subset_s...

I solved the problem how I learned it at university, by walking through
a tree.
I hand the loot and the target value over to the knapsack solver and
remove the result from the loot until either the loot is empty or
solving the problem failed.

Here's my complete solution:

class Array
def sum
inject { |s,x| s + x }
end
def delete_one! n
(i = index(n)) ? delete_at(i) : nil
end
def count n
inject(0) { |c,x| x == n ? c+1 : c }
end
end

class Knapsack
def initialize target, numbers
@target,@numbers = target, numbers
end
def solve
solver @numbers.map { |n| [n] }
end
def solver paths
new_paths = Array.new
paths.each do |path|
return path if path.sum == @target
@numbers.each do |n|
unless path.count(n)>=@numbers.count(n) || path.sum+n > @target
new_path = path.dup
new_path << n
new_paths << new_path
return new_path if new_path.sum == @target
end
end
end
return nil if new_paths.empty?
solver new_paths
end
end

adventures,loot = ARGV.shift.to_i,ARGV.map { |a| a.to_i }
fair_split,stakes = loot.sum/adventures,Array.new

begin
stake = Knapsack.new(fair_split,loot).solve
stakes << stake
stake.each { |s| loot.delete_one!(s) } unless stake.nil?
end until stake.nil? || loot.empty?

if stakes.include?nil
puts "It is not possible to fairly split this treasure #{adventures}
ways."
else
stakes.size.times { |i| puts "#{i+1}: " + stakes[i].sort.join(" ") }
end


James Gray

2/5/2006 4:00:00 PM

0

On Feb 5, 2006, at 12:09 AM, Aditya Mahajan wrote:

>> The first command-line argument to the program will be the number
>> of adventures.
>> All other arguments are the numerical values of treasures found.
>> You're program
>> should output a fair split of the treasures, if possible, or a
>> warning message
>> if a fair split cannot be found.
>>
>> Examples:
>>
>> $ ruby loot.rb 3 1 2 3 4
>> It is not possible to fairly split this treasure 3 ways.
>> $ ruby loot.rb 2 9 12 14 17 23 32 34 40 42 49
>> 1: 9 12 32 34 49
>> 2: 14 17 23 40 42
>
> Can one assume that the treasure values are only integers?

Yes, they are.

James Edward Gray II


Luke Blanshard

2/5/2006 4:16:00 PM

0

Patrick Deuster wrote:
> ...
> The splitting the loot problem is actually a problem known as the
> "Knapsack problem" or the "Subset sum problem".
> http://en.wikipedia.org/wiki/Subset_s...
I don't believe that this problem is equivalent to the subset sum
problem, because all of the numbers involved are positive. Different
animal.

Luke Blanshard



Manuel Kasten

2/5/2006 4:51:00 PM

0

=begin ############################################################

Hello,
this is my first participation in a Ruby Quiz. I used a simple
recursive algorithm, so don't expect speed wonders from this one.
I'm sure there are faster and more elegant solutions out there.
Nevertheless, I had fun implementing this.
Oh, and I swapped the adventurers for pirates, because they gave
me a headache spelling them. (adventurerers, adventurererer.. ?)

Manuel Kasten

=end ############################################################

class SplitIt
def initialize pirates, treasure
@pirates = pirates
@treasure = treasure
@bags = []
(0...@pirates).each{ |pirate| @bags[pirate] = [[], 0] }
loot = @treasure.inject(0){ |res, gem| res + gem }
done unless loot % @pirates == 0
@share = loot/@pirates
end
def go
done if @treasure.length == 0
gem = @treasure.pop
(0...@pirates).each do |pirate|
if @bags[pirate][1] + gem <= @share
@bags[pirate][1] += gem
@bags[pirate][0].push gem
go
@bags[pirate][0].pop
@bags[pirate][1] -= gem
# it doesn't matter which pirate is which,
# as long as their bags are empty
break if @bags[pirate][1] == 0
end
end
@treasure.push gem
end
def done
puts
if (@treasure.length == 0)
@bags.each_with_index do |bag, pirate|
puts "#{pirate+1}: #{bag[0].sort.inspect}"
end
else
puts "The #{@pirates} pirates won't be able to " +
"split their loot fairly. Take cover!"
end
exit
end
end

if $0 == __FILE__
pirates = ARGV.shift.to_i
treasure = ARGV.map{ |gem| gem.to_i }.sort
si = SplitIt.new(pirates, treasure)
si.go
si.done
end

soxinbox

2/5/2006 7:25:00 PM

0


#booty
class Array
def sum
inject(0){|v,e| v += e.to_i}
end
end
class PileOfBooty
attr :sum
def initialize
@sum = 0
@pile = []
end
def add(i)
@sum += i.to_i
@pile << i.to_i
end
def rem
r = @pile.pop
@sum -= r
r
end
def sort!
@pile.sort!
end
end

def sumit(piles,treasure,divy)
if treasure.sum == 0
return piles
else
ruby = treasure.rem
piles.size.times{|i| #try adding the ruby to each pirate's pile in
turn
piles[i].add ruby #add the ruby to the this pile
if piles[i].sum <= divy and sumit(piles,treasure,divy) != nil
return (piles) #that worked ok, now divy up the rest of the booty
else
piles[i].rem #that didn't work, take the ruby back
end
}
treasure.add ruby #couldn't find a soultion from here, put the ruby
back in the booty pile and return nil
return nil
end
end
def dumpit ( piles,n )
print "\n\n"
if piles == nil
print "It bees not possible to divy the booty amongst #{n} pirates,
ARRRGH!\n"
else
piles.each_index{|i|
piles[i].sort!
print "#{i}:"
print " #{piles[i].rem}" while piles[i].sum != 0
print "\n"
}
end
end

n=ARGV.shift.to_i #number of pirates
treasure = PileOfBooty.new
ARGV.each{|e| treasure.add e} #collection of rubys to divy up
divy = treasure.sum/n #each pirate's share
piles = []
n.times{piles << PileOfBooty.new} #a pile of booty for each pirate
dumpit( sumit(piles,treasure,divy) ,n)



"Ruby Quiz" <james@grayproductions.net> wrote in message
news:20060203152022.EJEN8318.centrmmtao04.cox.net@localhost.localdomain...
> The three rules of Ruby Quiz:
>
> 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 by submitting ideas as often as you can:
>
> http://www.rub...
>
> 3. Enjoy!
>
> Suggestion: A [QUIZ] in the subject of emails about the problem helps
> everyone
> on Ruby Talk follow the discussion.
>
> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
>
> You, and your trusty band of adventurers, have stumbled upon a hidden
> cache of
> rubies! (What luck, eh?) Not all gems are created equal, so you sneak
> them
> home and take your time evaluating the stones. The find was an equal
> effort,
> and you're all horribly greedy, so you must find a fair system for
> dividing up
> the gems.
>
> This week's Ruby Quiz is to write a program that fairly divides treasures,
> based
> on worth.
>
> The first command-line argument to the program will be the number of
> adventures.
> All other arguments are the numerical values of treasures found. You're
> program
> should output a fair split of the treasures, if possible, or a warning
> message
> if a fair split cannot be found.
>
> Examples:
>
> $ ruby loot.rb 3 1 2 3 4
> It is not possible to fairly split this treasure 3 ways.
> $ ruby loot.rb 2 9 12 14 17 23 32 34 40 42 49
> 1: 9 12 32 34 49
> 2: 14 17 23 40 42
>
>


Adam Shelly

2/5/2006 10:03:00 PM

0

"Ruby Quiz" <james@grayproductions.net> wrote in message
> This week's Ruby Quiz is to write a program that fairly divides treasures,
> based on worth.

Here's mine. I reused several methods from the wierd numbers quiz.

#############################################
#loot.rb
#Adam Shelly
#evenly splits an array into N parts with equal value , if possible

class Array
def sum
inject(0){|s,v| s+v}
end

def subtract arr
return clear if arr==self
arr.each{|e| if (n=index(e)) then delete_at(n); end }
self
end

#fast version which misses some subsets.
#useful as a rough filter.
def quick_find_subset_with_sum n
a = self.sort.reverse
sum,set = 0,[]
a.each {|e|
if (sum+e <= n)
sum+=e
set<<e
return set if sum == n
end
}
nil
end

def find_subset_with_sum n
s = quick_find_subset_with_sum n
return s if s
possibilities, seen = [self.select{|e| e<=n}],{}
until possibilities.empty?
candidate = possibilities.pop
diff = candidate.sum - n
return candidate if diff == 0
break if diff < 0
candidate.each_with_index{|e,i|
break if e > diff
new_cand = (candidate.dup)
new_cand.delete_at(i)
return new_cand if e == diff
possibilities << new_cand if !seen[new_cand]
seen[new_cand]=true
}
end
nil
end
end


# Splitter algorithm
#1: put all loot in pile 1
#2: find a share from pile 1
#3: if you can't find one, it can't be split
#4: find a share in the remaining pile
#5: repeat unitl you find all shares
#6: if you can't find enough shares
#7: move the first share to pile2
#8: repeat from step 2, but add pile2 to the remainder in step 4
# this serves to shuffle the possible combinations, until you find one
that works.


def splitter n, loot
splits=[]
pile1,pile2=loot.dup.sort.reverse,[]
total = loot.sum
share = total/n
return nil if total%n != 0 || loot.size < n || loot.max > share

until pile1.empty?
splits[0] = pile1.find_subset_with_sum(share)
break if !splits[0]
remaining = pile1.subtract(splits[0])+pile2
(1...n).each do |i|
break if nil == (splits[i] = remaining.find_subset_with_sum(share))
remaining.subtract(splits[i])
end
return splits if splits[n-1]
pile2 += splits[0]
end
return nil
end

if __FILE__ == $0

if ARGV.size < 2 || ARGV[0].to_i < 1
puts "Usage: #{$0} partners item1 item2 ..."
else
shares = splitter(ARGV.shift.to_i, ARGV.map{|a| a.to_i })
if !shares
puts "This loot can not be evenly divided into #{n} parts!"
else
shares.each_with_index{|share,i| puts "#{i}: #{share.join(' ')}"}
puts "everyone gets #{shares[0].sum}"
end
end

end


#############################################

-Adam