I have to say, I think Martin provided a great quiz here… His
description was old-school, and most of the solutions have that
feeling. In return, I’m looked at the solutions from a similar
viewport. While the solution from Jesse Brown is simple and makes
use of gnuplot
– and would generally be a good way to solve the
problem – it ain’t old-school. (Of course, this means that if you
have a real graphing problem, using gnuplot
would be a good idea.)
Michael Suchanek provided a solution using RubyX11. Oh man, does
that bring back memories… Michael just opened up a can of X11
goodness that, with Ruby, looks trivial compared to my old X11 C code.
I am rather tempted to study this library and start playing, but I do
have papers to write and books to analyze. Please take a look at
Michael’s code, because X11 is definitely old-school, definitely
powerful, and RubyX11 makes it definitely cool.
The ASCII solution from brabuhr is also several flavors of awesome.
Heck, if we geeks can still be entertained playing ASCII games like
Nethack and Dwarf Fortress, then why not our function
plotters? This solution is also totally Ruby, with a single, trivial
function that accepts your “equation” as a block… Top notch, dear
sir.
I am going to go into a bit of detail with Martin DeMello’s
solution, not because he wrote the quiz, but it reminded me most of my
oldest coding experience: BASIC. Sure, there’s a class and a
function or two in there, but I could imagine line numbers in front of
the code and almost expected to see a GOTO
statement in there
somewhere.
Plus, it makes use of the cool, little, cross-platform Ruby library
for making little applications: Shoes. While Shoes still seems a
little rough around the edges, it’s a fun environment and API, a kind
of toy that recalls days on the Commodore 64. (I use Apple computers
nowadays, but back then, Commondore was king!)
Here’s the main body:
Shoes.app :height => Y, :width => X do
g = Grapher.new
background rgb(255, 255, 255)
fill white
stroke black
strokewidth 1
u, v = nil
Xmin.step(Xmax, (Xmax - Xmin)/(X*1.0)) {|i|
begin
x0, y0 = g.at(u,v)
u, v = i, g.fn(i)
x, y = g.at(u,v)
if g.bounded?(x,y) and g.bounded?(x0,y0)
line(x0, y0, x, y)
end
rescue
end
}
end
There are three essential things going on here. First, the creation of
a Shoes application and the calls to prepare it, such as fill white
and strokewidth 1
. Second is the creation of a Grapher
object and
the calls into it: we’ll come back to that. Finally, the main loop is
here, contained in this one line:
Xmin.step(Xmax, (Xmax - Xmin)/(X*1.0)) {|i|
Numeric#step
is a method that works for either integers or floats,
and counts from the first number (Xmin
) up to the second number
(Xmax
) by the provided increment. Martin divides the window width
into the user-specified domain. This increment will ensure the
evaluations contained within the loop are evaluated once per
horizontal pixel.
The multiplication by 1.0 serves to convert X (and the rest of that
expression) to floating-point. Now, typically this might be done with
the to_f
method. But multiplying by 1.0 seems old-school, especially
as it is one character less that .to_f
. Rad.
The Grapher
is a simple class containing three methods. bounded?
and fn
are fairly straightforward; the former checks that a
coordinate pair is contained within the window’s drawing area, while
the latter evaluates the function provided by the user. Then there’s
Grapher#at
:
def at(x,y)
[((x - Xmin) * XScale).to_i, Y - ((y - Ymin) * YScale).to_i]
rescue nil
end
This function converts the pair (x, y)
from function-space values to
window-space values. That is, it maps the evaluation of the function
to the appropriate coordinate within the Shoes window. There is some
repetition here that could stand to be refactored into a general
lerp
method (i.e. linear interpolation), but as we’re in old-school
mode, I’ll let it slide.
So, back in the main loop, we can now read this easily. For each
iteration, we evaluate the provided function at each pixel column of
the domain (i
, stored to u
) to get the pixel row (v
) via method
fn
. The (u, v)
pair is converted to window coordinates (x, y)
via method at
. The previous window coordinate is recalculated into
(x0, y0)
, and the Shoes’ line
method is called to draw a line
between the two window coordinates.
Good show, gents. Now, if I can get Ruby running on this ol’ Commodore
64 sitting in the closet, my life will be complete.
New quiz will show up later this evening, or early tomorrow morning.
Quick hint: it’s about time to finish the final part of the
Statistician quiz.