0

print str[i].upcase is not working and i have to capitalize specific letters determined using an index. Can someone help me with this?

def mumble_letters

  str = nil
  print "Please write a string :  "
  str = gets.to_str
  # puts str.length

  while str.length == 1
    print "Please write a string :  "
    str = gets.to_str
  end

  for i in 0..str.length

    print str[i].upcase!
    i.times{ print str[i].capitalize}

    if i != str.length - 1
      print"-"
    end
  end

end

mumble_letters

the error I get is : undefined method `upcase' for nil:NilClass (NoMethodError) Did you mean? case

SteveTurczyn
  • 36,057
  • 6
  • 41
  • 53
Ioana C
  • 1
  • 1
  • 1
    Welcome to Stack Overflow! Please provide a bit more context. What is `str`, what is `i`? What (wrong) result do you get and what's the (correct) expected result? It's best to provide a full (but small) example that we can just copy and paste. Add the relevant details by editing your question (there's an _edit_ button) – Stefan Jun 09 '20 at 11:14
  • Thanks for your edit. In addition to your code, can you give some example input and output? If a user enters _"Hello world!"_ what would be the expected result? – Stefan Jun 09 '20 at 11:52

3 Answers3

1

Problem

str[i].upcase! mutates the single character in the Array value into an uppercase character. However, at least on Ruby 2.7.1, it won't actually change the contents of your original String object until you reassign the element back to the String index you want modified. For example:

str[i] = str[i].upcase

However, the approach above won't work with frozen strings, which are fairly common in certain core methods, libraries, and frameworks. As a result, you may encounter the FrozenError exception with the index-assignment approach.

Solution

There's more than one way to solve this, but one way is to:

  1. split your String object into an Array of characters,
  2. modify the letter at the desired indexes,
  3. rejoin the characters into a single String, and then
  4. re-assign the modified String to your original variable.

For example, showing some intermediate steps:

# convert String to Array of characters
str   = "foobar"
chars = str.chars

# contents of your chars Array
chars
#=> ["f", "o", "o", "b", "a", "r"]

# - convert char in place at given index in Array
# - don't rely on the return value of the bang method
#   to be a letter
# - safe navigation handles various nil-related errors
chars[3]&.upcase!
#=> "B"

# re-join Array of chars into String
chars.join
#=> "fooBar"

# re-assign to original variable
str = chars.join
str
#=> "fooBar"

If you want, you can perform the same operation on multiple indexes of your chars Array before re-joining the elements. That should yield the results you're looking for.

More concisely:

str   = "foobar"
chars = str.chars
chars[3]&.upcase!
p str = chars.join
#=> "fooBar"

Personally, I find operating on an Array of characters more intuitive and easier to troubleshoot than making in-place changes through repeated assignments to indexes within the original String. Furthermore, it avoids exceptions raised when trying to modify indexes within a frozen String. However, your design choices may vary.

Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
  • _"`str[i].upcase!` mutates the single character in the Array value"_ – there's no array in that code snippet. – Stefan Jun 09 '20 at 15:15
0

str[i].upcase returns the upcased letter, but does not modify it in place. Assign it back to the string for it to work.

str = 'abcd'
str[2] = str[2].upcase #=> "C"
str #=> "abCd"
Siim Liiser
  • 3,860
  • 11
  • 13
  • This works, but future readers should note that using String#upcase! may assign `nil` if the indexed character is already uppercase, nil, or out of bounds for the array. While you used the non-bang method, the OP was using it for in-place operations, which changes the semantics. – Todd A. Jacobs Jun 09 '20 at 13:42
0

I can see two problems with your code...

First, an empty string has a length of 0 so what you wanted to write is

while str.length == 0

Secondly, when you do...

for i in 0..str.length

You are iterating up to the string length INCLUDING the string length. If the string has five characters, it actually only has valid indexes 0 through 4 but you are iterating 0 through 5. And str[5] doesn't exist so returns nil and you cannot do upcase! on a nil.

To handle that common situation, Ruby has the tripe dot operator

for i in 0...str.length

...which will stop at the integer before the length, which is what you want.

It's also more ruby-eque to do

(0...str.length).each do |i|
SteveTurczyn
  • 36,057
  • 6
  • 41
  • 53