修改 Ruby 中包含特定单词的数组项

Modifying an array item in Ruby if it includes a specific word

我找不到这个问题的解决方案,我进行了研究以找到问题并解决它们,但还想不出任何答案。

我想要做的是将一个字符串转换成标题大小写的字符串

例如: "The Lord Of The Rings" > "The Lord of the Rings"

(可以看到,第一个单词总是大写,如果是文章就无所谓,但是如果字符串中有文章单词,那应该是小写的,如上面的例子,并将任何其他不是的单词大写)。

这是我要解决的练习的规格 (RSpec)

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

这是我的尝试,到目前为止我所拥有的:

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

输出:

"This"

"is"

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


我尝试做的事情:

  1. 创建一个 class 命名的 Title 并用字符串初始化它。
  2. 创建一个名为 fix 的方法,到目前为止,它只检查索引 0 @string.split.each_with_index 方法(循环 through),并打印 element.capitalize!(注意 'bang',即 应该修改原始字符串,正如您在输出中看到的那样 以上)
  3. 我的代码所做的是检查索引 1(第二个字)和 调用 .include?('is') 来查看第二个单词是否是文章, 如果是(使用 if 语句),则调用 element.downcase!, 如果没有,我可以为索引创建更多检查(但我意识到 这里是一些字符串可以由 3 个单词组成,其他的由 5 个单词组成, 其他人增加 10,依此类推,所以我的代码对此效率不高, 这是我无法解决的问题。

也许创建一个文章单词列表并使用 .include 检查?如果列表中有某个单词,方法是什么? (我试过这个,但是 .include? 方法只接受字符串而不是数组变量,我试过 join(' ') 方法但没有成功)。

非常感谢! 真的!

我将在与您的程序相同的文件夹中创建一个新文件(在我的示例中,该文件称为 "exclude.txt" 并将 "and, the" 之类的词全部放在一个新行上。然后我' d 做这样的事情:

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

假设您的 exclude.txt 文件包含您希望保持小写的单词(我的包含 of、on、a、is、the 和 and),您的所有测试都应该通过:

p Title.new("the great gatsby").fix #=> "The Great Gatsby"

我认为这里的模块比 class 更合适,因为不需要 "instances of titles" - 您以字符串开头并以字符串结尾。如果标题有更多的方法,也许 class 是必要的。这是我的实现:

您遗漏的部分是 Array#map。一般来说,从基础 ruby classes(数组、字符串、哈希等)中学习尽可能多的方法总是一项很好的投资。

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

测试:

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

编辑:它可以在一个长行中完成,但它需要一个正则表达式来完成首字母大写:

str.downcase.split.map{|w| nocaps.include?(w) ? w : w.capitalize}.join(' ').sub(/^./, &:upcase)

我喜欢将这些类型的问题分解成更小的逻辑块,以帮助我在编写算法之前理解。在这种情况下,您需要根据一些规则修改字符串的每个单词。

  1. 如果是第一个字,大写。
  2. 如果不是特殊词,就大写。
  3. 如果它是一个特殊词并且不是第一个词,请将其小写。

使用这些规则,您可以编写要遵循的逻辑。

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(" ")

您会注意到我在调用 split 和 each_with_index 之前对字符串使用了小写字母。这是为了让所有的单词都标准化为小写,并且可以很容易地对照 special_words 数组进行检查。

我还将这些转换后的单词存储在一个数组中,最后将它们重新组合在一起。这样做的原因是,如果我尝试使用小写字母!或大写!在拆分字符串上,我没有修改原始标题字符串。

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.

由于您希望输出为字符串,因此模块方法可能更合适。下面的模块方便扩展,也比较清晰。

看起来像这样:

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

它是这样工作的:

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'

创建漂亮的 titleize 函数时需要考虑几个边缘情况:

  • 最后一个字无论如何都要大写(即使是像"of"这样的字)
  • 应保留首字母缩略词(如 "USA")

我们需要使用 each_with_index 和 if 语句。请注意我如何确保他们忽略第一篇文章 "the" making using index > 0。祝你好运!

    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