I'm working through The Odin Projects Ruby basics and completely stuck on 05_book_titles.
The title needs to be capitalized, including the 1st word but not including "small words" (ie "to", "the", etc) UNLESS it's the 1st word.
I can't get the code to do anything besides capitalize everything. Am I misusing map method? How can I get it to include the no_cap words in the returned title without capitalizing?
The Ruby file:
class Book
def title
#title
end
def title=(title)
no_cap = ["if", "or", "in", "a", "and", "the", "of", "to"]
p new_title = #title.split(" ")
p new_new_title = new_title.map{|i| i.capitalize if !no_cap.include? i}
.join(" ")
end
end
Some of the Spec file:
require 'book'
describe Book do
before do
#book = Book.new
end
describe 'title' do
it 'should capitalize the first letter' do
#book.title = "inferno"
expect(#book.title).to eq("Inferno")
end
it 'should capitalize every word' do
#book.title = "stuart little"
expect(#book.title).to eq("Stuart Little")
end
describe 'should capitalize every word except...' do
describe 'articles' do
specify 'the' do
#book.title = "alexander the great"
expect(#book.title).to eq("Alexander the Great")
end
specify 'a' do
#book.title = "to kill a mockingbird"
expect(#book.title).to eq("To Kill a Mockingbird")
end
specify 'an' do
#book.title = "to eat an apple a day"
expect(#book.title).to eq("To Eat an Apple a Day")
end
end
specify 'conjunctions' do
#book.title = "war and peace"
expect(#book.title).to eq("War and Peace")
end
end
end
end
Result:
Book
title
should capitalize the first letter (FAILED - 1)
Failures:
1) Book title should capitalize the first letter
Failure/Error: #book.title = "inferno"
NoMethodError:
undefined method `split' for nil:NilClass
# ./05_book_titles/book.rb:8:in `title='
# ./05_book_titles/book_titles_spec.rb:25:in `block (3 levels) in <top (required)>'
Finished in 0.0015 seconds (files took 0.28653 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./05_book_titles/book_titles_spec.rb:24 # Book title should capitalize the first letter
You are using #title before it's assigned in
new_title = #title.split(" ")
It should be changed to title.
You don't assign the calculated title to #title at the end of the title= method.
You also need to add 'an' to no_cap in order to pass the spec using "to eat an apple a day" as title.
And take care of the first word:
class Book
def title
#title
end
def title=(title)
no_cap = ["if", "or", "in", "a", "and", 'an', "the", "of", "to"]
new_title = title.split(' ').each_with_index.map do |x, i|
unless i != 0 && no_cap.include?(x)
x.capitalize
else
x
end
end
#title = new_title.join(' ')
end
end
small_words = ["if", "or", "in", "a", "and", "the", "of", "to"]
str = "tO be Or Not to be."
str.gsub(/\p{Alpha}+/) { |s| Regexp.last_match.begin(0) > 0 &&
small_words.include?(s.downcase) ? s.downcase : s.capitalize }
#=> "To Be or Not to Be."
Related
class Book
# write your code here
attr_accessor :title
def title= (title)
#title = title.split()
#title = #title.map {
|x|
index = #title.index(x)
if x == 'and' or x == 'in' or x == 'of' or x == 'the' or x == 'a' or x == 'an' and index != 0
x = x
else
x.capitalize
end
}
#title = #title.join(" ")
return #title
end
end
This is an exercise from the Project Ruby on the Odin Project. It's about capitalizing the titles of a book bound to some certain conditions life if the word is a preposition or an article or a conjunction then dont capitalize it unless it occurs in the beginning of the title then capitalize it. I have written the code for it but it isn't working as you can see :
index = #title.index(x)
if x == 'and' or x == 'in' or x == 'of' or x == 'the' or x == 'a' or x == 'an' and index != 0
x = x
else
x.capitalize
end
But again it doesn't work
expected: "The Man in the Iron Mask"
got: "The Man in The Iron Mask"
The second The gets capitalized too when I have said in the if statement that if it isn't equal to the first word then don't capitalize it but it still capitalizes it.
Because index(x) always returns the first match.
I would rewrite it like this:
class Book
attr_accessor :title
DO_NOT_CAPITALIZE = %w[and in of the a an]
def title=(title)
words = title.split.map do |word|
# capitalize all word that are not excluded
DO_NOT_CAPITALIZE.include?(word) ? word : word.capitalize
end
# always capitalize the first word
#title = words.join(' ').capitalize
end
end
The problem with your code has been identified. One way to obtain the desired result is to use String#gsub with a simple regular expression.
LITTLE_WORDS = %w| a an and in of the |
#=> ["a", "an", "and", "in", "of", "the"]
LWR = /\b#{LITTLE_WORDS.join('|')}\b/i
#=> /\ba|an|and|in|of|the\b/i
def normalize(str)
str.gsub(/\S+/) do |word|
if Regexp.last_match.begin(0).zero?
word.capitalize
else
word.match?(LWR) ? word.downcase : word.capitalize
end
end
end
normalize("tHe cat and A hAt")
#=> "The Cat and a Hat"
See Regexp::last_match (same as the value of the global variable $~) and MatchData#begin. Notice that this preserves spacing in the string.
In the publishing industry such articles, prepositions and conjunctions are often referred to as "little words", and written "a", "an", "and", "in", "of", "the".
class Book
attr_accessor :title
def title=(book_name)
small_words = ["and", "in", "the", "of", "a", "an"]
words = book_name.split(' ')
words.each do |word|
small_words.include?(word) ? word : word.capitalize!
end
words[0].capitalize!
#title = words.join(' ')
end
end
So I've been learning Ruby and have been doing some Ruby tests. This one was to capitalize all book titles except for 'and', 'in', 'the' e.t.c. I understand most of it, except a couple things.
After reading the errors I could see that it wanted a class called Book, so I did that. Why is this needed?
And then attr_accessor :title with #title = words.join(' ') at the end. I understand what they mean, but why are they needed?
Basically let's say your input (book_name) is "the call of the cthulhu".
words = book_name.split(' ')
Splits into array of "the", "call", "of", "the", "cthulhu".
words.each do |word|
small_words.include?(word) ? word : word.capitalize!
end
Iterates over the array and capitalizes all the words in place except the words in small_words. Array now has "the", "Call", "of", "the", "Cthulhu".
words[0].capitalize!
Capitalizes the first word. So now the array has "The", "Call", "of", "the", "Cthulhu".
#title = words.join(' ')
Joins the array in one word (The Call of the Cthulhu) and sets it in the title -instance variable, which is referenced in Ruby with # -syntax inside the class.
attr_accessor creates a setter and a getter for the title, which is needed when not inside the class.
x = book.title # x is now "The Call of the Cthulhu"
book.title = "New Title" # sets the title to a new String
How do I get the .include? to work? When the user chooses a character, I want the console to print the puts ok statement and then go to the if statement.
name = {"1" => "Mario",
"2" => "Luigi",
"3" => "Kirby",
}
puts "Peach's apocalypse, will you survive?"
def character (prompt, options)
puts = "who will you be?"
options = name[1] || name[2] || name[3]
character = gets.chomp.downcase
until character.include? name
end
puts "ok #{name} all three of you run out of peach's castle which has been overrun"
if character = name[1] || name[2] || name[3]
puts ("zombies are in the castle grounds, there are weapons over the bridge")
puts "What do you do, charge through or sneak?"
x = gets.chomp.downcase
if x == "sneak"
puts "oh you died"
if x == "charge through"
puts "the zombies tumbled over the bridge's edge, you made it safe and sound"
else
puts "you did nothing and were eaten alive by Princess Peach"
end
end
end
end
It looks like you're calling include? on a string. This will only return true if you pass it a substring of itself. For example:
"Mario".include?("Mar") #=> true
You want to call include? on the array of keys in the name hash. You could do:
name.values.include?(character)
or more concisely
name.has_value?(character)
Here's some documentation on the include? method of the Array class and the include? method of the string class, as well as the has_value? method of the Hash class.
There's considerably more that needs modifying for this program to run as you're expecting it to though. Here's one working implementation:
puts "Peach's apocalypse, will you survive?"
names = {
"1" => "Mario",
"2" => "Luigi",
"3" => "Kirby"
}
def choose_character(character = "", options)
puts = "who will you be?"
options.each do |num, name|
puts "#{num}: #{name}"
end
until options.has_key? character or options.has_value? character
character = gets.chomp.capitalize
end
return options[character] || character
end
name = choose_character(names)
puts "ok #{name} all three of you run out of peach's castle which has been overrun"
puts "zombies are in the castle grounds, there are weapons over the bridge"
puts "What do you do, charge through or sneak?"
case gets.chomp.downcase
when "sneak"
puts "oh you died"
when "charge through"
puts "the zombies tumbled over the bridge's edge, you made it safe and sound"
else
puts "you did nothing and were eaten alive by Princess Peach"
end
The answer above is great and features awesome refactoring, but I would use
character = gets.strip.downcase
instead as it also gets rid of any potential whitespace.
To elaborate on the string thing, 'gets' stands for 'get string' (or at least so I was taught), so everything you get via 'gets' will be a string until you convert it further. Consider this:
2.2.1 :001 > puts "put in your input"
put in your input
=> nil
2.2.1 :002 > input = gets.strip
5
=> "5"
2.2.1 :003 > input.class
=> String
You would have to use .to_i to convert your input back to integer.
I have requirements with my Title class as below.
Take an all-lower-case string like "the united states" and make the initial letter in each word capitalized ("The United States").
Take a camel case string like "ThE UnIted STatEs" and make it "The United States".
The following code satisfies them:
class Title
attr_accessor :string
def initialize(string)
#string = string
end
def fix
string2 = string.split(" ").map{ |string| string.capitalize }.join(" ")
end
end
I added another condition:
If the string is "the", "The", "of", "Of", it does not capitalize it.
The attempt to modify fix with map logic as below did not work:
class Title
def fix
string2 = string.split(" ").map{ |string| string.capitalize }.join(" ")
string2.split(" ").map{ |string| (string.include?("of","Of","the","The") ? string.downcase : string.capitalize) }.join(" ")
end
end
#=> Error: wrong number of arguments (2 for 1)
Is there another way I can implement this logic? I'm not sure why this isn't working for me. Can anyone offer any assistance/guidance?
String#include only takes one argument, that's where the ArgumentError is coming from. Instead you could do something like:
[8] pry(main)> prepositions = ["of", "Of", "the", "The"]
=> ["of", "Of", "the", "The"]
[9] pry(main)> string2.split(" ").map{ |string| prepositions.include?(string) ? string.downcase : string.capitalize }.join(" ")
=> "of Thy Self In the Capital"
I prefer the above, it allows you to easily keep a list of words that are outside the normal capitalization method. It's easy to read, easy to add to etc. That said, you can use case insensitive regex with match as well:
string2.split(" ").map{ |string| string.match(/(the)|(of)/i) ? string.downcase : string.capitalize }.join(" ")
Use gsub
You don't need to convert the string to an array of words, map the words, then join. Instead, just use the form of String#gsub that takes a block.
Little Words
You said you do not want to capitalize certain words. Editors often refer to such words as "little words". Let's define a few:
LITTLE_WORDS = %w{ the of for a an or and }
#=> ["the", "of", "for", "a", "an", "or", "and"]
Code
I assume that all little words encountered are be downcased, and all other words are to be downcased and capitalized. We can do that thus:
def fix(str)
str.gsub(/\w+/) do |w|
if LITTLE_WORDS.include?(w.downcase)
w.downcase
else
w.capitalize
end
end
end
Examples
Let's try it:
fix("days of wine aNd roses") #=> "Days of Wine and Roses"
fix("of mice and meN") #=> "of Mice and Men"
Hmmm. A bit of a problem with the second example. Presumably, we should capitalize the first word regardless of whether it's a little word. There are various ways to do that.
#1 Capitalize the first word after modifying all words
def fix(str)
str.gsub(/\w+/) do |w|
if LITTLE_WORDS.include?(w.downcase)
w.downcase
else
w.capitalize
end
end.sub(/^(\w+)/) { |s| s.capitalize }
end
fix("of mice and men")
#=> "Of Mice and Men"
Notice that I've introduced a capture group in the regex. Alternatively, you could change the penultimate line to:
end.sub(/^(\w+)/) { $1.capitalize }
#2 Set a flag
def fix(str)
first_word = true
str.gsub(/\w+/) do |w|
if LITTLE_WORDS.include?(w.downcase) && !first_word
w.downcase
else
first_word = false
w.capitalize
end
end
end
fix("of mice and men")
#=> "Of Mice and Men"
#3 Use an index
def fix(str)
str.gsub(/\w+/).with_index do |w,i|
if LITTLE_WORDS.include?(w.downcase) && i > 0
w.downcase
else
w.capitalize
end
end
end
fix("of mice and men")
#=> "Of Mice and Men"
#4 Modify the regex
def fix(str)
str.gsub(/(^\w+)|\w+/) do |w|
if $1.nil? && LITTLE_WORDS.include?(w.downcase)
w.downcase
else
w.capitalize
end
end
end
fix("of mice and men")
#=> "Of Mice and Men"
More problems
Now we need just fix:
fix("I bought an iPhone and a TV")
#=> "I Bought an Iphone and a Tv"
I am trying to write a very simple method in Ruby which takes a string and an array of words and checks if the string contains any of the words and if it does it replaces them with their uppercase.
I made an attempt but its not great due to my level of Ruby skills.
def(my_words,my_sentence)
#split the sentence up into an array of words
my_sentence_words = my_sentence.split(/\W+/)
#nested loop that checks the words array for each brand
my_sentence_words.each do |i|
my_words.each do |i|
#if it finds a brand in the words and sets them to be uppercase
if my_words[i] == my_sentence_words[i]
my_sentence_words[i] == my_sentence_words[i].up.case
end
end
end
#put the words array into one string
words.each do |i|
new_sentence = ("" + my_sentence_words[i]) + " "
end
end
I am getting: can't convert string into integer error
def convert(mywords,sentence)
regex = /#{mywords.join("|")}/i
sentence.gsub(regex) { |m| m.upcase }
end
convert(%W{ john james jane }, "I like jane but prefer john")
#=> "I like JANE but prefer JOHN"
This will work better. It loops through the brands, searches for each, and replaces with the uppercase version.
brands = %w(sony toshiba)
sentence = "This is a sony. This is a toshiba."
brands.each do |brand|
sentence.gsub!(/#{brand}/i, brand.upcase)
end
Results in the string.
"This is a SONY. This is a TOSHIBA."
For those who like Ruby foo!
sentence.gsub!(/#{brands.join('|')}/i) { |b| b.upcase }
And in a function
def capitalize_brands(brands, sentence)
sentence.gsub(/#{brands.join('|')}/i) { |b| b.upcase }
end
You get this error because i doesn't start from 0 as you expected, in each method i is an element of array, and has string type, it's a first word from your sentence:
my_sentence_words = ["word"]
my_sentence_words.each do |i|
puts i.length #=> 4
puts i.type #=> String
puts i #=> word
end
So you try to call my_sentence_words[word] instead of my_sentence_words[0]. You can try method each_index that passes index of element instead of element itself`:
def check(str, *arr)
upstr = str.split(' ')
upstr.eachindex do |i| #=> i is index
arr.each_index do |j|
upstr[i].upcase! if upstr[i] == arr[j]
end
end
upstr
end
check("This is my sentence", "day", "is", "goal", "may", "my")
#=>["This", "IS", "MY", "sentence"]