0

The following code is intended to capitalize the first letter of each word in a string, and it works:

def capitalize_words(string)
  words = string.split(" ")
  idx = 0
  while idx < words.length
    word = words[idx]
    word[0] = word[0].upcase
    idx += 1
  end
  return words.join(" ")
end

capitalize_words("this is a sentence") # => "This Is A Sentence"
capitalize_words("mike bloomfield") # => "Mike Bloomfield"

I do not understand why it works. In the while loop, I did not set any element in the words array to anything new. I understand that it might work if I added the following line before the index iteration:

words[idx] = word

I would then be altering the elements of words. However, the code works even without that line.

sawa
  • 165,429
  • 45
  • 277
  • 381
  • 1
    FWIW, there is a [`capitalize`](http://ruby-doc.org/core-2.2.3/String.html#method-i-capitalize) method. – orde Oct 01 '15 at 20:35
  • Thanks - I am aware of the capitalize method, but I wanted to understand why this particular manual method of capitalizing each word in a string worked. – user3416984 Oct 01 '15 at 20:51

3 Answers3

2

yet in no place in the while loop that I am using to capitalize the first letter of each word do I actually set any of the elements in the "words" array to anything new.

You do, actually, right here:

word = words[idx]
word[0] = word[0].upcase # This changes words[idx][0]!

The upcase method does just that: returns the upcase of a given string. For example:

'example'.upcase
# => "EXAMPLE" 
'example'[0].upcase
# => "E" 
sawa
  • 165,429
  • 45
  • 277
  • 381
Leo Brito
  • 2,053
  • 13
  • 20
  • So... Ruby is passing `words[idx]` to `word` by reference and not creating a separate value? Interesting... It was doing the opposite for me on a certain project. – Charles Oct 02 '15 at 22:02
  • @c650 well, sort of. Strictly speaking, Ruby is a pass-by-value language, but all values are references (note that `[]` is a function; writing `'abc'[1]` is just a simpler way of writing `'abc'.[](1)`). You should check [this post](http://stackoverflow.com/questions/1872110/is-ruby-pass-by-reference-or-by-value) on the subject. – Leo Brito Oct 02 '15 at 22:13
0

The method String#[]= that you are using in:

word[0] = ...

is not variable assignment. It alters the content of the receiver string at the given index, retaining the identity of the string as an object. And since word is not a copy but is the original string taken from words, in turn, you are modifying words.

sawa
  • 165,429
  • 45
  • 277
  • 381
  • This is very helpful along with Brito's answer. You provide a more thorough explanation, but his first code block responding directly to part of my question made it easy for me to understand exactly what was going on. – user3416984 Oct 01 '15 at 20:57
0

You're doing a lot of work that you don't have to:

def capitalize_words(string)
  string.split.map{ |w|
    [w[0].upcase, w[1..-1]].join # => "Foo", "Bar"
  }.join(' ')
end

capitalize_words('foo bar')
# => "Foo Bar"

Breaking it down:

'foo'[0] # => "f"
'foo'[0].upcase # => "F"
'foo'[1..-1] # => "oo"

['F', 'oo'].join # => "Foo"
the Tin Man
  • 158,662
  • 42
  • 215
  • 303