[SUMMARY] Ducksay (#52)

"Ruby Quiz" <james@grayproductions.net> wrote in message
news:20051027140053.ITEU4942.centrmmtao03.cox.net@localhost.localdomain...

···

__________________
< class Cow < Duck >
------------------
    \ _____
     \ \\_-~~ ~~-_
      \ /~ ~\
       \ _| _ |
        \ ___) ~~) ~~~--_ |
         \ _-~ ~-_ ___ \ |/
          \ / _-~ ~-_ /
           \ | / \ |
            \ | O) | | |
                   > > > >
                    > > (O | |
                     \ | | |\
                     (~-_ _-\ / _--_ / \
                      \__~~~ ~-_ _-~ / ~\
                      / ~---~~-_ ~~~ _-~ /| |
                   _-~ / \ ~~--~ | | |
                _-~ | / |
   ,-~~-_ __--~~ |-~ /
   > \ | _-~
    \ |--~~
     \ | |
      ~-_ _ | |
         ~-_ ~~---__ _--~~\ |
            ~~--__ / |
                  ~~---___ __--~~| |
                          ~~~~~ | |
                                     > >

That's using Ryan Leavengood's solution to display the much celebrated
line from
Dave Burt's solution. It's just so Ruby!

It looks like people had plenty of fun solving this quiz, which is great.
We're
all about The Fun Factor(tm) here at Ruby Quiz.

Let's get to a solution:

______________________
< Dave Burt's Solution >
----------------------
   \
    \ .-"""-. _.---..-;
      :.) ;"" \/
__..--'\ ;-"""-. ;._
`-.___.^.___.'-.____J__/-._J

Dave is always teaching me great Ruby tricks in his solutions and this
week's
trick of choice is right at the top of the program:

class String
  def width
    inject(0) {|w, line| [w, line.chomp.size].max }
  end
  def height
    to_a.size
  end
  def top
    to_a.first
  end
  def middle
    to_a.values_at(1..-1)
  end
  def bottom
    to_a.last
  end
end

# ...

I just know I would have written width() as:

map { |line| line.chomp.size }.max

Dave's is more efficient though. It only ever keeps the highest number,
instead
of building an Array of all the lengths. Just one more reason inject() is
the
one iterator to rule them all. Thanks for trick #562, Dave!

Obviously we're just dealing with some helpers added to String above, to
make
later work easier. Just remember that String.each() traverses lines by
default
and inject() and to_a() are defined in terms of each() and there shouldn't
be
any surprises here.

# ...

class Duck
  def self.say(speech="quack?", *args)
    balloon(speech) + body(*args)
  end

  def self.balloon(speech)
    " _#{ '_' * speech.width }_\n" +
      if speech.chomp =~ /\n/
        "/ %-#{ speech.width }s \\\n" % speech.top.chomp +
        speech.middle.map do |line|
          "| %-#{ speech.width }s |\n" % line.chomp
        end.join +
        "\\ %-#{ speech.width }s /\n" % speech.bottom.chomp
      else
        "< #{ speech.chomp } >\n"
      end +
    " -#{ '-' * speech.width }-\n"
  end

  def self.body(thoughts='\\', eyes='cc', tongue=' ')
" #{thoughts}
        #{thoughts}
        _ ___
       / \\ / \\
       \\. |: #{eyes}|
        (.|:,---,
        (.|: \\( |
        (. y-'
         \\ _ / #{tongue}
          m m
"
  end
end

# ...

The say() method should be easy enough to digest. Build a balloon() and a
body(), slap them together, and return them. Beyond that, body() is
almost
right out of the quiz. It just builds a String, interpolating the
substitutions.

That leaves balloon(). It's really just building a String too, with a
little
more logic thrown in. The if branch handles multiline comments, so the
speech
bubble can be rounded at the edges. Otherwise the else branch is used.
The
rest is just border drawing.

# ...

class Cow < Duck
  def self.body(thoughts='\\', eyes='oo', tongue=' ')
" #{thoughts} :slight_smile:
   #{thoughts} (#{eyes})\\_______
      (__)\\ )\\/\\
       #{tongue} ||----w |
          >> >>
"
  end
end

class DuckOnWater < Duck
  def self.body(thoughts='\\', eyes='º', tongue='>')
" #{thoughts}
     ` #{tongue[0, 1]}(#{eyes[0, 1]})____,
        (` =~~/
~^~^~^~^~`---'^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~
"
  end
end

# ...

From there, other animals can just inherit and define a new body() method.
Now
we all truly understand Duck Typing! Everything is really just a Duck...

# ...

if $0 == __FILE__
  if ARGV.include?("--help")
    puts "usage: #$0 animal thoughts eyes tongue <speech\n"
    puts "animals: Duck Cow DuckOnWater\n"
    puts "e.g.: #$0 DuckOnWater o x ) </etc/fortune\n"
  else
    animal = Object.const_get(ARGV.shift) rescue Duck
    puts animal.say(STDIN.read, *ARGV)
  end
end

The main code is trivial. Show usage (if branch), or locate the proper
class
with const_get() and call say() (else branch). That's all it really takes
to
get the animals talking.

_________________________________________________________________
< Ryan Leavengood's Solution: Where even Chunky Bacon can talk... >
-----------------------------------------------------------------
      \
       \ __ _.._
       .-'__`-._.'.--.'.__.,
      /--' '-._.' '-._./
     /__.--._.--._.'``-.__/
     '._.-'-._.-._.-''-..'

I won't show the whole thing here, but do look it over. It had a nice
collection of animals embedded in the DATA section of the code. They were
controlled through a Zoo class, where even a random animal could be chosen
to
escape():

# ...

# Our lovely Zoo, full of many wacky animals
class Zoo
  include Singleton
  attr_reader :animals
  def initialize
    @animals = {}
    current_key = nil
    DATA.each do |line|
      if line.chomp =~ /^'(\w*)':$/
        current_key = $1
        @animals[current_key] =
      elsif current_key
        @animals[current_key] << line
      end
    end
  end

  # A random animal has escaped!
  def escape
    choices = @animals.keys
    @animals[choices[rand(choices.length)]]
  end
end

# ...

Ryan also emulated fortune for Windows, with only a few lines of code:

require 'open-uri'

# ...

# Since I wrote this on Windows I don't have fortune...but the
# internet does!
def get_fortune
  open('http://www.coe.neu.edu/cgi-bin/fortune&#39;\) do |page|
    page.read.scan(/<pre>(.*)<\/pre>/m)[0][0].gsub("\t",' ')
  end
end

# ...

Fun to read and educational at the same time. You can't beat that folks!

________________
< The Other Guys >
----------------
       \
        \
        _ ___
       / \ / \
       \. |: ^-|
        (.|:,---,
        (.|: \( |
        (. y-'
         \ _ /
          m m

Jacob Quinn Shenker golfed the solution, needing less than 400 strokes.
Check
it out.

JB Eriksson supported thought bubbles and sent in a duck on water
template.
More good stuff.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(Wrap Up: This went just swimmingly!)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

      o
       ` >(o)____,
          (` =~~/
  ^~^~^~^~^~`---'^~^~^~^~^~

I have to offer major thanks to everyone who made is possible for me to
fill a
quiz summary with animal drawings! How cool is that?

Tomorrow's quiz takes us from animals to appliances as we build our own
DRYer...

Ruby Quiz aka JEGII wrote:

I have to offer major thanks to everyone who made is possible for me to
fill a
quiz summary with animal drawings! How cool is that?

Thanks for another great quiz, James.

I'd just like to point out to everyone that there are now two ducks at
cowsay.com: "duck" and "ruby", "ruby" being Type, The Only Copyrightless
Duck in Recent History, commonly portrayed with Audie the Upright Horse on
his back.

Cheers,
Dave

Hi,

At Thu, 27 Oct 2005 23:01:46 +0900,
Ruby Quiz wrote in [ruby-talk:162917]:

   ______________________
  < Dave Burt's Solution >
   ----------------------
     \
      \ .-"""-. _.---..-;
        :.) ;"" \/
  __..--'\ ;-"""-. ;._
  `-.___.^.___.'-.____J__/-._J

Pretty.

···

--
Nobu Nakada

Ruby Quiz schrieb:

  class String
    def width
      inject(0) {|w, line| [w, line.chomp.size].max }
    end
  end

I just know I would have written width() as:

  map { |line| line.chomp.size }.max

Dave's is more efficient though. It only ever keeps the highest number, instead
of building an Array of all the lengths. Just one more reason inject() is the
one iterator to rule them all. Thanks for trick #562, Dave!

I'm not sure about the efficiency. The inject() version builds a new array on each pass, whereas the map() version builds only one array. Even an inject() implementation without the arrays was running slower than the map() implementation on my computer. inject() is nice, but it definitely doesn't rule them all (sorry Robert :-).

Regards,
Pit

I didn't make that very clear.

I was speaking of memory consumption. map() has to copy the whole Array, while inject() does not.

James Edward Gray II

···

On Oct 27, 2005, at 10:48 AM, Pit Capitain wrote:

Ruby Quiz schrieb:

    class String
      def width
        inject(0) {|w, line| [w, line.chomp.size].max }
      end
    end
I just know I would have written width() as:
    map { |line| line.chomp.size }.max
Dave's is more efficient though. It only ever keeps the highest number, instead
of building an Array of all the lengths. Just one more reason inject() is the
one iterator to rule them all. Thanks for trick #562, Dave!

I'm not sure about the efficiency. The inject() version builds a new array on each pass, whereas the map() version builds only one array. Even an inject() implementation without the arrays was running slower than the map() implementation on my computer. inject() is nice, but it definitely doesn't rule them all (sorry Robert :-).

I'm not sure what you mean with "copy the whole array". The Enumerable#map implementation just creates an empty array and adds all elements that the block returns to it.

One could argue that this leads to one big array (which still just contains Fixnums), while the arrays generated by the inject version can be garbage collected. But the inject version still allocates more memory overall.

And the map version is really faster:

require "benchmark"
include Benchmark

str = IO.read(ARGV.shift)

class String
  def width1
    inject(0) { |w, line| [w, line.chomp.size].max }
  end
  def width2
    map { |line| line.chomp.size }.max
  end
  def t1
    each { |line| [0, line.chomp.size] }
  end
  def t2
    each { |line| line.chomp.size }
  end
end

bmbm(4) { |x|
  x.report("w1") { str.width1 }
  x.report("w2") { str.width2 }
  x.report("t1") { str.t1 }
  x.report("t2") { str.t2 }
}

$ ruby str_width_bm.rb /usr/share/dict/words
Rehearsal ---------------------------------------
w1 1.200000 0.000000 1.200000 ( 1.234289)
w2 0.790000 0.010000 0.800000 ( 0.797576)
t1 0.960000 0.000000 0.960000 ( 1.014776)
t2 0.730000 0.010000 0.740000 ( 0.744466)
------------------------------ total: 3.700000sec

           user system total real
w1 1.190000 0.010000 1.200000 ( 1.231020)
w2 0.800000 0.000000 0.800000 ( 0.809417)
t1 0.550000 0.000000 0.550000 ( 0.556069)
t2 0.460000 0.010000 0.470000 ( 0.500228)

Dominik

···

On Thu, 27 Oct 2005 17:55:04 +0200, James Edward Gray II <james@grayproductions.net> wrote:

On Oct 27, 2005, at 10:48 AM, Pit Capitain wrote:

Ruby Quiz schrieb:

    class String
      def width
        inject(0) {|w, line| [w, line.chomp.size].max }
      end
    end
I just know I would have written width() as:
    map { |line| line.chomp.size }.max
Dave's is more efficient though. It only ever keeps the highest number, instead
of building an Array of all the lengths. Just one more reason inject() is the
one iterator to rule them all. Thanks for trick #562, Dave!

I'm not sure about the efficiency. The inject() version builds a new array on each pass, whereas the map() version builds only one array. Even an inject() implementation without the arrays was running slower than the map() implementation on my computer. inject() is nice, but it definitely doesn't rule them all (sorry Robert :-).

I didn't make that very clear.

I was speaking of memory consumption. map() has to copy the whole Array, while inject() does not.

ary.max { |a,b| a.size <=> b.size }.size

···

On 2005-10-28 00:55:04 +0900, James Edward Gray II wrote:

I was speaking of memory consumption. map() has to copy the whole
Array, while inject() does not.

--
Florian Frank

I'm not sure what you mean with "copy the whole array".

I meant that you have to create a second Array of equal size.

One could argue that this leads to one big array (which still just contains Fixnums), while the arrays generated by the inject version can be garbage collected.

That was my point, yes. I suspect this would win out in complex transformations of large data sets.

James Edward Gray II

···

On Oct 27, 2005, at 12:45 PM, Dominik Bathon wrote:

Very nice. I forgot max() can take a block.

James Edward Gray II

···

On Oct 27, 2005, at 12:59 PM, Florian Frank wrote:

On 2005-10-28 00:55:04 +0900, James Edward Gray II wrote:

I was speaking of memory consumption. map() has to copy the whole
Array, while inject() does not.

ary.max { |a,b| a.size <=> b.size }.size

James Edward Gray II wrote:

ary.max { |a,b| a.size <=> b.size }.size

Very nice. I forgot max() can take a block.

In 1.9. it could be even nicer, but maybe slightly more inefficient:

ary.max_by { |x| x.size }.size

···

--
Florian Frank