9. Methods, Blocks - Re-capping what I've learned


Hi all,

I am well on my way through the prerequisite material before scheduling an interview but I am struggling with stringing together the previous lessons on-the-fly all in one go; pardon the pun!

A good example is the following bit of code:

# block that capitalizes each string in the array
["ryan", "jane"].each {|string| puts "#{string[0].upcase}#{string[1..-1]}"} # prints "Ryan", then "Jane"

From what I recall in previous lessons, and please correct me if I am wrong:

- format stuff in place

.each - for the stuff after me, do the commands I'm saying for each bit
string[0] - create a hash with a default of 0, because we're adding stuff to it soon

The part that's confusing me is the [1..-1]. I remember a lesson way back on using .. for stuff but having a hard time remembering what it was. If I were to take a wild guess, something along the lines of : "Since we're capitalizing things, start at the first character of the string which is 1. But we don't want to capitalize the whole thing, so don't go on to character 2, 3 and so forth but stop at -1. Since -1 is less than 1, then we stop at the very first character because there's nothing else to do."

Is this a correct interpretation?

I am trying to find ways to read/understand Ruby code and make analogies as to what the code is saying. This helps me grasp and retain the concept. I tend to try and explain my challenge in English terms then use code to break it down into computer-language.

Thanks in advance for your thoughts and comments.


Basically, yes, each will work on all elements of the array or hash, one at a time.

No, not at all.

A hash with a default value of 0 is created with the following code for example:

my_hash = Hash.new(0)

With string[0], we're only asking for the first character of the string. Since we're in a code executed for each element of an array of strings, we'll be working on a string (hence the name of the temporary variable called string)

If we had used string.upcase, we would have had RYAN and JANE at the end of the code. Instead we're asking only for the first character to be upcased, therefore string[0].upcase. Why 0? Because strings behave like arrays (arrays of characters), and arrays have their index starting at 0.

The string[1..-1] is basically the same logic, we want only the rest of the characters, not the first one (because we already printed it just before). So for that we're using a range, 1..-1. The range starts at the second element of the object string (since 0 was the first element), and stops at the last element of the object. Yes -1 is used to mean "the last one". And it works in this order just as it works in the normal order (meaning that if we had used [1..-2] then we would have gone from the second character until the one before last, and so on).

Long story short:

string = "ryan"
string[0] # contains 'r'
string[-1] # contains 'n'
string[-2] # contains 'a'


I should also add that this whole piece of code you presented is probably only shown for didactic purposes, because a much simpler solution to the problem could be:

["ryan", "jane"].each {|string| puts string.capitalize}


Thanks for the explanation :smile:

Curious, if string = "ryan", why does -2 return "a"? Shouldn't that be 2 instead? Is it because 1..-2 is saying "Go through the whole string and instead of stopping at n, which is -1, stop at -2 which is a" ?

So if that's the case, then it could kind of be like in Excel where I would have a string of text, say "hello" and by asking it to return the text of the cell minus 2, I would get "hel", yes?


In string = "ryan", both string[2] and string[-2] contain 'a'. It just depends on what you choose to use.


Is there a difference in use cases for the two? Or is there the same answer for both formats and it's just a cosmetic thing?


It will depend on your needs really. Sometimes starting from the end will be simpler for your implementation, sometimes not.