[SUMMARY] Ducksay (#52)

R

Ruby Quiz

__________________
< 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} ^__^
#{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') 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...
 
D

Dave Burt

Ruby Quiz said:
__________________
< 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} ^__^
#{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') 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...
 
D

Dave Burt

Ruby said:
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
 
N

nobu.nokada

Hi,

At Thu, 27 Oct 2005 23:01:46 +0900,
Ruby Quiz wrote in [ruby-talk:162917]:
______________________
< Dave Burt's Solution >
----------------------
\
\ .-"""-. _.---..-;
:.) ;"" \/
__..--'\ ;-"""-. ;._
`-.___.^.___.'-.____J__/-._J

Pretty.
 
P

Pit Capitain

Ruby said:
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
 
J

James Edward Gray II

Ruby said:
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.

James Edward Gray II
 
D

Dominik Bathon

Ruby said:
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 =20
number, instead
of building an Array of all the lengths. Just one more reason =20
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 =20
array on each pass, whereas the map() version builds only one array. =20
Even an inject() implementation without the arrays was running slower = =20
than the map() implementation on my computer. inject() is nice, but it= =20
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 =20
Array, while inject() does not.

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

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


And the map version is really faster:

require "benchmark"
include Benchmark

str =3D 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
 
J

James Edward Gray II

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
 
F

Florian Frank

James said:
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

Forum statistics

Threads
473,780
Messages
2,569,607
Members
45,241
Latest member
Lisa1997

Latest Threads

Top