How to do string slicing in Ruby - ruby

This is a Pig Latin translate practice in Ruby.
Why am I getting different results from these two versions of code? In other words, why is word = word[i..-1] not taking effect in the second code block?
def translate(input)
output_array = input.split(" ").each do |word|
i=0
while !['a', 'e', 'i', 'o', 'u'].include?(word[i])
i += 1
end
unless i == 0
word << word[0..i-1]
word[0..i-1] = ''
end
word << "ay"
end
return output_array.join(" ")
end
puts translate('apple')
puts translate('banana')
puts translate('trash')
puts translate('eat pie')
which outputs:
appleay
ananabay
ashtray
eatay iepay
And:
def translate(input)
output_array = input.split(" ").each do |word|
i=0
while !['a', 'e', 'i', 'o', 'u'].include?(word[i])
i += 1
end
unless i == 0
word << word[0..i-1]
word = word[i..-1]
end
word << "ay"
end
return output_array.join(" ")
end
puts translate('apple')
puts translate('banana')
puts translate('trash')
puts translate('eat pie')
prints out:
appleay
bananab
trashtr
eatay piep

output_array = input.split(" ").each do |word|
i=0
while !['a', 'e', 'i', 'o', 'u'].include?(word[i])
i += 1
end
unless i == 0
word << word[0..i-1] # Good
word = word[i..-1] # Bad
end
word << "ay"
end
The line
word << word[0..i-1]
changes the string in place, while
word = word[i..-1]
creates a new string and assigns the new string to word. Changing the new string does not affect the old string in the array, so the words in the array stay what they were after
word << word[0..i-1]
Do every modification in-place (like what you did in solution 1), or use Array#map which is more Ruby-like.
This is off-topic, but your while loop can be replaced by
i = word.index(/[aeiou]/)
if you happen to know regular expressions.

Related

How to I get the right output when there is more than vowel in each word? My code only works with one vowel in each word

Aba is a German children’s game where secret messages are exchanged. In Aba,
after every vowel we add “b” and add that same vowel.
Write a method aba_translate that takes in a sentence string and returns a new
sentence representing its Aba translation. Capitalized words of the original sentence
should be properly capitalized in the new sentence.
aba_translate(“Cats and dogs”) #=> “Cabats aband dobogs”
aba_translate(“Everyone can code”) #=> “Ebeveryobonebe caban cobodebe”
aba_translate(“Africa is Africa in German”) #=> “Abafribicaba ibis Abafribicaba ibin
Gebermaban”
My code:
def aba_translate(sentence)
translation = []
words = sentence.split(" ")
vowels = "aeiou"
vowel = ""
before = ""
after = ""
full = ""
words.each do |word|
word.each_char.with_index do |char, idx|
if vowels.include?(char)
vowel = char
before = word[0...idx]
after = word[idx+1..-1]
full = before + vowel + "b" + vowel + after
translation << full
end
end
end
return translation.join(" ")
end
puts aba_translate("Cats and dogs")
puts aba_translate("Everyone can code")
puts aba_translate("Africa is Africa in German")
Your code generates a whole new word every time it sees a vowel. Instead you need to build each word character by character and make changes when it sees a vowel.
def aba_translate(sentence)
translation = []
words = sentence.split(" ")
vowels = "aeiouAEIOU"
words.each do |word|
full = ""
word.each_char.with_index do |char, idx|
full += char
if vowels.include?(char)
full = full + "b" + char.downcase
end
end
translation << full
end
return translation.join(" ")
end
Every time you find a vowel, you take the entire string before the vowel and the entire string after the vowel and add it to the result.
So, for a word like "code", that means you first produce the output c + obo + de and then the output cod + ebe.
However, what you actually need to do is simply keep the part you have processed instead of duplicating it.
You can do this by either changing your logic to keep track of up to which index you have already processed the word, or alternatively by processing it character-by-character instead of chunk-by-chunk.
However for problems like this, Regex are usually a much better solution:
VOWELS = 'aeiou'
def aba_translate(sentence)
sentence.gsub(Regexp.union(*VOWELS.chars), '\0b\0')
end
or just making VOWELS a Regexp in the first place:
VOWELS = /[aeiou]/.freeze
def aba_translate(sentence)
sentence.gsub(VOWELS, '\0b\0')
end
I think the nested loops complicates it since you can solve the problem with just one loop. Here you just need to initialize a string and a string of vowels to check each character. While you iterate through each character you shovel it into the empty string, and then you check if it is a vowel you shovel b + that vowel's lowercase version to account for the uppercase instances. Then you finally return the new string.
def aba_translate(string)
new_string = ""
vowels = "AEIOUaeiou"
string.each_char do |char|
new_string << char
if vowels.include?(char)
new_string << "b" + char.downcase
end
end
return new_string
end
Here's my beginner-friendly solution
def aba_translate(sent)
vowels = "AEIOUaeiou"
aba_sent = ""
sent.each_char do |char|
if vowels.include?(char)
aba_sent += char + "b" + char.downcase
else
aba_sent += char
end
end
return aba_sent
end

Each loop skipping values in array

I'm trying to solve a simple pig latin problem, but when looping over the array, the loop skips a letter.
In this case it's the letter 'h', and I can't understand why.
Any help would be greatly appreciated
def louchebemize(sentence)
# TODO: implement your louchebem translator
array_of_words = sentence.split(" ")
array_of_changed = []
vowels = ['a', 'e', 'i', 'o', 'u']
random = ["em", "é", "ji", "oc", "ic", "uche", "ès"]
puts array_of_words
array_of_words.each do |word|
next if word[0] == 'a'
next if word.length == 1
word_array = word.split("")
p word_array
word_array.each do |letter|
if vowels.include?(letter)
puts "Letter #{letter}"
word_array.unshift('l')
word_array << random[rand(0..6)]
break
else
puts "Letter #{letter}"
word_array.push(letter)
word_array.delete_at(0)
end
end
index = array_of_words.index(word)
join_word_array = word_array.join('')
array_of_words[index] = join_word_array
end
sentence = array_of_words.join(" ")
p sentence
return sentence
end
louchebemize('chat')
Returns this, skipping the 'h' character:
chat
["c", "h", "a", "t"]
Letter c
Letter a
"lhatcès"

Why does my ruby pig latin translator not capitalize the first word of a string properly?

I am trying to write a program that translates a string with some capitalized words and punctuation into Pig Latin. Here are the conditions:
1) words beginning with a vowel should just tack on "ay".
2) words beginning with a single phoneme like "sch" or "qu" or "squ" or "ch" should move all of those characters to the end, not just the first letter, and then tack on "ay".
3) the regular pig latin rules for a word beginning with one consonant (i.e., "Well," => 'Ellway,").
4) capitalization and punctuation should be preserved, but the initial letter would change if the letter doesn't begin with a vowel. So "Well," would become "Ellway,".
Everything works, except for the first word of my string. The fourth condition is never met with the first word of a string. So, for example, "Well," becomes "ellWay,". So punctuation works, but the capitalization isn't working properly.
Edit: I have realized that this issue occurs only when the word does NOT begin with a vowel. So, "Actually," becomes "Actuallyay," (which it should), but "Quaint," becomes "aintQuay,", when it should be "Aintquay,". So, here is the code where I actually pass the pig latin into the array named pig_latin:
string = string.split(' ')
pig_latin = []
string.each do |word|
if vowels.include?(word[0])
pig_latin << word + "ay"
elsif (consonants.include?(word[0]) && consonants.include?(word[1]) && consonants.include?(word[2])) || word[1..2].include?('qu')
pig_latin << (word[3..-1] + word[0..2] + "ay")
elsif (consonants.include?(word[0]) && consonants.include?(word[1])) || word[0..1].include?('qu')
pig_latin << (word[2..-1] + word[0..1] + "ay")
else
pig_latin << (word[1..-1] + word[0] + "ay")
end
end
Here is the part of my code that handles the capitalization and punctuation. To clarify, pig_latin is the array with the pig-latinized phrase passed into it. uppercase_alphabet is an array i created to include all uppercase letters:
idx1 = 0
while idx1 < pig_latin.count
word = pig_latin[idx1]
idx2 = 0
while idx2 < word.length
if uppercase_alphabet.include?(word[idx2])
word[idx2] = word[idx2].downcase
word[0] = word[0].upcase
end
if punctuation.include?(word[idx2])
word[word.length], word[idx2] = word[idx2], ''
end
idx2 += 1
end
idx1 += 1
end
pig_latin.join(' ')
Edit: Here is the code outlining the various arrays I'm using:
vowels = ['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U']
lowercase_alphabet = ('a'..'z').to_a
uppercase_alphabet = ('A'..'Z').to_a
alphabet = lowercase_alphabet + uppercase_alphabet
punctuation = ['.', ',', ';', '?', '!', ':']
consonants = []
alphabet.each do |letter|
consonants << letter unless vowels.include?(letter)
end
And, here are the errors I'm getting when I run the test with the following string: "Well, I have, not even. seen that movie." (I understand the punctuation makes no sense).
1) #translate retains punctuation from the original phrase
Failure/Error: s.should == "Ellway, Iay avehay, otnay evenay. eensay atthay oviemay."
expected: "Ellway, Iay avehay, otnay evenay. eensay atthay oviemay."
got: "ellWay, Iay avehay, otnay evenay. eensay atthay oviemay." (using ==)
# ./spec/04_pig_latin_spec.rb:83:in `block (2 levels) in <top (required)>
It is hard to debug imaginary code. You are asking people why your code doesn't work without providing the values of some key variables.
Her are some tips:
1) A String works just like an Array, so you don't have to create an
Array of individual letters, which forces you to type all the commas and quote marks:
vowels = 'aeiou'
vowels.include?('a') #=>true
2) You don't have to include caps in your arrays of consonants and vowels, instead you can downcase before calling include?():
ch = 'A'
vowels.include?(ch.downcase) #=> true
3) When you are debugging, puts and p(for Arrays, Hashes) are your friend. You can find out which elsif branches are executing by adding puts/p statements:
if vowels.include?(word[0])
puts 'X'
pig_latin << word + "ay"
elsif (consonants.include?(word[0]) && consonants.include?(word[1]) && consonants.include?(word[2])) || word[1..2].include?('qu')
puts 'A'
pig_latin << (word[3..-1] + word[0..2] + "ay")
elsif (consonants.include?(word[0]) && consonants.include?(word[1])) || word[0..1].include?('qu')
puts 'B'
pig_latin << (word[2..-1] + word[0..1] + "ay")
else
puts 'C'
pig_latin << (word[1..-1] + word[0] + "ay")
end
end
4) When you are comparing strings, you can use ==. Instead of this:
word[0..1].include?('qu')
...you can write:
if word[0..1].downcase == 'qu'
It's more efficient to use == when you can.
5) Your if conditionals are too complex. If you know what regexes are, you can simplify things by extracting the consonants at the beginning of a word, and then using if statements to test what the consonants are:
words = %w{
schlepp
quail
squall
checkers
}
consonants = ('a'..'z').to_a.join.tr('aeiou', '')
words.each do |word|
md = word.match(/
\A #match start of string, followed by...
[#{consonants}]+ #a consonant, 1 or more times
/x)
if md
starting_consonants = md[0]
#Test for starting_consonants here, e.g.
#if starting_consonants == 'q' and word[1] == 'u'
# do something
else #then word starts with a vowel
...
end
end
--output:--
schl
q
sq
ch
You can limit the number of consonants extracted to three like this:
[#{consonants}]{1,3}
6) I would handle capitalization at the same time you change the words--then you won't have to search through all the letters in every word. First thing, check for capitalization of first letter (then set a flag variable, e.g. capitalized = true). Then downcase the first letter. Then after you change the word, if there was a capital, upcase the first letter(you can also call capitalize(), but the result can be different than just calling upcase() on the first letter). That way you don't have to search through the whole word in your complicated nested loop. Be sure to set the flag variable back to false.
7) In ruby, you rarely use while loops and increment a counter:
while idx2 < word.length
char = word[idx2]
...
...
idx2 += 1
end
Instead, you use each() loops:
word.each_char do |char|
#do something with char
end

How can I check for first letter(s) in string in Ruby?

I'm writing test file, but I can't get it pass second test, here:
def translate(word)
if word.start_with?('a','e','i','o','u')
word << "ay"
else
word << "bay"
end
end
Is start_with? the right method to do the job?
describe "#translate" do
it "translates a word beginning with a vowel" do
s = translate("apple")
s.should == "appleay"
end
it "translates a word beginning with a consonant" do
s = translate("banana")
s.should == "ananabay"
end
it "translates a word beginning with two consonants" do
s = translate("cherry")
s.should == "errychay"
end
end
EDIT:
My solution is not complete.
My code pass first test only because I was able to push "ay" to the end of word. What I'm missing to pass the second test is to remove the first letter if its consonant, which is "b" in "banana".
You can do this also:
word << %w(a e i o u).include?(word[0]) ? 'ay' : 'bay'
Using a Regex might be overkill in your case, but could be handy if you want to match more complex strings.
word << word[0].match(/a|e|i|o|u/).nil? ? 'bay' : 'ay'
Your code means:
if word start with ('a','e','i','o','u') add "ay" at the end
else add "bay" at the end.
Second test will be "bananabay" and not "ananabay" (with b as first letter)
def translate(word)
prefix = word[0, %w(a e i o u).map{|vowel| "#{word}aeiou".index(vowel)}.min]
"#{word[prefix.length..-1]}#{prefix}ay"
end
puts translate("apple") #=> "appleay"
puts translate("banana") #=> "ananabay"
puts translate("cherry") #=> "errychay"
Looks like you are removing the first character if the word starts with a consonant too, so:
if word.start_with?('a','e','i','o','u')
word[0] = ''
word << 'ay'
else
consonant = word[0]
word << "#{consonant}ay"
end
The below piece of code passes all the tests...
def translate(word)
if word.start_with?('a','e','i','o','u')
word<<'ay'
else
pos=nil
['a','e','i','o','u'].each do |vowel|
pos = word.index(vowel)
break unless pos.nil?
end
unless pos.nil?
pre = word.partition(word[pos,1]).first
word.slice!(pre)
word<<pre+'ay'
else
#code to be executed when no vowels are there in the word
#eg words fry,dry
end
end
end
Figured I'd share my first contribution!
Good luck!
def method(word)
word[0].eql?("A" || "E" || "I" || "O" || "U")
end

Pig-Latin method translation

Trying to write Method in ruby that will translate a string in pig-latin , the rule :
Rule 1: If a word begins with a vowel sound, add an "ay" sound to the end of the word.
Rule 2: If a word begins with a consonant sound, move it to the end of the word, and then add an "ay" sound to the end of the word and also when the word begins with 2 consonants , move both to the end of the word and add an "ay"
As a newbie , my prob is the second rule , when the word begin with only one consonant it work , but for more than one , I have trouble to make it work ,Can somebody look at the code and let me know how i can code that differently and probably what is my mistake , probably the code need refactoring. Thanks , so far i come up with this code :
def translate (str)
str1="aeiou"
str2=(/\A[aeiou]/)
vowel = str1.scan(/\w/)
alpha =('a'..'z').to_a
con = (alpha - vowel).join
word = str.scan(/\w/)
if #first rule
str =~ str2
str + "ay"
elsif # second rule
str != str2
s = str.slice!(/^./)
str + s + "ay"
elsif
word[0.1]=~(/\A[con]/)
s = str.slice!(/^../)
str + s + "ay"
else
word[0..2]=~(/\A[con]/)
s = str.slice!(/^.../)
str + s + "ay"
end
end
translate("apple") should == "appleay"
translate("cherry") should == "errychay"
translate("three") should == "eethray"
No need for all those fancy regexes. Keep it simple.
def translate str
alpha = ('a'..'z').to_a
vowels = %w[a e i o u]
consonants = alpha - vowels
if vowels.include?(str[0])
str + 'ay'
elsif consonants.include?(str[0]) && consonants.include?(str[1])
str[2..-1] + str[0..1] + 'ay'
elsif consonants.include?(str[0])
str[1..-1] + str[0] + 'ay'
else
str # return unchanged
end
end
translate 'apple' # => "appleay"
translate 'cherry' # => "errychay"
translate 'dog' # => "ogday"
This will handle multiple words, punctuation, and words like 'queer' = 'eerquay' and 'school' = 'oolschay'.
def translate (sent)
vowels = %w{a e i o u}
sent.gsub(/(\A|\s)\w+/) do |str|
str.strip!
while not vowels.include? str[0] or (str[0] == 'u' and str[-1] == 'q')
str += str[0]
str = str[1..-1]
end
str = ' ' + str + 'ay'
end.strip
end
okay this is an epic pig latin translator that I'm sure could use a bit of refactoring, but passes the tests
def translate(sent)
sent = sent.downcase
vowels = ['a', 'e', 'i', 'o', 'u']
words = sent.split(' ')
result = []
words.each_with_index do |word, i|
translation = ''
qu = false
if vowels.include? word[0]
translation = word + 'ay'
result.push(translation)
else
word = word.split('')
count = 0
word.each_with_index do |char, index|
if vowels.include? char
# handle words that start with 'qu'
if char == 'u' and translation[-1] == 'q'
qu = true
translation = words[i][count + 1..words[i].length] + translation + 'uay'
result.push(translation)
next
end
break
else
# handle words with 'qu' in middle
if char == 'q' and word[i+1] == 'u'
qu = true
translation = words[i][count + 2..words[i].length] + 'quay'
result.push(translation)
next
else
translation += char
end
count += 1
end
end
# translation of consonant words without qu
if not qu
translation = words[i][count..words[i].length] + translation + 'ay'
result.push(translation)
end
end
end
result.join(' ')
end
So this will give the following:
puts translate('apple') # "appleay"
puts translate("quiet") # "ietquay"
puts translate("square") # "aresquay"
puts translate("the quick brown fox") # "ethay ickquay ownbray oxfay"
def translate(sentence)
sentence.split(" ").map do |word|
word = word.gsub("qu", " ")
word.gsub!(/^([^aeiou]*)(.*)/,'\2\1ay')
word = word.gsub(" ", "qu")
end
end
That was fun! I don't like the hack for qu, but I couldn't find a nice way to do that.
So for this pig latin clearly I skipped and\an\in and singular things like a\I etc. I know that wasn't the main question but you can just leave out that logic if it's not for your use case. Also this goes for triple consonants if you want to keep it with one or two consonants then change the expression from {1,3} to {1,2}
All pig latin is similar so just alter for your use case. This is a good opportunity to use MatchData objects. Also vowel?(first_letter=word[0].downcase) is a style choice made to be more literate so I don't have to remember that word[0] is the first letter.
My answer is originally based off of Sergio Tulentsev's answer in this thread.
def to_pig_latin(sentence)
sentence.gsub('.','').split(' ').collect do |word|
translate word
end.compact.join(' ')
end
def translate(word)
if word.length > 1
if word == 'and' || word == 'an' || word == 'in'
word
elsif capture = consonant_expression.match(word)
capture.post_match.to_s + capture.to_s + 'ay'
elsif vowel?(first_letter=word[0].downcase)
word + 'ay'
elsif vowel?(last_letter=word[-1].downcase)
move_last_letter(word) + 'ay'
end
else
word
end
end
# Move last letter to beginning of word
def move_last_letter(word)
word[-1] + word[0..-2]
end
private
def consonant_expression
# at the beginning of a String
# capture anything not a vowel (consonants)
# capture 1, 2 or 3 occurences
# ignore case and whitespace
/^ [^aeiou] {1,3}/ix
end
def vowel?(letter)
vowels.include?(letter)
end
def vowels
%w[a e i o u]
end
Also just for the heck of it I'll include my dump from a pry session so you all can see how to use MatchData. MINSWAN. It's stuff like this that makes ruby great.
pry > def consonant_expression
pry * /^ [^aeiou] {1,3}/ix
pry * end
=> :consonant_expression
pry > consonant_expression.match('Stream')
=> #<MatchData "Str">
pry > capture = _
=> #<MatchData "Str">
pry > ls capture
MatchData#methods:
== begin end hash length offset pre_match regexp string to_s
[] captures eql? inspect names post_match pretty_print size to_a values_at
pry >
pry > capture.post_match
=> "eam"
pry > capture
=> #<MatchData "Str">
pry > capture.to_s
=> "Str"
pry > capture.post_match.to_s
=> "eam"
pry > capture.post_match.to_s + capture.to_s + 'ay'
=> "eamStray"
pry >
If I understood your question correctly, you can just directly check if a character is a vowel or consonant and then use array ranges to get the part of the string you want.
vowels = ['a', 'e', 'i', 'o', 'u']
consonants = ('a'..'z').to_a - vowels
return str + "ay" if vowels.include?(str[0])
if consonants.include?(str[0])
return str[2..-1] + str[0..1] + "ay" if consonants.include?(str[1])
return str[1..-1] + str[0] + "ay"
end
str
Here's a solution that handles the "qu" phoneme as well as other irregular characters. Had a little trouble putting the individual words back into a string with the proper spacing. Would appreciate any feedback!
def translate(str)
vowels = ["a", "e", "i", "o", "u"]
new_word = ""
str.split.each do |word|
vowel_idx = 0
if vowels.include? word[0]
vowel_idx = 0
elsif word.include? "qu"
until word[vowel_idx-2]+word[vowel_idx-1] == "qu"
vowel_idx += 1
end
else
until vowels.include? word[vowel_idx]
vowel_idx += 1
end
end
idx_right = vowel_idx
while idx_right < word.length
new_word += word[idx_right]
idx_right += 1
end
idx_left = 0
while idx_left < vowel_idx
new_word += word[idx_left]
idx_left += 1
end
new_word += "ay "
end
new_word.chomp(" ")
end
I done gone did one too
def translate(string)
vowels = %w{a e i o u}
phrase = string.split(" ")
phrase.map! do |word|
letters = word.split("")
find_vowel = letters.index do |letter|
vowels.include?(letter)
end
#turn "square" into "aresquay"
if letters[find_vowel] == "u"
find_vowel += 1
end
letters.rotate!(find_vowel)
letters.push("ay")
letters.join
end
return phrase.join(" ")
end
def piglatinize(word)
vowels = %w{a e i o u}
word.each_char do |chr|
index = word.index(chr)
if index != 0 && vowels.include?(chr.downcase)
consonants = word.slice!(0..index-1)
return word + consonants + "ay"
elsif index == 0 && vowels.include?(chr.downcase)
return word + "ay"
end
end
end
def to_pig_latin(sentence)
sentence.split(" ").collect { |word| piglatinize(word) }.join(" ")
end
This seems to handle all that I've thrown at it including the 'qu' phoneme rule...
def translate str
letters = ('a'..'z').to_a
vowels = %w[a e i o u]
consonants = letters - vowels
str2 = str.gsub(/\w+/) do|word|
if vowels.include?(word.downcase[0])
word+'ay'
elsif (word.include? 'qu')
idx = word.index(/[aeio]/)
word = word[idx, word.length-idx] + word[0,idx]+ 'ay'
else
idx = word.index(/[aeiou]/)
word = word[idx, word.length-idx] + word[0,idx]+'ay'
end
end
end
I'm grabbing the words with the 'qu' phoneme and then checking all the other vowels [excluding u].
Then I split the word by the index of the first vowel (or vowel without 'u' for the 'qu' cases) and dropping the word part before that index to the back of the word. And adding 'ay' ftw.
Many of the examples here are fairly long. Here's some relatively short code I came up with. It handles all cases including the "qu" problem! Feedback always appreciated (I'm pretty new to coding).
$vowels = "aeiou"
#First, I define a method that handle's a word starting with a consonant
def consonant(s)
n = 0
while n < s.length
if $vowels.include?(s[n]) && s[n-1..n] != "qu"
return "#{s[n..-1]}#{s[0..n-1]}ay"
else
n += 1
end
end
end
#Then, I write the main translate method that decides how to approach the word.
def translate(s)
s.split.map{ |s| $vowels.include?(s[0]) ? "#{s}ay" : consonant(s) }.join(" ")
end

Resources