Calling methods within methods to Titleize in Ruby - 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

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.

How to make only the first element of a array capitalize in Ruby

I've been given a string. First, I transform it into a array and then I try to capitalize only words into the condition. One of the conditions is that the first string is capitalized.
And it's just the one that is left
class Book
# write your code her
attr_reader :title
def title=(new_title)
words = new_title.split(" ")
if words.length > 1
final_title = ""
final_title = words.map! {|a| ((a != "and" && a != "the") && a.length > 2) ? a.capitalize : a}
#title = final_title.join(" ")
else
#title = new_title.capitalize
end
end
end
That's what I've done until now.
I tried to use each_with_index but map! doesn't work with it.
I expected:
"To Kill a Mockingbird"
but I got:
"to Kill a Mockingbird"
I'd start by separating the first word from the remaining words:
first, *rest = new_title.split(' ')
Then I would capitalize the first word:
first.capitalize!
Afterwards, I would capitalize each remaining word matching the conditions:
rest.select { |w| w != 'and' && w != 'the' && w.length > 2 }.each(&:capitalize!)
And finally put everything back together:
[first, *rest].join(' ')
Since version 1.9.3, Ruby has had Enumerator#with_index. The method map without a block returns an Enumerator, so you can do the following:
final_title = words.map.with_index do |word, i|
if i != 0 && (word == "and" || word == "the" || word.length < 3)
word.downcase
else
word.capitalize
end
end
Clearly, you should make sure your title is lowercase to begin with, or the code checking for "and" and "the" won't work.
You can map over an array with an index, but you have to do it like this:
class Book
attr_reader :title
def title=(new_title)
words = new_title.split
final_title = words.map.with_index { |word, i| primary_word?(word) || i == 0 ? word.capitalize : word }
#title = final_title.join(' ')
end
def primary_word?(word)
((word != 'and' && word != 'the') && word.length > 2)
end
end
For clarity, I also extracted the logic for determining if a word should be capitalized into its own method.
Here's a much shorter version, using gsub with block :
It works with any case as input
It checks directly in the regex if the word has at least 3 characters
It uses capitalize on new_title : that way, the first word is capitalized and all the others are lowercase before the processing.
Here's the method :
OMIT_CAPITALIZE = %w(the and)
new_title.capitalize.gsub(/\S{3,}/) do |word|
OMIT_CAPITALIZE.include?(word) ? word : word.capitalize
end
Here's a way to integrate it into your class. title has been added as a parameter to initialize for easier use of Book :
class Book
OMIT_CAPITALIZE = %w(the and)
attr_reader :title
def initialize(title)
self.title = title
end
def title=(new_title)
#title = new_title.capitalize.gsub(/\S{3,}/) do |word|
OMIT_CAPITALIZE.include?(word) ? word : word.capitalize
end
end
end
puts Book.new('to kill a mockingbird').title
# To Kill a Mockingbird
puts Book.new('ThE beauty and THE beast').title
# The Beauty and the Beast
puts Book.new('TO BE OR NOT TO BE').title
# To be or Not to be
Try This:
string.split(' ').map(&:capitalize).join(' ')

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!"

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

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)

Resources