Ruby -- If Elsif Else Error - ruby

I'm getting an error here with a simple if else chain, and I can't figure out what is going on. I started learning ruby the other day, I already know some java, and was just trying to re-write programs to learn ruby faster. I am trying to tally vowels and consonants. Anyways here is my code...
#!/usr/bin/ruby/
alphabet = 'abcdefghijklmnopqrstuvwxyz'
array = alphabet.chars.to_a
vowel = 0
cons = 0
puts array.at(1)
for i in 0...26
if array.at(i) == "a"
vowel++
elsif array.at(i) == 'e'
vowel++
elsif array.at(i) == 'i'
vowel++
elsif array.at(i) == 'o'
vowel++
elsif array.at(i) == 'u'
vowel++
else
cons++
end#end if else chain
end#end for loop
puts 'Vowel: ' + vowel.to_s
puts 'Consonants: ' + cons.to_s
Here is the error I am getting:
C:/Users/Kelan/Documents/Programming/Ruby
Files/Little Programs/Alphabet.rb:11:
syntax error, unexpected keyword_elsif
elsif array.at(i) == 'e'
^
C:/Users/Kelan/Documents/Programming/Ruby
Files/Little Programs/Alphabet.rb:13:
syntax error, unexpected keyword_elsif
elsif array.at(i) == 'i'
^
C:/Users/Kelan/Documents/Programming/Ruby
Files/Little Programs/Alphabet.rb:15:
syntax error, unexpected keyword_elsif
elsif array.at(i) == 'o'
^
C:/Users/Kelan/Documents/Programming/Ruby
Files/Little Programs/Alphabet.rb:17:
syntax error, unexpected keyword_elsif
elsif array.at(i) == 'u'
^
C:/Users/Kelan/Documents/Programming/Ruby
Files/Little Programs/Alphabet.rb:19:
syntax error, unexpected keyword_else
C:/Users/Kelan/Documents/Programming/Ruby
Files/Little Programs/Alphabet.rb:21:
syntax error, unexpected keyword_end
C:/Users/Kelan/Documents/Programming/Ruby
Files/Little Programs/Alphabet.rb:25:
syntax error, unexpected $end,
expecting keyword_end puts
'Consonants: ' + cons.to_s
^
[Finished in 0.203 seconds]
I'm sure it's just something silly, but I've been looking forever online for help and I have heard of your great community, so I thought I would try here,
Kelan

There is no ++ operator in Ruby. You should have used += 1
You may also want to learn about case statement:
alphabet = 'abcdefghijklmnopqrstuvwxyz'
26.times do |i|
case alphabet[i]
when 'a' then vowel += 1
when 'e' then vowel += 1
when 'i' then vowel += 1
when 'o' then vowel += 1
when 'u' then vowel += 1
else cons += 1
end#end case
end#end times
puts 'Vowel: ' + vowel.to_s
puts 'Consonants: ' + cons.to_s
Or, even better, use method count from class String, like this:
alphabet = 'abcdefghijklmnopqrstuvwxyz'
vowels = 'aeiou'
vowel_count = alphabet.count vowels
cons_count = alphabet.length - vowel_count
puts "Vowels: #{vowel_count}"
puts "Consonants: #{cons_count}"

Your problem is you're using the Java/PHP/C style increment operator. Ruby isn't down with that. You have to use foo += 1 instead.
How about I show you a more Ruby way of doing this though?
# use a range to define your alphabet
alphabet = ('a'..'z').entries #=> ['a', 'b', 'c', ...]
# define vowels as members of an array. it's more flexible, which
# is great for things that change (what if you decide to use 'y'?)
vowels = %w{ a e i o u } #=> ['a', 'e', 'i', 'o', 'u']
# keep counts all together in a hash, which I personally find cleaner
counts = { :vowels => 0, :consonants => 0 }
# even the `for` loops in ruby use the iterators, so you actually
# get better performance out of using the more user-friendly `.each`
alphabet.each do |letter|
if vowels.include? letter
counts[:vowels] += 1
else
counts[:consonants] += 1
end
end
puts "There were #{counts[:vowels]} vowels and #{counts[:consonants]} consonants."

I think rather than vowel++ and con++, you need to use vowel+=1 and con+=1.
Ruby does not have C-style pre/post incrementors.

Here is yet another way to write the demo:
puts("%d vowels & %d consonants" % ('a'..'z').inject([0,0]) do |m, e|
m[/[aeiou]/.match(e) ? 0:1] += 1; m
end)

There is an easy way of constructing the set of alphabets, using Range.
Since you are using ruby, you should use internal iterators instead of the external ones. You will rarely see a for loop in a good program in ruby.
case construction is handy in this case. You can put multiple matching patterns into one, separated by comma.
There is no ++ or -- operators in ruby. Use += 1.
Use "#{ }" notation. It's much better and faster than using +. In fact, you can omit to_s if you use it.
I would go like this:
vowel = 0
cons = 0
('a'..'z').each do |c|
case c
when 'a', 'e', 'i', 'o', 'u'; vowel += 1
else cons += 1
end
end
puts "Vowel: #{vowel}"
puts "Consonants: #{cons}"
If I wanted a shorter one, I might go with this:
partition = ('a'..'z').group_by{|c| c =~ /[aeiou]/}
puts "Vowel: #{partition[0].length}"
puts "Consonants: #{partition[nil].length}"

Related

if statement doesn't get executed in Ruby

I'm new to Ruby programming language and i am asked to make a small program that does the following:
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.
but in my if else statement it doesn't go into the if even if its true it stays at the else statement
i have tried taking the string and converting it into an array and work on the array and tried working on the string as is
def translate (str)
i = 0
while i < str.length
if (str[i] == "a" or str[i] == "e" or str[i] == "o" or str[i] == "u" or str[i] == "i")
str = str + "ay"
return str
else
temp = str[0...1]
str = str[1...str.length]
str = str + temp
end
i = i + 1
end
end
s = translate("banana")
puts s
the program doesn't enter the if statement at all and keeps getting into the else statement until the word returns the same with out any changes
Aside from my suggestion to use || instead of or, your method doesn't need a #while iterator since you're checking only for the first letter. The if/else statement should be executed only once.
You can also replace all the checks with a single #include? method like this:
def translate (str)
if %w[a e i o u].include?(str[0])
str + "ay"
else
str[1..-1] + str[0] + "ay"
end
end
Notice that I've also removed the return statement since the last executed line will be returned, so either line 3 or line 5 in the method above.
You can also add a ternary operator to make it in one line:
%w(a e i o u).include?(str[0]) ? str + "ay" : str[1..-1] + str[0] + "ay"
Rule 1: If a word begins with a vowel sound, add an "ay" sound to the
end of the word.
translate("ana")
# ~> "anaay"
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.
translate("banana")
# ~> "ananabay"
If I understand the problem correctly, you do not need to loop at all here. You just need to check the first letter, and not all of the letters.
def translate (str)
if str[0] == 'a' or str[0] == 'e' or str[0] == 'o' or str[0] == 'u' or str[0] == 'i'
str + 'ay'
else
temp = str[0...1]
str = str[1...str.length]
str = str + temp
str + 'ay'
end
end
By the way, I was able to figure this out with the debugger. Did you try that at all? Also, with your original code it turns out that for some inputs (like 'baaan'), your else statement does execute.
I don't see a problem with or or || in this case.
The problem I see is that if the start letter is a consonant, you changing str rotating it's letters at each iteration (see the commented part of the code), so the starting letter is never a vowel.
Then you are missing a returning value at the end so it returns nil and puts nothing.
def translate (str)
i = 0
while i < str.length
p str[i] # it's never a vowel
if (str[i] == "a" or str[i] == "e" or str[i] == "o" or str[i] == "u" or str[i] == "i")
str = str + "ay"
return str
else # here you are rotating the word
temp = str[0...1]
str = str[1...str.length]
str = str + temp
p str
end
i = i + 1
end
# missing a return value
end
s = translate("banana")
p s
So it prints out:
# "b"
# "ananab"
# "n"
# "nanaba"
# "n"
# "anaban"
# "b"
# "nabana"
# "n"
# "abanan"
# "n"
# "banana"
# nil
The code works correctly in case the first letter is a vowel, so it enters the if true:
s = translate("ananas")
p s
#=> "ananasay"
By the way, as already posted by others, you don't need any while loop. Just checking the first letter with an if statement is enough.

Question on how to filter x || y and not x && y

I am having trouble using || ("or").
This is the first time I select using the "or" feature and I have been trying to select the words that are greater than 6 characters long OR start with an "e". I tried everything but I keep getting just one feature or an "and". This is the code so far
def strange_words(words)
selected_words = []
i = 0
while i < words.length
word = words[i]
if word.length < 6
selected_words << word
end
i += 1
end
return selected_words
end
print strange_words(["taco", "eggs", "we", "eatihhg", "for", "dinner"])
puts
print strange_words(["keep", "coding"])
Using the || operator is the same as writing multiple if statements. Let's use a silly example to demonstrate it. Say you wanted to determine if a word started with the letter 'e'. Well there are a few forms of 'e'. There is the lowercase e and the upppercase E. You want to check for both forms so you could do something like this:
def starts_with_e?(string)
result = false
if string[0] == 'e'
result = true
end
if string[0] == 'E'
result = true
end
result
end
Notice however that you're doing the same actions after checking for the condition. This means you could simplify this code using the OR/|| operator, like such:
def starts_with_e?(string)
result = false
if string[0] == 'e' || string[0] == 'E'
result = true
end
end
For your specific question, you can do the following:
def strange_words(words)
words.select { |word| word.length < 6 || word[0] == 'e' }
end
When you run with your example, it gives you this output:
> strange_words(["taco", "eggs", "we", "eatihhg", "for", "dinner"])
=> ["taco", "eggs", "we", "eatihhg", "for"]
This is still not good code. You'll want to protect the methods from bad input.

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

Pig Latin converter

I'm currently working on an exercise which involves converting the sentence "The quick brown fox" to “Hetay uickqay rownbay oxfay”
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..words[i].length] + translation + 'ay'
result.push(translation)
next
end
break
else
# handle words with 'qu' in middle
if char == 'q' and translation[i-1] == 'u'
qu = true
translation = words[i][count +1..words[i].length] + 'ay'
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
puts translate("The quick brown fox")
However, I'm getting "ethay uickqay ownbray oxfay" instead of “Hetay uickqay rownbay oxfay”.
Where are the areas that needs correction? I couldn't pinpoint the problem. Could you show me the solution?
This is a very procedural and complex way of going about this. I'm not sure what your rules for checking q and u are for, since the pig latin translation rules make no mention of qu as a special case:
A far simpler way is to split the sentence into an array of words, and transform each word as required:
def translate(sent)
translation = sent.split(' ').map do |w|
vowels = %w(a e i o u)
word = w.downcase.split('')
if vowels.include? word[0]
"#{word}way"
else
"%s%say" % [word[1..-1], word[0]]
end
end
translation.join(' ').capitalize
end
puts translate("The quick brown fox")
# outputs Hetay uickqay rownbay oxfay
And, for 1.9 and likely above:
def translate(sent)
translation = sent.split(' ').map do |w|
vowels = %w(a e i o u)
word = w.downcase.split('')
if vowels.include? word[0]
"#{word.join}way"
else
"%s%say" % [word[1..-1].join, word[0]]
end
end
translation.join(' ').capitalize
end
puts translate("The quick brown fox")
Obviously, both of these are examples and can likely be made far better with work. But they serve to illustrate the point.
This makes use of map and join, and can probably be optimised further. The key difference between this method and yours is that you are attempting to build up a map of the translation iteratively, when you probably do not need to. Use the enumeration functions, they are part of what makes the functional programming style more expressive. Learn to think "how do I permute this data set to get my desired response" as opposed to "What steps do I need to perform to get my desired response".

Calling methods within methods to Titleize in Ruby

I am trying to create a titleizing method for a programming assignment, it capitalizes certain words and ignores others. It always capitalizes the first word. To this end, I made a method that finds the first word of a string, and tried to call it within the titleize method. I'm getting an error that says "warning: string literal in condition". I've tried changing the phrasing of the if loop around, but it's not fixing my error. Can anyone explain to my why my code is broken? Thanks so much for your help!
def first_word(str)
array = str.split(' ')
return array[0]
end
def titleize(str)
words = str.split
words.each do |word|
if word != first_word(str)
word.capitalize!
elsif word != 'and' or 'the'
word.capitalize!
end
words.join ' '
end
end
Change the following
elsif word != 'and' or 'the'
to
elsif word != 'and' or word != 'the'
The operator != has higher precedence than or. It means that this line
elsif word != 'and' or 'the'
is equivalent to
elsif (word != 'and') or 'the'
and not to
elsif word != ('and' or 'the')
as you probably expected. The latter equivalence should be expressed as
elsif word != 'and' or word != 'the'
but even in this case it would not make a lot of sense and it's very hard to read.
You may want to change the link to
elsif !%w(and the).include?(word)
str = 'abc'
p "hi" if str == '1' or '12'
#=> warning: string literal in condition
or
str = 'abc'
p "hi" if (str == '1' or '12')
#=> warning: string literal in condition
p "hi" if '12'
#=> warning: string literal in condition
This happened as ruby interpreter sees your code as below:
p "hi" if str == '1' or true
The second one will always evaluates to true, because '12' always exist. The warning is saying that instead of a boolean or test, you have a string literal, '12', which always evaluates to true.
So a fix is as below:
p "hi" if str == '1' or str == '12' #=> "hi"
p "hi" if ['1','12'].include? str #=> "hi"
Not sure how readable this is. But it's short!
def titleize(str)
str.capitalize.split.map do |word|
%w{and the}.include?(word.downcase) ? word : word.capitalize
end.join(' ')
end

Resources