This question already has answers here:
Test if variable matches any of several strings w/o long if-elsif chain, or case-when
(3 answers)
Closed 6 years ago.
I cannot get my validator to function properly.
I need to allow the user to be able to enter letters A-F (upper and lowercase) while giving them an error if they enter otherwise.
Here is my code:
print "Enter a letter A-E to add to your order "
items=gets.upcase.chomp
if items != ("A" || "B" || "C" || "D" || "E")
puts ("Incorrect Letter")
end
It functions correctly if 'A' or 'a' are entered, but it does not work at all for any of the other numbers. It seems simple enough that it should work.
Any idea what I'm doing wrong here?
if (items != "A" || items != "B" || items != "C" || items != "D" || items != "E")
is a working version with the "||".
unless items.between?("A", "E")
is perhaps more readable.
("A" || "B" || "C" || "D" || "E") is an expression that always evaluates to "A", since "A" is "truthy". Therefore, your if statement is equivalent to if items != 'A'
To check if the input is one of many options, use Array's include? function: http://ruby-doc.org/core-2.2.0/Array.html#method-i-include-3F
if ["A", "B", "C", "D", "E"].include?(items)
("A" || "B" || "C" || "D" || "E") always returns "A" in Ruby, since "A" is a not nil of false:
nil || "B"
#=> "B"
false || "B"
#=> "B"
"A" || false
#=> "A"
"A" || "B"
#=> "A"
"A" || "B" || "C"
#=> "A"
and so on.
You should use include:
unless ["A","B","C","D","E"].include?(items) puts ("Incorrect Letter") end
Since you want to include both lower- and uppercase letters, I suggest this:
unless ("a".."e").include?(items.downcase) puts ("Incorrect Letter")
Related
I only want to do a simple loop:
I want something like:
loop do
puts "What hotel would you like to pick"
hotelCode = gets.chomp.downcase #gets user input and puts it lowerCase
if hotelCode != "a" || hotelCode != "b" || hotelCode != "c" || hotelCode != "d" # if user input is not a,b,c,d break
break
else
puts "How many nights would you like to stay"
nights = gets.chomp.to_i
end
end #end while loop
puts "congrats u got out"
In my code, it just keeps breaking out of the loop no matter what I do. Am I missing something obvious?
Maybe you want your loops ends if the input is NONE of those choice. So
if hotelCode != "a" && hotelCode != "b" & hotelCode != "c" && hotelCode != "d"
better
if !["a", "b", "c", "d"].include?(hotelCode)
even better
if !%w(a b c d).include?(hotelCode)
or
unless %w(a b c d).include?(hotelCode)
From your code, it should be something like this:
loop do
puts "What hotel would you like to pick"
hotelCode = gets.chomp.downcase #gets user input and puts it lowerCase
if hotelCode != "a" && hotelCode != "b" && hotelCode != "c" && hotelCode != "d" # if user input is not a,b,c,d break
break
else
puts "How many nights would you like to stay"
nights = gets.chomp.to_i
end
end #end while loop
puts "congrats u got out"
if hotelCode != "a" || hotelCode != "b" || ...
If hotel code is "b", it will break on the first condition. If it is "a", it will break on the second. This condition is impossible to satisfy.
Either use
if hotelCode != "a" && hotelCode != "b" && ...
or
if hotelCode == "a" || hotelCode == "b" || ...
# handle valid hotel
else
break
end
Simple boolean math :) Or better yet, use one of Ursus' examples.
I would suggest using a method of the String class. Here are several, ordered by my personal preference (high to low).
hotel_code !~ /[abcd]/
hotel_code =~ /[^abcd]/
!"abcd".include?(hotel_code)
"abcd".index(hotel_code).nil?
hotel_code.count("abcd").zero?
hotel_code.delete("abcd") == hodel_code
"abcd".delete(hotel_code) == "abcd"
The second returns an integer ("truthy") or nil ("falsy"); the others return true or false.
This question already has answers here:
Why word 'translate' is messing irb?
(2 answers)
Closed 6 years ago.
The issue is with the following code:
#write your code here
def translate phrase
phrase = phrase.downcase.split(/ /)
phrase.collect! do |word|
word = word.split(//)
switched = false
while switched == false
word.each_index do |letter|
if word[letter] == ("a" || "e" || "i" || "o" || "u")
switched = true
word = (word[letter..-1] + word[0..(letter-1)]).join + "ay"
end
end
end
end
return phrase.join(" ")
end
puts translate("chocolate cream")
#Should return "ocolatechay eamcray"
When I run this, Ruby just returns a blank line. So, to troubleshoot the issue, I loaded the definition into a repl. The repl returned the following error:
NoMethodError: undefined method `downcase' for #<RubyVM::InstructionSequence:0x000000016e8f88>
from /home/adc/odin-project/web-development-101/21-ruby-tdd/ruby_tdd_project/learn_ruby/04_pig_latin/pig_latin.rb:3:in `translate'
from /home/adc/.rvm/rubies/ruby-2.3.0/bin/irb:11:in `<main>'
If I remove downcase from my code, I get the same error message, only this time with split.
What's the problem here? (I'm pretty confident that downcase and split are not the problem.)
If returns a blank string because you don't return anything inside the collect! block. Return word and it will work :
def translate phrase
phrase = phrase.downcase.split(/ /)
phrase.collect! do |word|
word = word.split(//)
switched = false
while switched == false
word.each_index do |letter|
if word[letter] == ("a" || "e" || "i" || "o" || "u")
switched = true
word = (word[letter..-1] + word[0..(letter-1)]).join + "ay"
end
end
end
word
end
return phrase.join(" ")
end
puts translate("chocolate cream")
#=> atechocolay amcreay
It doesn't look like it's returning exactly what you expected, but it's still better than a blank string.
As for your weird error message in the console, it seems to be specific to the REPL (possibly because of the method name translate).
Update 1 (how I would write this code)
def translate phrase
phrase = phrase.downcase.split(/ /)
phrase.collect! do |word|
word = word.split(//)
word.each_index do |letter|
if ["a", "e", "i", "o", "u"].include?(word[letter])
if letter == 0
word = word[letter..-1].join + "ay"
else
word = (word[letter..-1] + word[0..(letter-1)]).join + "ay"
end
break
end
end
word
end
return phrase.join(" ")
end
puts translate("chocolate cream")
Update 2 (if I make some minor changes to your code to make it useful)
def translate phrase
phrase = phrase.downcase.split(/ /)
phrase.collect! do |word|
word = word.split(//)
switched = false
while switched == false
word.each_index do |letter|
if ["a", "e", "i", "o", "u"].include?(word[letter])
switched = true
if letter == 0
word = word[letter..-1].join + "ay"
else
word = (word[letter..-1] + word[0..(letter-1)]).join + "ay"
end
break
end
end
end
word
end
return phrase.join(" ")
end
puts translate("chocolate cream")
Explanation
"o"==("a" || "e" || "i" || "o" || "u")
and
"o"== "a" || "o" == "e" || "o" == "i" || "o" == "o" || "o" == "u"
statements are not the same. First one is false
(because ("a" || "e" || "i" || "o" || "u")=="a" and "o"!="a"), since the second one is true.
You need inner if statement
if letter == 0
word = word[letter..-1].join + "ay"
else
word = (word[letter..-1] + word[0..(letter-1)]).join + "ay"
end
because when word starts with any mentioned vowels then
word[letter..-1] + word[0..(letter-1)]
statement will return the whole word twice, cause letter will equal to 0.
And lastly you need to return word object after the while loop.
I can't make my code pass this test:
it "translates two words" do
s = translate("eat pie")
s.should == "eatay iepay"
end
I don't see the flaw in my logic, though it may be very brute force and there may be a simpler way of passing the test:
def translate(string)
string_array = string.split
string_length = string_array.size
i=0
while i < string_length
word = string_array[i]
if word[0] == ("a" || "e" || "i" || "o" || "u")
word = word + "ay"
string_array[i] = word
elsif word[0] != ( "a" || "e" || "i" || "o" || "u" ) && word[1] != ( "a" || "e" || "i" || "o" || "u" )
word_length = word.length-1
word = word[2..word_length]+word[0]+word[1]+"ay"
string_array[i] = word
elsif word[0] != ( "a" || "e" || "i" || "o" || "u" )
word_length = word.length-1
word = word[1..word_length]+word[0]+"ay"
string_array[i] = word
end
i += 1
end
return string_array.join(" ")
end
Here's the test failure message:
Failures:
1) #translate translates two words
Failure/Error: s.should == "eatay iepay"
expected: "eatay iepay"
got: "ateay epiay" (using ==)
# ./04_pig_latin/pig_latin_spec.rb:41:in `block (2 levels) in <top (required)>'
The additional code checking other conditions are for other tests that I already passed. Basically, now I'm checking a string with two words.
Please let me know how I can make the code pass the test. Thank you in advance!
"a" || "e" || "i" || "o" || "u" is evaluated to "a" because "a" is truth value. (not a nil, not a false):
irb(main):001:0> ("a" || "e" || "i" || "o" || "u")
=> "a"
irb(main):002:0> "a" == ("a" || "e" || "i" || "o" || "u")
=> true
irb(main):003:0> "e" == ("a" || "e" || "i" || "o" || "u")
=> false
How about using Array#include? instead:
irb(main):001:0> %w{a e i o u}.include? "a"
=> true
irb(main):002:0> %w{a e i o u}.include? "e"
=> true
or using =~ (regular expression match):
irb(main):007:0> "e" =~ /[aeiou]/
=> 0
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I am struggling finding a way to verify these methods and was wondering if anyone knew a basic way to do this?
class PigLatinTest < MiniTest::Unit::TestCase
def test_word_beginning_with_a
assert_equal "appleay", PigLatin.translate("apple")
end
def test_other_word_beginning_e
assert_equal "earay", PigLatin.translate("ear")
end
def test_word_beginning_with_p
assert_equal "igpay", PigLatin.translate("pig")
end
For example the first one might be:
module PigLatin
class Word
def initialize(word)
#word = word.to_s
end
# remember to use the .to_s method
def translate(word)
if word[0] == "a" || "e" || "o" || "u" || "i"
word = word + "ay"
elsif word[0] != "a" || "e" || "o" || "u" || "i"
word = word-word[0]+"ay"
end
end
end
# you can add method here even outside of the class ...
end
------------in another file
module PigLatin
class Word
# remember to use the .to_s method
end
# you can add method here even outside of the class ...
end
Your translate method won't work. The problem is here:
if word[0] == "a" || "e" || "o" || "u" || "i"
and
elsif word[0] != "a" || "e" || "o" || "u" || "i"
You can't compare that way as the right side of either will not do what you think it will.
Some simple checks will show why there's something wrong:
'abc'[0] == "a" || "e" || "o" || "u" || "i" # => true
'efg'[0] == "a" || "e" || "o" || "u" || "i" # => "e"
'opq'[0] == "a" || "e" || "o" || "u" || "i" # => "e"
'xyz'[0] == "a" || "e" || "o" || "u" || "i" # => "e"
'abc'[0] != "a" || "e" || "o" || "u" || "i" # => "e"
'efg'[0] != "a" || "e" || "o" || "u" || "i" # => true
'opq'[0] != "a" || "e" || "o" || "u" || "i" # => true
'xyz'[0] != "a" || "e" || "o" || "u" || "i" # => true
Why are those wrong? Let's look at what's happening:
When the word starts with 'a', the test 'a' == 'a' is true:
'abc'[0] == "a" # => true
If we || ("or") true and something, we get true back because it was the first "true" value seen:
true || "e" # => true
If the first test failed, then || causes the second test to be evaluated, which in your code was "e", and wasn't a test, but Ruby didn't know that, and thought it was a "true" return value so it became the result of the expression:
false || "e" # => "e"
Knowing that, a correct way to write this would be:
'abc'[0] == "a" || 'abc'[0] == "e" || 'abc'[0] == "o" || 'abc'[0] == "u" || 'abc'[0] == "i" # => true
'efg'[0] == "a" || 'efg'[0] == "e" || 'efg'[0] == "o" || 'efg'[0] == "u" || 'efg'[0] == "i" # => true
'opq'[0] == "a" || 'opq'[0] == "e" || 'opq'[0] == "o" || 'opq'[0] == "u" || 'opq'[0] == "i" # => true
'xyz'[0] == "a" || 'xyz'[0] == "e" || 'xyz'[0] == "o" || 'xyz'[0] == "u" || 'xyz'[0] == "i" # => false
'abc'[0] != "a" && 'abc'[0] != "e" && 'abc'[0] != "o" && 'abc'[0] != "u" && 'abc'[0] != "i" # => false
'efg'[0] != "a" && 'efg'[0] != "e" && 'efg'[0] != "o" && 'efg'[0] != "u" && 'efg'[0] != "i" # => false
'opq'[0] != "a" && 'opq'[0] != "e" && 'opq'[0] != "o" && 'opq'[0] != "u" && 'opq'[0] != "i" # => false
'xyz'[0] != "a" && 'xyz'[0] != "e" && 'xyz'[0] != "o" && 'xyz'[0] != "u" && 'xyz'[0] != "i" # => true
however, that rapidly becomes hard to read and unwieldy, so something more concise is needed:
%w[a e o u].include? 'abc'[0] # => true
%w[a e o u].include? 'efg'[0] # => true
%w[a e o u].include? 'opq'[0] # => true
%w[a e o u].include? 'xyz'[0] # => false
!%w[a e o u].include? 'abc'[0] # => false
!%w[a e o u].include? 'efg'[0] # => false
!%w[a e o u].include? 'opq'[0] # => false
!%w[a e o u].include? 'xyz'[0] # => true
There is a problem with this though; As the array size increases, more loops are required to compare to the [0] value, which slows the code unnecessarily. A regular expression, written correctly, can get rid of that looping so the speed stays very constant:
'abc'[0][/[aeou]/] # => "a"
'efg'[0][/[aeou]/] # => "e"
'opq'[0][/[aeou]/] # => "o"
'xyz'[0][/[aeou]/] # => nil
Notice though, that instead of getting true/false, the results are the character matched by the pattern or nil. In Ruby, only nil and false are considered false values, and everything else is true, so we can translate those into true, true, true, false respectively, but by taking advantage of the ! operator we can make it even more clear:
!!'abc'[0][/[aeou]/] # => true
!!'efg'[0][/[aeou]/] # => true
!!'opq'[0][/[aeou]/] # => true
!!'xyz'[0][/[aeou]/] # => false
It might seem that we'd have to use !!! to "not" the results like we'd want when using !=, but that isn't necessary. A single ! will do the same thing:
!'abc'[0][/[aeou]/] # => false
!'efg'[0][/[aeou]/] # => false
!'opq'[0][/[aeou]/] # => false
!'xyz'[0][/[aeou]/] # => true
But wait! There's more! Even that can be improved upon a slight amount by removing the string slice ([0]) and using a regex anchor. Compare these two, and their benchmark:
require 'fruity'
ALPHABET = ('a'..'z').to_a.join
compare do
slice_it { ALPHABET[0][/[aeou]/] }
regex_it { ALPHABET[/^[aeou]/] }
end
# >> Running each test 8192 times. Test will take about 1 second.
# >> regex_it is faster than slice_it by 39.99999999999999% ± 10.0%
So, using something like:
'abc'[/^[aeou]/] # => "a"
!'abc'[/^[aeou]/] # => false
!!'abc'[/^[aeou]/] # => true
will be fast and compact and let you test to see what a string starts with.
Here is the code:
def main_menu
print_main_menu
user_selected = gets.chomp
if user_selected.downcase == "no"
main_menu
elsif user_selected == "1" || "2" || "3" || "4" || "5" || "6" || "7"
user_selected = user_selected.to_i
call_option(user_selected)
else
main_menu
end
end
This code uses calls to allow a user to make a selection from a main menu. Depending on the input, be it a certain number, a certain word, or something else, the respective method is called (in the case of a valid input) or the main menu is printed again (in the case of an invalid input or "no").
My questions are twofold.
Is there an efficient way to get rid of the literal string error that appears as a result of this redundant or statement on the elsif line? (the code itself works fine, but this error appears and is frustrating).
When an alternate/unspecified input is made by the user, the else branch doesn't execute and main_method doesn't start over. I have no idea why this is happening. Is there something I'm missing here?
Thanks
Your elsif line is not semantically valid relative to your intentions. In Ruby, you can't check a variables value against several other values via x == v1 || v2 || .... You might want to consider using a Ruby case statement.
def main_menu
print_main_menu
user_selected = gets.chomp
case user_selected.downcase
when "no"
main_menu
when "1", "2", "3", "4", "5", "6", "7"
user_selected = user_selected.to_i
call_option(user_selected)
else
main_menu
end
end
It should be noted that the following expression won't do what you think it should:
elsif user_selected == "1" || "2" || "3" || "4" || "5" || "6" || "7"
This will not compare user_selected with each of the items separated by the logical OR.
The expression you are using, although not having the intent you are wanting, will not generate an error since this is a valid Ruby expression:
exp-1 || exp-2 || exp-3 || ... || exp-N
Which has the value of the first exp-i which is "truthy". That is, the first expression which doesn't evaluate to either false or nil.
So in this case, it represents a logical check of the Ruby expression:
(user_selected == "1") || "2" || "3" || "4" || "5" || "6" || "7"
The result of this expression will, as described above, be the first subexpression which is "truthy". If user_selected has the value "1", then the result will be true and the elsif will succeed. If the user_selected is not "1", then the value of the expression above will be "2" since it is "truthy", and the elsif will still succeed. In fact, it will always succeed (since "2" is always "truthy").
So your code will appear to work unless you enter something like 9 in which case the code will attempt to execute call_option(9) and perhaps generate some unexpected result depending upon what call_option does with unexpected argument values.
use case:
case user_selected
when "1", "2", "3", "4", "5", "6", "7"
user_selected = user_selected.to_i
call_option(user_selected)
else # includes "no"
main_menu
end