Previous | ToC | Up | Next |

** Carol**: So where were we? We wanted to declare ` a` as an array.
more precisely as an instance of the ` Array` class. Here is one way to
do that:

a = []

Let me write the new version in a new file, euler_array.rb, in the hope things will be correct now:

include Math r = [1, 0, 0] v = [0, 0.5, 0] a = [] dt = 0.01 print(r[0], " ", r[1], " ", r[2], " ") print(v[0], " ", v[1], " ", v[2], "\n") 1000.times{ r2 = r[0]*r[0] + r[1]*r[1] + r[2]*r[2] r3 = r2 * sqrt(r2) a[0] = - r[0] / r3 a[1] = - r[1] / r3 a[2] = - r[2] / r3 r[0] += v[0]*dt r[1] += v[1]*dt r[2] += v[2]*dt v[0] += a[0]*dt v[1] += a[1]*dt v[2] += a[2]*dt print(r[0], " ", r[1], " ", r[2], " ") print(v[0], " ", v[1], " ", v[2], "\n") }

** Dan**: Seeing is believing. Does it now work?

** Carol**: Let's try:

|gravity> ruby euler_array.rb | tail -1 7.6937453936572 -6.27772005661599 0.0 0.812206830641815 -0.574200201239989 0.0

** Carol**: Well, let's check:

|gravity> ruby euler.rb | tail -1 7.6937453936572 -6.27772005661599 0.0 0.812206830641815 -0.574200201239989 0.0So far, so good. Okay, we got our first array-based version of forward Euler working, but it still looks just like the old version. Time to start using some of the power of Ruby's arrays.

In general, a Ruby class can have methods that are associated with
that class. A while ago, we have come across a very simple in
section 5.4, where we encountered the method ` times`
that was associated with the class ` Fixnum`. By writing `10.times`
we could cause a loop to be transversed ten times.

Ruby has a somewhat confusing notation *(class name)#(method name)*
to describe methods that are associated with classes. The example
`10.times` is a way to invoke the method `Fixnum#times`.
I find it a bit confusing, because in practice you always use the dot
notation *(object name).(method name)* in your code. You'll never
see the `#` notation in a piece of code; you only encounter it
in a manual or other text description of a code.

Back to our application.
There are three methods for the class ` Array` that we can use right away,
namely `Array#each`, `Array#each_index` and `Array#map`.

I'll explain what they all do in a moment, but it may be easiest to show how they work in our forward Euler example, in file euler_array_each.rb:

include Math r = [1, 0, 0] v = [0, 0.5, 0] dt = 0.01 r.each{|x| print(x, " ")} v.each{|x| print(x, " ")} print "\n" 1000.times{ r2 = 0 r.each{|x| r2 += x*x} r3 = r2 * sqrt(r2) a = r.map{|x| -x/r3} r.each_index{|k| r[k] += v[k]*dt} v.each_index{|k| v[k] += a[k]*dt} r.each{|x| print(x, " ")} v.each{|x| print(x, " ")} print "\n" }

** Erica**: That looks nice and compact.

** Dan**: Does it work?

** Carol**: Let's see:

|gravity> ruby euler_array_each.rb | tail -1 7.6937453936572 -6.27772005661599 0.0 0.812206830641815 -0.574200201239989 0.0

** Erica**: Good! Now let's look at these magic terms. I can guess what
` each` does. It seems to iterate over all the elements of an array,
applying whatever appears in parentheses to each element.

** Carol**: Yes, indeed. And while working with a specific element, it
needs to give that element a name. The name is defined between the two
vertical bars that follow the opening parentheses. It works just like
the lambda notion in Lisp.

** Dan**: I've no idea what lambda notation means, but I can see what is
happening here. In the line

r.each{|x| print(x, " ")}

writing `{|x| ...}` lets ` x` stand for the element of the array
` r`. First `x = r[0]`, and the `...` command then becomes
`print(r[0], " ")`. Then in the next round, `x = r[1]`,
and so on.

Hey, now that I'm looking at the code a bit longer, I just noticed that
the construction `.each{|x| ...}` is actually quite similar to the
construction `.times{...}` that we use in the loop.

** Carol**: Yes, in both cases we are dealing with a method, ` each` and ` times`
that causes the statements in parentheses to be iterated. And the analogy
goes further. Remember that we learned how to get sparse output? At that
time we added a counter to the ` times` loop, so that it became
`.times{|i| ...}`. Just like ` x` stands in for each successive array
element in `r.each{|x| ...}`, so ` i` stands in for each successive value
between 0 and 999 in `1000.times{|i| ...}`.

** Erica**: As for your second magic term, the method `each_index`
seems to do something similar to ` each`. What's the difference?

** Carol**: Take the line:

r.each_index{|k| r[k] += v[k]*dt}

There we want to add to each element of array ` r` the corresponding element
of array ` v`, multiplied by ` dt`. However, we cannot just use the
` each` method, since in that case we would iterate over the * values*
of ` r`, and the dummy parameter, defined between vertical bars, will
take on the values `r[0]`, `r[1]`, and so on. That would
give us no handle on the value of the * index*, which is `0` in the
first case, `1` in the second, and so on.

In the print case above, we had no need to know the value of the index of
each element of the array that we were printing. But here, the value
of the index is needed, otherwise we cannot line up the corresponding
elements of ` r` and ` v`.

** Erica**: I see. Or at least I think I do. Let me try it out, using irb.

|gravity> irb a = [4, 7, 9] [4, 7, 9] a.each{|x| print x, "\n"} 4 7 9 [4, 7, 9] a.each_index{|x| print x, "\n"} 0 1 2 [4, 7, 9] a.each_index{|x| print a[x], "\n"} 4 7 9 [4, 7, 9] quitYes, that makes sense.

** Dan**: Why do we get an echo of the whole array, at the end of each result?

** Carol**: That's because irb always prints the value of an expression.
First the expression is evaluated, and as a side effect the print statements
in the expression are executed. But then a value is returned, which turns out
to be the array ` a` itself. That's not particularly useful here, but in
general, it is convenient that irb always gives you the value of anything
it deals with, without you having to add print statements everywhere.

** Dan**: What about this mysterious ` map` that you are using in line:

a = r.map{|x| -x/r3}

** Carol**: Ah, that is another Lisp like feature, but don't worry about that,
since you're not familiar with Lisp. The method ` map`, when applied
by a given array, returns a new array in which every element is the
result of a mapping that is applied to the corresponding element of
the old array. That sounds more complicated than it really is.
Better to look at an example:

|gravity> irb a = [4, 7, 9] [4, 7, 9] a.map{|x| x + 1} [5, 8, 10] a.map{|x| 2 * x} [8, 14, 18] quit

** Carol**: Yes, and notice how convenient it is that irb echoes the value
of each statement you type. You don't have to write
`print a.map{|x| x + 1}`, for example.

So in our case the line

a = r.map{|x| -x/r3}

transforms the old array ` r` into a new array ` a` for which each element
gets a minus sign and is divided by `r3`, which is just what we
needed.

** Erica**: Ah, look, you forgot to include the line `a = []`, and
it still worked, this time. That must be because now we * are* actually
producing a new array ` a`, and we are no longer trying to assign values
to elements of ` a` as we did before.

** Carol**: That's right! I had not even realized that. Good. One less
line to worry about.

Oh, by the way, when you look at books about Ruby, or when you happen
to see someone else's code, you may come across the method
`Array#collect`. That is just another name for `Array#map`.
Both ` collect` and ` map` are interchangeable terms. This often happens
in Ruby: many method names are just an alias for another method name.
I guess the author of Ruby tried to please many of his friends, even
though they had different preferences.

** Erica**: I prefer the name ` map`, since it gives you the impression
that some type of transformation is being performed. As for the word
` collect`, it does not suggest much work being done.

** Carol**: I agree, and that's why I chose to use ` map` here.

** Erica**: Carol, you convinced us that we should obey the DRY principle,
and indeed, we are no longer repeating ourselves on the level of vector
components. But when I look at the last code that you produced, there
is still a glaring violation of the DRY principle. Look at the three lines
that we use to print the positions and velocities right at the beginning.
The very same three lines are used inside the loop, at the end.

** Carol**: Right you are! Let's do something about that. Time to define
a method of our own. Here, this is easy. Let's introduce a method
called `print_pos_vel(r,v)`, which prints the position and velocity
arrays. It has two arguments, ` r` and ` v`, the values of the two arrays
it should print.

We can write the definition of `print_pos_vel` at the top of the
file, and then we can invoke that method wherever we need it; I'll call
the file euler_array_each_def.rb:

include Math def print_pos_vel(r,v) r.each{|x| print(x, " ")} v.each{|x| print(x, " ")} print "\n" end r = [1, 0, 0] v = [0, 0.5, 0] dt = 0.01 print_pos_vel(r,v) 1000.times{ r2 = 0 r.each{|x| r2 += x*x} r3 = r2 * sqrt(r2) a = r.map{|x| -x/r3} r.each_index{|k| r[k] += v[k]*dt} v.each_index{|k| v[k] += a[k]*dt} print_pos_vel(r,v) }

** Erica**: Good! I think we can now certify this program as DRY compliant.

** Dan**: Does it work?

** Carol**: Ah yes, to be really compliant, it'd better work. Here we go:

|gravity> ruby euler_array_each_def.rb | tail -1 7.6937453936572 -6.27772005661599 0.0 0.812206830641815 -0.574200201239989 0.0

** Dan**: I wonder, would it be possible to make the code even shorter?

** Erica**: Making a code shorter doesn't necessarily make it more readable!

** Dan**: Sure, but I'm just curious.

** Carol**: Well, if you want to get fancy, there is an array method called
` inject`. It's a strange name for what is something like an accumulation
method. Let me show you:

|gravity> irb a = [3, 4, 5] [3, 4, 5] a.inject(0){|sum, x| sum + x} 12 a.inject(1){|product, x| product * x} 60 quit

** Carol**: Indeed. So this will allow me to make the loop part of the code
a bit shorter, in euler_array_inject1.rb:

include Math def print_pos_vel(r,v) [r,v].flatten.each{|x| print(x, " ")} print "\n" end r,v = [[1, 0, 0], [0, 0.5, 0]] dt = 0.01 print_pos_vel(r,v) 1000.times{ r2 = r.inject(0){|sum, x| sum + x*x} r3 = r2 * sqrt(r2) a = r.map{|x| -x/r3} r.each_index{|k| r[k] += v[k]*dt ; v[k] += a[k]*dt} print_pos_vel(r,v) }

** Dan**: I see. That got rid of the first line of the previous loop code.
Does it work?

** Carol**: Good point, let's first test it:

|gravity> ruby euler_array_inject1.rb | tail -1 7.6937453936572 -6.27772005661599 0.0 0.812206830641815 -0.574200201239989 0.0Same answer as before. So yes, it works.

** Dan**: And above that, you combined the assignment of the position and velocity
arrays. I'm surprised that * that* works!

** Carol**: In general, in Ruby you can
assign values to more than one variable in one statement, where Ruby
assumes that the values are listed in an array:

|gravity> irb a, b, c = [10, "cat", 3.14] [10, "cat", 3.14] a 10 b "cat" c 3.14 quit

** Carol**: I takes a nested array, and replaces it by a flat array, where
all the components of the old tree structure are now arranged in one
linear array. Here's an example:

|gravity> irb [1, [[2, 3], [4,5], 6], 7].flatten [1, 2, 3, 4, 5, 6, 7] quit

** Carol**: Ah, but I can do better! How about this one,
euler_array_inject2.rb?

include Math def print_pos_vel(r,v) [r,v,"\n"].flatten.each{|x| print(x, " ")} end r,v,dt = [[1, 0, 0], [0, 0.5, 0], 0.01] print_pos_vel(r,v) 1000.times{ r2 = r.inject(0){|sum, x| sum + x*x} r3 = r2 * sqrt(r2) a = r.map{|x| -x/r3} r.each_index{|k| r[k] += v[k]*dt ; v[k] += a[k]*dt} print_pos_vel(r,v) }

** Dan**: Two lines less. You're getting devious! And does it work?

|gravity> ruby euler_array_inject2.rb | tail -1I guess not. But how can it produce nothing?

** Carol**: Beats me. Strange. Let's show a bit more output:

|gravity> ruby euler_array_inject2.rb | tail -3 7.68562253804505 -6.27197741210993 0.0 0.81228556121432 -0.5742644506056 0.0 7.6937453936572 -6.27772005661599 0.0 0.812206830641815 -0.574200201239989 0.0Ah, of course, I've been a bit too clever. By adding the return character

include Math def print_pos_vel(r,v) [r,v,"\n"].flatten.each{|x| print(" ", x)} end r,v,dt = [[1, 0, 0], [0, 0.5, 0], 0.01] print_pos_vel(r,v) 1000.times{ r2 = r.inject(0){|sum, x| sum + x*x} r3 = r2 * sqrt(r2) a = r.map{|x| -x/r3} r.each_index{|k| r[k] += v[k]*dt ; v[k] += a[k]*dt} print_pos_vel(r,v) }

and here are the results:

|gravity> ruby euler_array_inject3.rb | tail -3 7.677498893594 -6.26623412376799 0.0 0.812364445105053 -0.574328834193899 0.0 7.68562253804505 -6.27197741210993 0.0 0.81228556121432 -0.5742644506056 0.0 7.6937453936572 -6.27772005661599 0.0 0.812206830641815 -0.574200201239989 0.0Same!

** Dan**: Almost the same: now every line has a few blank spaces at the start.

** Carol**: Actually, that looks more elegant, doesn't it?

** Erica**: Frankly, I'm getting a bit tired of shaving lines from codes.
Stop playing, you two, and let's move one!

Previous | ToC | Up | Next |