Help me understand how this block works?

B

Brian A.

Hello -

I'm working through a tutorial and Ive come across a line of code that I
do not understand. The program basically takes a roman number value and
converts it to its integer counterpart.

Here is the line:
roman_string.to_s.upcase.split(//).reverse.inject(0) do |memo, digit|

If roman_string contains the value "IV", when we come to this line can
you please explain what the 'inject' does? When I step through it in
debug mode, I can see it gets assigned '0'. But how is the 'digit'
populated? How does it know it gets the 'V' first and subsequently 'I'?
Does it do this by default because the inject(0) populates the memo so
the only variable left is digit?

I tried to play around, by removing the '.inject(0)' and the 'memo' to
see if I could understand what it was doing but to no avail.

roman_string.to_s.upcase.split(//).reverse. do |digit| # program balks
at this

If you'd like me to post the whole program I could, but I didnt want to
clutter the post.

Thank you.
Brian A.
 
E

Einar Magnús Boson

Hello -

I'm working through a tutorial and Ive come across a line of code
that I
do not understand. The program basically takes a roman number value
and
converts it to its integer counterpart.

Here is the line:
roman_string.to_s.upcase.split(//).reverse.inject(0) do |memo, digit|

If roman_string contains the value "IV", when we come to this line can
you please explain what the 'inject' does? When I step through it in
debug mode, I can see it gets assigned '0'. But how is the 'digit'
populated? How does it know it gets the 'V' first and subsequently
'I'?
Does it do this by default because the inject(0) populates the memo so
the only variable left is digit?

I tried to play around, by removing the '.inject(0)' and the 'memo' to
see if I could understand what it was doing but to no avail.

roman_string.to_s.upcase.split(//).reverse. do |digit| # program balks
at this

If you'd like me to post the whole program I could, but I didnt want
to
clutter the post.

Thank you.
Brian A.


Google "ruby iterators tutorial" and try typing

ri Enumerable#inject

in the terminal.


einarmagnus
 
M

matt neuburg

Brian A. said:
Here is the line:
roman_string.to_s.upcase.split(//).reverse.inject(0) do |memo, digit|

If roman_string contains the value "IV", when we come to this line can
you please explain what the 'inject' does? When I step through it in
debug mode, I can see it gets assigned '0'. But how is the 'digit'
populated? How does it know it gets the 'V' first and subsequently 'I'?
Does it do this by default because the inject(0) populates the memo so
the only variable left is digit?

Just look at the docs:

http://ruby-doc.org/core-1.8.7/classes/Enumerable.html

Scroll down to "inject" and look at the third format. The "0" from the
original call is the first value passed to "memo"; meanwhile, the
elements of the array are the values passed each time to "digit".

The "digit" population goes in the order "V" then "I" because the string
has been reversed with the "reverse" method.

The hard part of "inject" to understand is not either of those; it is
the matter of the *subsequent* values of "memo". It is the result of
each previous execution of the block. (There is no "previous execution
of the block" the first time, obviously, so we need to settle on the
first value in some other way. Here, that way is the specification of
the "0" parameter.)

The example in the docs is a very nice one:

(5..10).inject(1) {|product, n| product * n } #=> 151200

If you can see why the final output of the block is the multiplicative
product of all the numbers in the enumerable, you've understood
"inject". The "memo" (here called "product") is seeded with "1" because
multiplying any value by "1" gives the same value, so we get the right
answer for the *first* enumerable and then we're off to the races.

So, our "product" and "n" values each time thru the loop are:

1, 5
1*5, 6
1*5*6, 7
1*5*6*7, 8
1*5*6*7*8, 9
1*5*6*7*8*9, 10

Finally, the last time through, we produce 1*5*6*7*8*9*10 and stop.

If you apply that kind of reasoning to your example, you'll understand
it!

m.
 
M

matt neuburg

Einar Magnús Boson said:
try typing

ri Enumerable#inject

in the terminal.

Except that on my machine, the result is:

Big-iMac-Attack:~ mattneub$ ri Enumerable#inject
------------------------------------------------------ Enumerable#inject
inject(init) {|result, item| ...}

From /usr/local/share/ri/1.8/site/Enumerable/cdesc-Enumerable.yaml
 
T

Todd Benson

Hello -

I'm working through a tutorial and Ive come across a line of code that I
do not understand. The program basically takes a roman number value and
converts it to its integer counterpart.

Here is the line:
roman_string.to_s.upcase.split(//).reverse.inject(0) do |memo, digit|

If roman_string contains the value "IV", when we come to this line can
you please explain what the 'inject' does? When I step through it in
debug mode, I can see it gets assigned '0'. But how is the 'digit'
populated? How does it know it gets the 'V' first and subsequently 'I'?
Does it do this by default because the inject(0) populates the memo so
the only variable left is digit?

It populates memo with 0 at first and thereafter with the value that
the block returns on each iteration. digit is simply the value of
each element in turn, which, in this case, is each character in the
string that is in #reverse. The value of the block (memo on the next
iteration) is the last statement executed within that block. Some
people don't like the method name #inject, but I do, because I think
of it as "injecting" the block result back into the block on each
iter. Learn #each and #map first, then tackle #inject.

The most common example is using it for summing...

a = [1, 5, 9, 7]
a.inject(0) {|s, i| s + i}
#=> 22

...if you don't supply the initial 0 the memo will take on the first
object of the enumerable object. So if I did...

a.inject {|s, i| s + i}

...it would still work for me, but not for you, because in your roman
numeral case, your first object would be a String.
I tried to play around, by removing the '.inject(0)' and the 'memo' to
see if I could understand what it was doing but to no avail.

roman_string.to_s.upcase.split(//).reverse. do |digit| # program balks

"do" is not a method, it's a keyword, so you can't just use "do" on
something that doesn't except a block. That line is trying to use
"do" with no block accepting method.

hth,
Todd
 
B

Brian A.

matt said:
Just look at the docs:

http://ruby-doc.org/core-1.8.7/classes/Enumerable.html

Scroll down to "inject" and look at the third format. The "0" from the
original call is the first value passed to "memo"; meanwhile, the
elements of the array are the values passed each time to "digit".

The "digit" population goes in the order "V" then "I" because the string
has been reversed with the "reverse" method.

The hard part of "inject" to understand is not either of those; it is
the matter of the *subsequent* values of "memo". It is the result of
each previous execution of the block. (There is no "previous execution
of the block" the first time, obviously, so we need to settle on the
first value in some other way. Here, that way is the specification of
the "0" parameter.)

The example in the docs is a very nice one:

(5..10).inject(1) {|product, n| product * n } #=> 151200

If you can see why the final output of the block is the multiplicative
product of all the numbers in the enumerable, you've understood
"inject". The "memo" (here called "product") is seeded with "1" because
multiplying any value by "1" gives the same value, so we get the right
answer for the *first* enumerable and then we're off to the races.

So, our "product" and "n" values each time thru the loop are:

1, 5
1*5, 6
1*5*6, 7
1*5*6*7, 8
1*5*6*7*8, 9
1*5*6*7*8*9, 10

Finally, the last time through, we produce 1*5*6*7*8*9*10 and stop.

If you apply that kind of reasoning to your example, you'll understand
it!

m.

Thank you, that explains it very well for me. Another quick question.
Would it be possible to inject to two different vars?

For instance if something like (syntax is probably wrong if its even
possible):

(5..10).inject(1).inject(2) {|product, variable_two, n| product *
variable_two * n}
 
D

David A. Black

Hi --

Thank you, that explains it very well for me. Another quick question.
Would it be possible to inject to two different vars?

For instance if something like (syntax is probably wrong if its even
possible):

(5..10).inject(1).inject(2) {|product, variable_two, n| product *
variable_two * n}

If the collection you're iterating through has pairs of values, you
can do this:

[[1,2], [3,4], [5,6]].inject(0) {|acc, (x,y)| acc + x + y }

Kind of pointless here, since I'm just adding them up, but that
technique might help you.


David

--
Rails training from David A. Black and Ruby Power and Light:
INTRO TO RAILS (Jan 12-15) | ADVANCING WITH RAILS (Jan 19-22) *
Both in Fort Lauderdale, FL * Co-taught with Patrick Ewing
See http://www.rubypal.com for details
Coming in 2009: The Well-Grounded Rubyist (http://manning.com/black2)
 
T

Todd Benson

Thank you, that explains it very well for me. Another quick question.
Would it be possible to inject to two different vars?

You apply #inject on an Enumerable object. Do you mean that you want
to iterate over two arrays at the same time?
For instance if something like (syntax is probably wrong if its even
possible):

(5..10).inject(1).inject(2) {|product, variable_two, n| product *
variable_two * n}

I can't tell what you are trying to accomplish here, but your
Enumerable can hold any object (like Arrays, etc.)

Todd
 
B

Brian A.

Todd said:
I can't tell what you are trying to accomplish here, but your
Enumerable can hold any object (like Arrays, etc.)

Todd

Not trying to accomplish anything, just trying to understand how it
works (Im just learning Ruby so I apologize for any nonsensical
questions).

My last question is because the block is parsing through my string. In
this case we are passing it roman value 'IV', why does it inject only on
the first pass? I would of expected it to inject on every pass... like
more of a loop?

Thank you again.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,186
Messages
2,570,998
Members
47,587
Latest member
JohnetteTa

Latest Threads

Top