MSA-Array Methods-Ruby

__NOTITLE__

Table of Contents = Array Methods (Ruby Version) =

An Array Declaration
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") }

Three  Array Methods
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

Dan: Great! And it would be even better if this is what we got before.

Carol: Well, let's check:

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

So 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</tt>. 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 #</tt> 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</tt> that we can use right away, namely Array#each</tt>, Array#each_index</tt> and Array#map</tt>.

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</tt>:

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

The Methods  each</tt> and each_index</tt>
Erica: Good! Now let's look at these magic terms. I can guess what  each</tt> 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| ...}</tt> lets  x</tt> stand for the element of the array

 r</tt>. First x = r[0]</tt>, and the ...</tt> command then becomes print(r[0], " ")</tt>. Then in the next round, <tt>x = r[1]</tt>, and so on.

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

Carol: Yes, in both cases we are dealing with a method, <tt> each</tt> and <tt> times</tt> 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 <tt> times</tt> loop, so that it became <tt>.times{|i| ...}</tt>. Just like <tt> x</tt> stands in for each successive array element in <tt>r.each{|x| ...}</tt>, so <tt> i</tt> stands in for each successive value between 0 and 999 in <tt>1000.times{|i| ...}</tt>.

Erica: As for your second magic term, the method <tt>each_index</tt> seems to do something similar to <tt> each</tt>. 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 <tt> r</tt> the corresponding element of array <tt> v</tt>, multiplied by <tt> dt</tt>. However, we cannot just use the <tt> each</tt> method, since in that case we would iterate over the  values of <tt> r</tt>, and the dummy parameter, defined between vertical bars, will take on the values <tt>r[0]</tt>, <tt>r[1]</tt>, and so on. That would give us no handle on the value of the index, which is <tt>0</tt> in the first case, <tt>1</tt> 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 <tt> r</tt> and <tt> v</tt>.

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] quit

Yes, 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 <tt> a</tt> 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.

The <tt> map</tt> Method
Dan: What about this mysterious <tt> map</tt> 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 <tt> map</tt>, 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

Dan: Ah, now I get it. In the first case, the mapping is simply adding the number one, and indeed, each element of the array gets increased by one. And in the second case, the mapping tells us that any element <tt> x</tt> is doubled to become <tt>2 * x</tt>, and that's exactly what happens.

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

So in our case the line

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

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

Erica: Ah, look, you forgot to include the line <tt>a = []</tt>, and it still worked, this time. That must be because now we are actually producing a new array <tt> a</tt>, and we are no longer trying to assign values to elements of <tt> a</tt> 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 <tt>Array#collect</tt>. That is just another name for <tt>Array#map</tt>. Both <tt> collect</tt> and <tt> map</tt> 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 <tt> map</tt>, since it gives you the impression that some type of transformation is being performed. As for the word <tt> collect</tt>, it does not suggest much work being done.

Carol: I agree, and that's why I chose to use <tt> map</tt> here.

Defining a Method
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 <tt>print_pos_vel(r,v)</tt>, which prints the position and velocity arrays. It has two arguments, <tt> r</tt> and <tt> v</tt>, the values of the two arrays it should print.

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

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

The <tt>Array#inject</tt> Method
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

<tt> inject</tt>. 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

Erica: I get the idea. What <tt>inject(p){|y, x| y @ x}</tt> does is to give <tt> y</tt> the initial value <tt> p</tt>, and then for each array component <tt> x</tt>, it applies the <tt>@</tt> operator, whatever it is, to the arguments <tt> y</tt> and <tt> x</tt>.

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

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.0

Same answer as before. So yes, it works.

Shorter and Shorter
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

Dan: Oh, and before that, in the <tt>print_pos_vel</tt> method, you've gotten rid of a line as well. What does <tt> flatten</tt> do?

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

Dan: And then just before the last print statement, you combine two statements into one, using a semicolon. Four little tricks, saving us four lines. I'm impressed!

Carol: Ah, but I can do better! How about this one, <tt>euler_array_inject2.rb</tt>?

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) }

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

|gravity> ruby euler_array_inject2.rb | tail -1

I 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.0

Ah, of course, I've been a bit too clever. By adding the return character <tt>\n</tt> character to the same line in the printing method, I have caused an extra two blank spaces to appear in the end. Well, I can get rid of that simply by reversing the order, by printing the blank spaces first. Here is <tt>euler_array_inject3.rb</tt>:

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.0

Same!

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!