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.