ruby cookbook
908 pág.

ruby cookbook


DisciplinaProgramação I24.411 materiais278.183 seguidores
Pré-visualização50 páginas
# => Complex(3.0, 4.25)
b + 1.5*Complex::I # => Complex(1.5, 5.75)
a - b # => Complex(-0.5, -0.25)
a * b # => Complex(-15.5, 10.25)
b.conjugate # => Complex(1.5, -4.25)
Math::sin(b) # => Complex(34.9720129257216, 2.47902583958724)
Discussion
You can use two floating-point numbers to keep track of the real and complex parts
of a complex number, but that makes it complicated to do mathematical operations
such as multiplication. If you were to write functions to do these operations, you\u2019d
have more or less reimplemented the Complex class. Complex simply keeps two
instances of Numeric, and implements the basic math operations on them, keeping
them together as a complex number. It also implements the complex-specific mathe-
matical operation Complex#conjugate.
Complex numbers have many uses in scientific applications, but probably their
coolest application is in drawing certain kinds of fractals. Here\u2019s a class that uses
complex numbers to calculate and draw a character-based representation of the
Mandelbrot set, scaled to whatever size your screen can handle.
class Mandelbrot
 # Set up the Mandelbrot generator with the basic parameters for
 # deciding whether or not a point is in the set.
68 | Chapter 2: Numbers
 def initialize(bailout=10, iterations=100)
 @bailout, @iterations = bailout, iterations
 end
A point (x,y) on the complex plane is in the Mandelbrot set unless a certain iterative
calculation tends to infinity. We can\u2019t calculate \u201ctends towards infinity\u201d exactly, but
we can iterate the calculation a certain number of times waiting for the result to
exceed some \u201cbail-out\u201d value.
If the result ever exceeds the bail-out value, Mandelbrot assumes the calculation goes
all the way to infinity, which takes it out of the Mandelbrot set. Otherwise, the itera-
tion will run through without exceeding the bail-out value. If that happens,
Mandelbrot makes the opposite assumption: the calculation for that point will never
go to infinity, which puts it in the Mandelbrot set.
The default values for bailout and iterations are precise enough for small, chunky
ASCII renderings. If you want to make big posters of the Mandelbrot set, you should
increase these numbers.
Next, let\u2019s define a method that uses bailout and iterations to guess whether a specific
point on the complex plane belongs to the Mandelbrot set. The variable x is a position
on the real axis of the complex plane, and y is a position on the imaginary axis.
 # Performs the Mandelbrot operation @iterations times. If the
 # result exceeds @bailout, assume this point goes to infinity and
 # is not in the set. Otherwise, assume it is in the set.
 def mandelbrot(x, y)
 c = Complex(x, y)
 z = 0
 @iterations.times do |i|
 z = z**2 + c # This is the Mandelbrot operation.
 return false if z > @bailout
 end
 return true
 end
The most interesting part of the Mandelbrot set lives between \u20132 and 1 on the real
axis of the complex plane, and between \u20131 and 1 on the complex axis. The final
method in Mandelbrot produces an ASCII map of that portion of the complex plane.
It maps each point on an ASCII grid to a point on or near the Mandelbrot set. If
Mandelbrot estimates that point to be in the Mandelbrot set, it puts an asterisk in that
part of the grid. Otherwise, it puts a space there. The larger the grid, the more points
are sampled and the more precise the map.
 def render(x_size=80, y_size=24, inside_set="*", outside_set=" ")
 0.upto(y_size) do |y|
 0.upto(x_size) do |x|
 scaled_x = -2 + (3 * x / x_size.to_f)
 scaled_y = 1 + (-2 * y / y_size.to_f)
 print mandelbrot(scaled_x, scaled_y) ? inside_set : outside_set
 end
 puts
2.13 Simulating a Subclass of Fixnum | 69
 end
 end
end
Even at very small scales, the distinctive shape of the Mandelbrot set is visible.
Mandelbrot.new.render(25, 10)
# **
# ****
# ********
# *** *********
# *******************
# *** *********
# ********
# ****
# **
See Also
\u2022 The scaling equation, used to map the complex plane onto the terminal screen, is
similar to the equations used to scale data in Recipe 12.5, \u201cAdding Graphical
Context with Sparklines,\u201d and Recipe 12.14, \u201cRepresenting Data as MIDI Music\u201d
2.13 Simulating a Subclass of Fixnum
Problem
You want to create a class that acts like a subclass of Fixnum, Float, or one of Ruby\u2019s
other built-in numeric classes. This wondrous class can be used in arithmetic along
with real Integer or Float objects, and it will usually act like one of those objects,
but it will have a different representation or implement extra functionality.
Solution
Let\u2019s take a concrete example and consider the possibilities. Suppose you wanted to
create a class that acts just like Integer, except its string representation is a hexadeci-
mal string beginning with \u201c0x\u201d. Where a Fixnum\u2019s string representation might be
\u201c208\u201d, this class would represent 208 as \u201c0xc8\u201d.
You could modify Integer#to_s to output a hexadecimal string. This would proba-
bly drive you insane because it would change the behavior for all Integer objects.
From that point on, nearly all the numbers you use would have hexadecimal string
representations. You probably want hexadecimal string representations only for a
few of your numbers.
This is a job for a subclass, but you can\u2019t usefully subclass Fixnum (the Discussion
explains why this is so). The only alternative is delegation. You need to create a class
that contains an instance of Fixnum, and almost always delegates method calls to that
instance. The only method calls it doesn\u2019t delegate should be the ones that it wants
to override.
70 | Chapter 2: Numbers
The simplest way to do this is to create a custom delegator class with the delegate
library. A class created with DelegateClass accepts another object in its constructor,
and delegates all methods to the corresponding methods of that object.
require 'delegate'
class HexNumber < DelegateClass(Fixnum)
 # The string representations of this class are hexadecimal numbers.
 def to_s
 sign = self < 0 ? &quot;-&quot; : &quot;&quot;
 hex = abs.to_s(16)
 &quot;#{sign}0x#{hex}&quot;
 end
 def inspect
 to_s
 end
end
HexNumber.new(10) # => 0xa
HexNumber.new(-10) # => -0xa
HexNumber.new(1000000) # => 0xf4240
HexNumber.new(1024 ** 10) # => 0x10000000000000000000000000
HexNumber.new(10).succ # => 11
HexNumber.new(10) * 2 # => 20
Discussion
Some object-oriented languages won\u2019t let you subclass the \u201cbasic\u201d data types like
integers. Other languages implement those data types as classes, so you can subclass
them, no questions asked. Ruby implements numbers as classes (Integer, with its
concrete subclasses Fixnum and Bignum), and you can subclass those classes. If you
try, though, you\u2019ll quickly discover that your subclasses are useless: they don\u2019t have
constructors.
Ruby jealously guards the creation of new Integer objects. This way it ensures that,
for instance, there can be only one Fixnum instance for a given number:
100.object_id # => 201
(10 * 10).object_id # => 201
Fixnum.new(100)
# NoMethodError: undefined method `new' for Fixnum:Class
You can have more than one Bignum object for a given number, but you can only cre-
ate them by exceeding the bounds of Fixnum. There\u2019s no Bignum constructor, either.
The same is true for Float.
(10 ** 20).object_id # => -606073730
((10 ** 19) * 10).object_id # => -606079360
Bignum.new(10