[/QUIZ] #65: Splitting the Loot

My submission is attached. The key insight: if you start with the largest values you can divide the loot one gang member at a time without fear of getting wrongly stuck. For an example of being wrongly stuck, consider:

% ruby loot.rb 3 1 1 1 2 2 2

The shares will all have to total 3, but if you start with the light end you might end up with (1, 1, 1) for the first guy, and then the rest of the loot can't be evenly split. Starting with the heavy end, though, gets you to the right place.

Technically, this is not so much an insight as an intuition. It might not be true in all cases. But using it as a heuristic, we can cut the solution down to O(n^3).

The "split" function takes a list of values and the number of gang members and returns a list of sublists that all sum to the same amount, or nil if no solution is possible. It works by sorting the list of values and calling "pick" once for each gang member to pick out that member's share from the remaining list. Using our insight, we never backtrack in "split". Once we've picked one member's share, we never go back to try a different combination.

"Pick" works recursively, walking from the high end of the list to the low end, until it finds a sublist that sums to the target amount or it runs off the end of the list. This function waits to delete elements from the list until it finds a sublist that sums to the target amount. In this way, "pick" doesn't have to create any intermediate lists.

Luke Blanshard

loot.rb (1.48 KB)

My solution is attached. It solves via recursion with a few
optimizations (it is still really slow, though)

* it checks, if the sum of all pieces is divisable by the number of persons,
   if not, then there is no solution.
* it avoids giving every piece to the first person at the beginning

Two interesting methods I used:

  module Enumerable
    def all_equal?
      self.inject { |a,b| break false unless a == b; b } != false }
    end
  end
  [3,3,3,3].all_equal? #=> true

  class Object
    def if_false; self ? self : yield; end;
  end

This works nicely for Enumerable#find:

  [1,2,3,4,5].find {|x| x > 10}.if_false { warn "nothing found"; -1 } #=> -1

-Levin

q65_levin_alexander.rb (1.89 KB)

My submission is attached. The key insight: if you start with the
largest values you can divide the loot one gang member at a time without
fear of getting wrongly stuck.

...

Technically, this is not so much an insight as an intuition. It might
not be true in all cases. But using it as a heuristic, we can cut the
solution down to O(n^3).

I thought I had a similar speedy solution, but then I found a few
places it fails:
Try splitting [4, 6, 8, 9, 18, 26, 34, 36, 39, 40, 43, 50, 55, 66, 67,
76, 82, 83, 86, 91, 96] into 5 parts.

The top-down approach fails to find an even division, but it is possible:
0: 82 66 55
1: 86 67 50
2: 91 76 36
3: 96 83 18 6
4: 43 40 39 34 26 9 8 4

I used the following to generate a bunch of test cases (not all of
which are solvable):
  r =
  n=rand(8)+2
  20.times{|e| r<< rand(100); }
  r << n-r.sum%n i #make sure it's divisible
  p n,r

-Adam

···

On 2/5/06, Luke Blanshard <luke@blanshard.us> wrote:

I wrote:

...
Technically, this is not so much an insight as an intuition. It might not be true in all cases...

D'oh! Here's my second solution, which does backtrack if necessary. It handles Adam's example, and a shorter example I found:

$ ruby loot.rb 3 3 3 2 2 2 2 2 2 2 1
This loot can't be evenly split 3 ways

$ ruby loot2.rb 3 3 3 2 2 2 2 2 2 2 1
1: 2 2 3
2: 2 2 3
3: 1 2 2 2

$ ruby loot.rb 5 4 6 8 9 18 26 34 36 39 40 43 50 55 66 67 76 82 83 86 91 96
This loot can't be evenly split 5 ways

$ ruby loot2.rb 5 4 6 8 9 18 26 34 36 39 40 43 50 55 66 67 76 82 83 86 91 96
1: 4 8 9 86 96
2: 6 39 67 91
3: 18 26 76 83
4: 55 66 82
5: 34 36 40 43 50

This one uses recursive yielding to manage the search space. It should be close to the first solution in speed for cases where the heuristic applies, except for the array duping.

Luke Blanshard

loot2.rb (2.11 KB)

Adam Shelly wrote:

···

On 2/5/06, Luke Blanshard <luke@blanshard.us> wrote:
  

My submission is attached. The key insight: if you start with the
largest values you can divide the loot one gang member at a time without
fear of getting wrongly stuck.
    

...
  

Technically, this is not so much an insight as an intuition. It might
not be true in all cases. But using it as a heuristic, we can cut the
solution down to O(n^3).

I thought I had a similar speedy solution, but then I found a few
places it fails:...
  
Rats, you're right. I came upon an example too. Oh well.

Luke

Here is an updated version that stops recursing, if someone gets
assigned more than his share. (I can not *believe* that I did not
think of this earlier) This speeds everything up quite a lot.

-Levin

q65_levin_alexander.rb (1.91 KB)

···

On 2/5/06, Levin Alexander <levin@grundeis.net> wrote:

My solution is attached. It solves via recursion with a few
optimizations (it is still really slow, though)

Looks like there are multiple correct splits for this one:

$ ruby loot.rb 5 4 6 8 9 18 26 34 36 39 40 43 50 55 66 67 76 82 83 86 91 96
1: 4 6 8 9 18 26 39 43 50
2: 34 83 86
3: 36 76 91
4: 40 67 96
5: 55 66 82

James Edward Gray II

···

On Feb 6, 2006, at 3:57 AM, Luke Blanshard wrote:

$ ruby loot2.rb 5 4 6 8 9 18 26 34 36 39 40 43 50 55 66 67 76 82 83 86 91 96
1: 4 8 9 86 96
2: 6 39 67 91
3: 18 26 76 83
4: 55 66 82
5: 34 36 40 43 50

Can you share any of these special cases??

···

On Feb 5, 2006, at 14:38, Luke Blanshard wrote:

Adam Shelly wrote:

On 2/5/06, Luke Blanshard <luke@blanshard.us> wrote:

My submission is attached. The key insight: if you start with the
largest values you can divide the loot one gang member at a time without
fear of getting wrongly stuck.

...

Technically, this is not so much an insight as an intuition. It might
not be true in all cases. But using it as a heuristic, we can cut the
solution down to O(n^3).

I thought I had a similar speedy solution, but then I found a few
places it fails:...

Rats, you're right. I came upon an example too. Oh well.

James Edward Gray II wrote:

$ ruby loot2.rb 5 4 6 8 9 18 26 34 36 39 40 43 50 55 66 67 76 82 83 86 91 96
1: 4 8 9 86 96
2: 6 39 67 91
3: 18 26 76 83
4: 55 66 82
5: 34 36 40 43 50

Looks like there are multiple correct splits for this one:

$ ruby loot.rb 5 4 6 8 9 18 26 34 36 39 40 43 50 55 66 67 76 82 83 86 91 96
1: 4 6 8 9 18 26 39 43 50
2: 34 83 86
3: 36 76 91
4: 40 67 96
5: 55 66 82

James Edward Gray II

My one produces a similar solution

ruby loot.rb 5 4 6 8 9 18 26 34 36 39 40 43 50 55 66 67 76 82 83 86 91 96

1: 26 86 91
2: 40 67 96
3: 55 66 82
4: 8 36 76 83
5: 4 6 9 18 34 39 43 50

···

On Feb 6, 2006, at 3:57 AM, Luke Blanshard wrote:

Try splitting [4, 6, 8, 9, 18, 26, 34, 36, 39, 40, 43, 50, 55, 66, 67,
76, 82, 83, 86, 91, 96] into 5 parts.

···

On 2/5/06, Dave Howell <groups@grandfenwick.net> wrote:

Can you share any of these special cases??