Ruby string method, can't make exception using .unless method - ruby

I'm working on changing letters of a string, 'e' to '3', 'o' to '0', 'I to '1' and 's' to 'z'. All my specs work but I can't figure out how to make an exception to 's'. If 's' is the first letter, it should not change. I've tried using .unless, but can't make it work. I've read multiple sites on how to implement .unless, but still can't make it work.
class String
def altspeak
new = []
old = self.split("")
old.each do |letter|
if letter == "e"
letter = "3"
new.push(letter)
elsif letter == "o"
letter = "0"
new.push(letter)
elsif letter == "I"
letter = "1"
new.push(letter)
elsif letter == "s"
letter = "z"
new.push(letter)
else
new.push(letter)
end
end
new.join
end
end
I've tried Variations of plugging .unless into the elsif, but I'm just guessing:
elsif letter == "s"
letter = "z"
unless old.first == "s"
new.push(letter)
Any suggestions?

To test for first letter, use each_with_index instead of each:
old.each_with_index do |letter, index|
...
elsif letter == "s" && index != 0
...
end
Tip: you don't need to use split, you could use self.each_char.each_with_index.
Advanced class:
class String
def altspeak
self.gsub /(?!^s)[eoIs]/ do |m|
"301z"["eoIs".index(m)]
end
end
end
"somethings".altspeak
# => "s0m3thingz"
This will search for letters e, o, I and s within the string, but not if they are s at the beginning of string, then substitute them with their corresponding replacements.

This is one way of doing it.
elsif letter == "s"
unless old.first == "s" && new.length.zero?
letter = "z"
end
new.push(letter)

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.

Ruby doesn't recognize `downcase` or `split` as defined methods? [duplicate]

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.

Ruby, how to change every "s" in each word of string, except if "s" is first letter

Trying to create a simple multi-word string method that replaces some of the letters of a string (3 for "e", 0 for "o", 1 for "I", and "z" for "s" (unless "s" is first letter in word. So, "sons sams roses" will change to "sonz samz roz3z".
All of my specs pass except for not changing the first 's' of each word in a string. I can't seem to go from working with each letter in a string to each word, targeting the first letter.
class String
def leetspeak
word = self.split("")
new_word = []
word.each do |i|
if i == "e"
new_word.push(3)
elsif i == "o"
new_word.push(0)
elsif i == "I"
new_word.push(1)
elsif (i == "s") && ((word.find_index(" ") + 1) != "s")
new_word.push("z")
else
new_word.push(i)
end
end
new_word.join
end
end
On the last elsif, I thought I could target the first letter using .find_index(" ") + 1, but it doesn't work, and even if it did, it doesn't work for the very first letter in the string, if it was "s". Any help would be appreciated.
There's many ways to accomplish that. Do you require this implementation to be using if/else? Otherwise, here follows a simpler one:
def leetspeak
self
.gsub('e', '3')
.gsub('o', '0')
.gsub('I', '1')
.gsub(/(?!^)s/, 'z')
end
The /(?!^)s/ part is a Regular Expression (or regex) that means everything that's an s except if it's the first character. The (?!something) is the negative lookahead, and the ^ is the beginning of the string. The gsub method replaces globally (in the whole string) any substring which is matched by the expression, and replaces it with the second argument.
I'm sure that are better ways to do that, both in performance and in clarity, but I guess this implementation should be good enough.
UPDATE:
I had misread the question, I've fixed the regex to not match every first s in any word using the \b, which stands for word boundary.
def leetspeak
self
.gsub('e', '3')
.gsub('o', '0')
.gsub('I', '1')
.gsub(/(?!\b)s/, 'z')
end
You can use each_with_index method.
class String
def leetspeak
word = self.split("")
new_word = []
word.each_with_index do |i, index|
if i == "e"
new_word.push(3)
elsif i == "o"
new_word.push(0)
elsif i == "I"
new_word.push(1)
elsif (i == "s") && index != 0
new_word.push("z")
else
new_word.push(i)
end
end
new_word.join
end
end
"ss".leetspeak
=> "sz"
you can invoke leetspeak method for each word:
"ss roses sams".split(" ").map{|e| e.leetspeak}.join(" ")
=> "sz r0z3z samz"
I don't understand what do you want but word.find_index(" ") returns an index and you should use the index like this:
elsif (i == "s") && ((word[word.find_index(" ") + 1]) != "s")
My example:
str = "egad!, she repeated, it's VI o'clock\nI really must go!"
If I understand the correctly, here are a couple of ways that use the hash:
replacements = { "e"=>"3", "o"=>"0", "I"=>"1", "s"=>"z" }
and the regex:
r = /
[a-rt-z] # match any lower case letter other than s
| # or
(?<=\w) # match a word character in a positive lookbehind
s # match s
/ix # case indifferent and extended mode
#1
str.gsub(r) { |s| replacements[s] || s }
#=> "3gad!, sh3 r3p3at3d, it's V1 0'cl0ck\n1 r3ally muzt g0!"
#2
replacements.default_proc = ->(_,k) { k }
str.gsub(r, replacements)
#=> "3gad!, sh3 r3p3at3d, it's V1 0'cl0ck\n1 r3ally muzt g0!"

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