2

I can't find the solution for this problem, I did a research in order to find the problems and fix them but can't think for any answer yet.

What I want to do is to convert a string into a Title cased string.

For example: "The Lord Of The Rings" > "The Lord of the Rings"

(As you can see, the first word is always capitalized, it doesn't matter if it's an article, but if there are articles words in the string, that should be in downcase, as the example above, and capitalize any other words that aren't).

This is the spec (RSpec) of the exercise I'm trying to solve:

describe "Title" do
  describe "fix" do
    it "capitalizes the first letter of each word" do
      expect( Title.new("the great gatsby").fix ).to eq("The Great Gatsby")
    end
    it "works for words with mixed cases" do
      expect( Title.new("liTTle reD Riding hOOD").fix ).to eq("Little Red Riding Hood")
    end
    it "downcases articles" do
      expect( Title.new("The lord of the rings").fix ).to eq("The Lord of the Rings")
      expect( Title.new("The sword And The stone").fix ).to eq("The Sword and the Stone")
      expect( Title.new("the portrait of a lady").fix ).to eq("The Portrait of a Lady")
    end
    it "works for strings with all uppercase characters" do
      expect( Title.new("THE SWORD AND THE STONE").fix ).to eq("The Sword and the Stone")
    end
  end
end

And here's my try, what I have so far:

class Title
  def initialize(string)
    @string = string
  end

  def fix
    @string.split.each_with_index do |element, index|
      if index == 0
        p element.capitalize!
      elsif index == 1
        if element.include?('Is') || element.include?('is')
          p element.downcase!
        end
      end
    end
  end
end

a = Title.new("this Is The End").fix
p a

Output:

"This"

"is"

=> ["This", "is", "The", "End"]


What I tried to do:

  1. Create a class named Title and initialize it with a string.
  2. Create a method called fix that so far, only checks for the index 0 of the @string.split with the method .each_with_index (looping through), and prints the element.capitalize! (note the 'bang', that should modify the original string, as you can see it in the output above)
  3. What my code does is to check for the index 1 (second word) and calls the .include?('is') to see if the second word is an article, if it is (with if statements) the element.downcase! is called, if not, I could create more checkings for index (but what I realized here is that some strings could be formed by 3 words, others by 5, others by 10, and so on, so my code is not efficient for that, that's a problem I can't solve.

Maybe creating a list of article words and check with the .include? method if there's some word of the list? (I tried this one but the .include? method only accepts a string not an array variable, I tried the join(' ') method but had no luck).

Thanks a lot! Really!

fuzzyalej
  • 5,903
  • 2
  • 31
  • 49
bntzio
  • 1,274
  • 14
  • 27

5 Answers5

1

I'd create a new file in the same folder as your program (in my example that file is called "exclude.txt" and put words like "and, the" all on a new line. Then I'd do something like this:

class String
  def capitalize_first_letter
    self[0].upcase + self[1..-1]
  end
end

class Title
  def initialize(string)
    @string = string
    @exclude_words = File.readlines('exclude.txt').map(&:downcase).map(&:chomp)
  end

  def fix
    @string.downcase.gsub(/\S+/) do |word|
      @exclude_words.include?(word) ? word : word.capitalize
    end.capitalize_first_letter
  end
end

Assuming your exclude.txt file contains the words you want to remain downcase (mine contains of, on, a, is, the, and), all of your tests should get passed:

p Title.new("the great gatsby").fix #=> "The Great Gatsby"
daremkd
  • 8,244
  • 6
  • 40
  • 66
1

I believe a module is more appropiate than a class here, since there's no need for "instances of titles" - you begin with strings and end with strings. If titles had more methods, maybe a class would be necessary. Here's my implementation:

The piece you were missing is Array#map. In general, learning as many methods as you can from the base ruby classes (Array, String, Hash, etc) is always a good investment.

module Title
  DEFAULT_NON_CAPITALIZED_WORDS = %w{a an the and but for on at to or}

  def self.titleize(str, nocaps = DEFAULT_NON_CAPITALIZED_WORDS)
    str = str.downcase.split.map{|w| nocaps.include?(w) ? w : w.capitalize}.join(' ')
    str[0].capitalize + str[1..-1]
  end
end

Tests:

puts Title.titleize("the great gatsby")
puts Title.titleize("liTTle reD Riding hOOD")
puts Title.titleize("The sword And The stone")
puts Title.titleize("THE SWORD AND THE STONE")

EDIT: It can be done in a single long line, but it requires a regular expression to do the initial capitalization:

str.downcase.split.map{|w| nocaps.include?(w) ? w : w.capitalize}.join(' ').sub(/^./, &:upcase)
kikito
  • 51,734
  • 32
  • 149
  • 189
1

I like to break these types of problems up into smaller chunks of logic to help me understand before I write an algorithm. In this case you need to modify each word of the string based on some rules.

  1. If it's the first word, capitalize it.
  2. If it's not a special word, capitalize it.
  3. If it's a special word AND it's not the first word, downcase it.

With these rules you can write your logic to follow.

special_words = ['a', 'an', 'and', 'of', 'the']
fixed_words = []
@string.downcase.split.each_with_index do |word, index|
    # If this isn't the first word, and it's special, use downcase
    if index > 0 and special_words.include?(word)
      fixed_words << word
    # It's either the first word, or not special, so capitalize
    else
      fixed_words << word.capitalize
    end
end
fixed_words.join(" ")

You'll notice I'm using downcase on the string before calling split and each_with_index. This is so that all the words get normalized a downcase and can be easily checked against the special_words array.

I'm also storing these transformed words in an array and joining them back together in the end. The reason for that, is if I try to use downcase! or capitalize! on the split strings, I'm not modifying the original title string.

Note: This problem is part of the Bloc Full Stack course work which is why I'm using a simplified solution, rather than one liners, modules, file io, etc.

Tyler Ferraro
  • 3,753
  • 1
  • 21
  • 28
1

Since you want the output as a string, a module method is perhaps more appropriate. The following module allows for easy extension, and is relatively clear.

It looks like this:

module Title
  SMALL_WORDS = %w(a an the at by for in of on to up and as but it or nor)

  def self.titleize(str)
    # Return the original string if it is empty
    return str if str.match(/\A\w*\Z)

    # Split the name into an array of words
    words = name.split(' ')

    # Capitalize every word that is not a small word
    words.each do |word|
      word[0] = word[0].upcase unless SMALL_WORDS.include? word.downcase
    end

    # Capitalize the first and last words
    words.each_with_index do |word, index|
      if index == 0 || index == words.count - 1
        word[0] = word[0].upcase
      end
    end

    # Return the words as a string
    words.join(' ')
  end
end

And it works like this:

Title.titleize('the USA swimming association')
  # => 'The USA Swimming Association'
Title.titleize('the great charter of the liberties of england')
  # => 'The Great Charter of the Liberties of England'
Title.titleize('  a      history  of of ')
  # => 'A History of Of'

There are several edge cases to consider when creating a nice titleize function:

  • The last word should be capitalized no matter what (even if it is a word like "of")
  • Acronyms (like "USA") should be preserved
0

We need to use the each_with_index and the if statements. Notice how I make sure they ignore the first article "the" making using index > 0. Good Luck!

    class Title
  attr_accessor :string
  Articles = %w( an the of a and )

  def initialize(string)
    @string = string
  end

  def fix
    fixed_words = []
    @string.downcase.split.each_with_index do |word, index|
      if index > 0 && Articles.include?(word)
        fixed_words << word
      else
        fixed_words << word.capitalize
      end
    end
    fixed_words.join(" ")
  end
end

p Title.new("the great gatsby").fix
p Title.new("liTTle reD Riding hOOD").fix
p Title.new("The lord of the rings").fix
p Title.new("The sword And The stone").fix
p Title.new("the portrait of a lady").fix
p Title.new("THE SWORD AND THE STONE").fix
Alberto
  • 1
  • 2