0

So I wrote this code:

def translate_word word
    vowel = ["a", "e", "i", "o", "u"]
    if vowel.include? word[0] 
        word = word + "ay"
    elsif vowel.include? word[1]
        word = word[1..-1] + word[0] + "ay"
    else
        word = word[2..-1] + word[0..1] + "ay"
    end
end

Translates a word into pig latin. For my purposes, works great. But what if we want to translate more than one word?

def translate string 
   vowel = ["a", "e", "i", "o", "u"]
   words = string.split(" ")
   words.each do |word|
     if vowel.include? word[0] 
         word = word + "ay"
     elsif vowel.include? word[1]
         word = word[1..-1] + word[0] + "ay"
     else
         word = word[2..-1] + word[0..1] + "ay"
     end
   end
   words.join(" ")
end

Except, if we try to do this with one word, it'll notice there aren't any spaces, say screw that, and return a string. Won't even throw me an error when I try to .each it, but the .each won't do any thing.

puts "apple".split

#=>apple

puts translate "apple"

#=>apple

This isn't an insurmountable problem. I could just run string.includes? " " and then run the two slightly different programs depending on if it was there or not. But this seems very ineloquent. What would be a better or more idiomatic way to deal with the string and the loop?

0

2 Answers 2

3

Assigning another value to the block argument doesn't change the array element:

words.each do |word|
  word = word + "ay" # <- this doesn't work as expected
end

To change the element, you have to call a method that changes the receiver, e.g.:

words.each do |word|
  word << "ay"
end

However, instead of repeating the algorithm, you could just call translate_word for each word:

def translate(string)
  string.split.map { |word| translate_word(word) }.join(" ")
end

translate("apple orange")
#=> "appleay orangeay"

I've used split and join here, but you could also use gsub:

def translate(string)
  string.gsub(/\w+/) { |word| translate_word(word) }
end
Sign up to request clarification or add additional context in comments.

Comments

1

As far as I can see you're not manipulating your original array words.

You would need something like this:

def translate string 
   vowel = ["a", "e", "i", "o", "u"]
   words = string.split(" ")
   words.each_with_index do |word, index|
     if vowel.include? word[0] 
         word = word + "ay"
     elsif vowel.include? word[1]
         word = word[1..-1] + word[0] + "ay"
     else
         word = word[2..-1] + word[0..1] + "ay"
     end
     words[index] = word
   end
   words.join(" ")
end

5 Comments

I don't get it. Didn't I change the value that word points to? And isn't a list just a bunch of pointers? Or am I making some sort of fundamental misunderstanding?
In the each loop, each value of self is written into a new variable. In your case every value of words is written into a new variable word. If you modify word, that doesn't mean you modified self aka. words.
That is extremely unintuitive to me. Thanks for telling me.
No problem. There is also a difference betweeen map and map!. While map also creates a new object, map! really changes the current object. With map! there is no need to reassign the object created. e.g. words.map!{ |word| translate_word(word) } does the same like words = words.map{ |word| translate_word(word) }
I knew about the "!" on a lot of variables. The distinction was biting me with .capitalize vs .capitalize! I didn't expect it to apply to iterators. What about .each? Could I .each! and get more permanent affect on my list somehow?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.