4

I'm going through App Academy's Ruby Prep questions, and I want to know why this solution works. It appears that the words array is never altered and yet the method works. Is this a glitch in the matrix, or is it right under my nose?

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
Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
Styx_
  • 135
  • 3
  • 9

3 Answers3

3

The method works because word contains a reference to the array position. So when you assign:

word = words[idx]

You're just using word as a shorthand to operate on that array element, which gets modified by:

word[0] = word[0].upcase

--

Also, if you'd like to come back to this answer after learning some Ruby, here's a simplified version of the method:

def capitalize_words(string)
  string.split.map(&:capitalize).join(' ')
end
nicosantangelo
  • 13,216
  • 3
  • 33
  • 47
  • That's... really weird. I would think that I would have to set words[idx] = word. Is this a ruby-specific thing or what? Is there a name for this so that I can study it a little more? – Styx_ Mar 06 '15 at 02:37
  • @Styx_ It’s called *mutation*. As stated in my answer, Arrays simply contain references to objects, so when you mutate the object, it “changes” everywhere there’s a reference to it. If you call `freeeze` on an object in Ruby it cannot be mutated. Almost every language (unfortunately) is ridden with mutation, save [Clojure](https://en.wikipedia.org/wiki/Clojure). – Andrew Marshall Mar 06 '15 at 02:43
1

String#[]= is a mutating operation. To illustrate using a concise, contained excerpt from your code:

word = "foo"
word[0] = word[0].upcase  # <-- verbatim from your code
word #=> "Foo"

word is still the same exact object contained in the array words (arrays simply contain references to objects, not the data within them), but it has been mutated in-place. It’s generally best to avoid mutations whenever possible as it makes it non-obvious what is happening (as you can see).

Your code could also be more concisely written using map & capitalize (and without any mutations):

string.split(' ').map(&:capitalize).join(' ')
Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
0

word = word[idx] creates a copy of your data. It will then modify that copy instead of the words in the original array.

Simple solution would be:

def capitalize_words(string)
  words = string.split(" ")

  idx = 0
  while idx < words.length
    words[idx][0] = words[idx][0].upcase
    idx += 1
  end

  return words.join(" ")
end
Eduardo Bautista
  • 1,340
  • 2
  • 10
  • 22