X-Combinator

Avatar

making the human scalable

ruby inject and the Mandelbrot set

Ever since I came across ruby’s Enumerable inject function, I have been curious about its applications, and how to best think of it in its most general mathematical form.

As an example, say we have a list of numbers that we want to sum:

We can do this in a traditional iterative loop:

=> 17
(=> 17 is the output of copying and pasting this code into irb for those who are following along at home.)

Using inject, we can do this in 1 line:

=> 17
To see what is going on here, it will be helpful to abstract out the addition function:

Let’s redo the previous inject call, and just replace “x+y” with “f(x,y)”:

=> 17
Let’s expand out what inject is really doing. This is inferred directly from the Ruby documentation:

=> 17
That is illuminating. It seems that inject allows us to iteratively apply a 2-parameter function to itself, where the output of each step in the iteration is fed back into the first parameter, and the second parameter is read in from the list it is called on. The first time it is called, it needs a value for the first parameter, and this single value is the parameter given to the inject call itself.

That’s quite a mouthful. But let’s test our understanding at least on the inject parameter. If we give it 1,000,000 in the previous examples instead of 0, we should have 1,000,000 added to the result:

=> 1000017
So it feels like we have a better understanding of ruby’s inject now. Another way to think of it is in terms of the f(f(f(...f(f(f(a,b),c),d)...,x),y),z) pattern.

As a final test of our ability to wield the inject method, let’s try to print out the Mandelbrot set onto the console using it as the main iterator.

If you’re not familiar with the Mandelbrot set, I recommend spending some time on that page either before or after reading this.

Let’s start with defining the iterated function:

For a given complex number a, to see whether it is included in the Mandelbrot set or not, we start by squaring it and adding the result back to itself:

(set a=0.1 if you are typing this into irb and don’t like to see the NameErrors.)
Or in other words,

We take the result of that, square it, and add it back to a:

or in other words,

Let’s do it two more times just to be sure we see the pattern:

This looks like a job for inject! The last line above is equivalent to:

The number of times the iteration is done is simply the size of the given array. Fortunately, ruby makes it easy to create arrays with repeated elements:

For the resolution we are aiming for, 50 iterations should be enough. Also, let’s replace m(z,c) with z*z+c since it doesn’t really save us space, and everything we are doing is Mandelbrot-specific so there is no need to abstract it out.

The only free variable here is a, so let’s wrap this into a function called mandelbrot:

As I started saying earlier, to test whether a given complex number a belongs to the Mandelbrot set or not, or rather for our purposes to approximate these points since we are only doing 50 iterations instead of an infinite amount, we check the absolute value of mandelbrot(a). If it is less than 2, we will print an asterisk on the screen. Otherwise we will leave it blank.
We’ll have y go from 1.0 to -1.0 in the vertical direction, and x go from -2.0 to 0.5 in the horizontal:

The double-nested loop at the bottom simply iterates through the rows and columns of the screen, printing out the Mandelbrot set line by line. The numerical step values are chosen so as to have 40 rows and 79 columns. The body of the loop creates the complex number Complex(x,y) and calls the mandelbrot(a) function we constructed above with it, and compares the absolute value of its output to 2. If less than 2, display an asterisk, otherwise display a space. After each horizontal line, the call to puts just goes to the beginning of the next line.

I won’t give away the surprise ending! Try it!

del.icio.us:ruby inject and the Mandelbrot set digg:ruby inject and the Mandelbrot set reddit:ruby inject and the Mandelbrot set

2 Comments, Comment or Ping

  1. Nate Murray

    Fantastic! The code is succinct and the output is beautiful. Raganwald just wrote another interesting article on Ruby inject that could be interesting to everyone (http://weblog.raganwald.com/2008/02/1100inject.html). As our interview last week said, if these types of constructs (map, inject, etc) should be interesting to anyone who is in the business of programming.

    Thanks for the insightful post!

Reply to “ruby inject and the Mandelbrot set”