The Coconuts Problem...in Ruby

I thought I'd give a Ruby solution a try. Here is my version:

PEOPLE = 5
initial_pile_size = PEOPLE
loop do
  initial_pile_size += 1
  pile_size = initial_pile_size
  puts "initial_pile_size=#{initial_pile_size}" if $DEBUG

  # loop through each person's split
  # rc will be 1 if loop runs to completion
  rc = 1.upto(PEOPLE) do |i|

    # calculates: pile_size divided by PEOPLE
    quotient, remainder = pile_size.divmod(PEOPLE)
    puts "... |#{i}| pile_size=#{pile_size} quotient=#{quotient} remainder=#{remainder}" if $DEBUG

    # set rc to -1 if this is not a solution
    break -1 if remainder != 1

    pile_size = quotient * (PEOPLE-1)

  end
  next if rc < 0

  # final pile should divide evenly
  quotient, remainder = pile_size.divmod(PEOPLE)
  puts "... |*| pile_size=#{pile_size} quotient=#{quotient} remainder=#{remainder}" if $DEBUG
  break if remainder == 0
end

puts "Solution found at initial_pile_size = #{initial_pile_size}"

Run with ruby -d to enable the debugging output.

My solution is significantly longer than the C or python implementations in the article, but you know what, I don't care. These days I value readability and maintainability far more than code size. (Not that Collin's versions are unreadable, mind you.)

For instance, the bottom of the loop:

quotient, remainder = pile_size.divmod(PEOPLE)
puts "..." if $DEBUG
break if remainder == 0

could be done more concisely as:

break if pile_size % PEOPLE == 0

I did the long form, calculating a quotient value we really don't need, just so I could get that debug statement in there. The additional diagnostic information was much more valuable to me than saving a couple lines.

I prefer to avoid language tricks unless there is good reason. Yet, I used one here. I use a bit lof language trickiness in the way that the rc variable gets set indicating whether the loop succeeded or failed. More traditionally, one would initialize a flag, set it when the loop fails, and test it when done. That would use a bunch more lines to do something that I think should be clear to somebody who understands the Ruby paradigms. I still felt guilty about doing it, though, which is why that code is commented.

I've been enjoying Ruby quite a bit. Right now, my three biggest concerns are:

  • No detection of misspelled variables or methods.
  • Problem detection deferred from load time to run time.
  • Lack of block scope for local variables.

I really like the simple object model (single inheritance extended by mixins). From a programming viewpoint, I really like that everything is an object, although I'm concerned about the impact that may have on performance. Other than the block scoping issue I mentioned, I find variable scoping to be sensible. I'm looking forward to some non-toy projects.

Comments

Comments have been closed for this entry.

re: The Coconuts Problem...in Ruby

I too really like Ruby. I've found that it only takes a little bit of attention to avoid the scoping problem you mention.

The other two problems are more problematic. Ruby's Test::Unit package makes testing very simple and clear. With more testing (which is always good) one can probably work around the run imte/misspelling problems. I'd prefer the ability to require variable (object reference) declaration, but...

A BIG gotcha for ex-perl devlopers! 0 is NOT false! I will say that again 0 is not false! So this very common perl idiom won't work in Ruby:

In perl this will sort primarily on the first element, and secondarily on the second element.

( ($a[0] <=> $b[0]) || ($a[1] <=> $b[1]))

In Ruby it must be written like:

$e = ($a[0] <=> $b[0])
if ($e != 0)
return $e
else
return ($a[1] <=> $b[1])
end

Forgetting this (I had already read that 0 (and '') was not false in Ruby), cost me a solid 8 hour day of debugging a very complex app.

re: The Coconuts Problem...in Ruby

Wayne, yeah ... I wish there was a more concise way to express:

if (a.nil? || a.empty?) ...

One solution would be to add an "empty?" method to the Object base class (and have it simply call "nil?"), but that smacks of "redefining the language" which is a bad thing for a program to do.