将字符串转换为正确的标题大小写

时间:2015-02-02 19:39:19

标签: ruby string title-case

我有这个练习:

  

编写一个用字符串初始化的Title类。

     

它有一个方法 - fix - 应返回字符串的标题版本:

     

Title.new("a title of a book").fix =   书的标题
  您需要使用条件逻辑 - ifelse语句来完成这项工作   确保仔细阅读测试规范,以便了解要实现的条件逻辑。

     

您想要使用的一些方法:

     

String#downcase   String#capitalize   Array#include?

此外,这是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

谢谢@simone,我收录了您的建议:

class Title
attr_accessor :string

def initialize(string)
@string = string
end

IGNORE = %w(the of a and)

def fix
s = string.split(' ')
s.map do |word|
  words = word.downcase
  if IGNORE.include?(word)
    words
  else
    words.capitalize
  end
end
s.join(' ')
end
end

虽然我在运行代码时仍遇到错误:

expected: "The Great Gatsby"
 got: "the great gatsby"

(compared using ==)

exercise_spec.rb:6:in `block (3 levels) in <top (required)>' 

从我的初学者的角度来看,我看不出我做错了什么?

最终编辑:我只是想感谢每个人在早些时候帮助我的所有努力。我将展示我能够制作的最终工作代码:

class Title
attr_accessor :string

def initialize(string)
@string = string
end

def fix
word_list = %w{a of and the}

a = string.downcase.split(' ')
b = []

a.each_with_index do |word, index|
  if index == 0 || !word_list.include?(word)
    b << word.capitalize
  else
    b << word
  end
end
b.join(' ')
end
end

4 个答案:

答案 0 :(得分:2)

这是一个可能的解决方案。

class Title
  attr_accessor :string

  IGNORES = %w( the of a and )

  def initialize(string)
    @string = string
  end

  def fix
    tokens = string.split(' ')
    tokens.map do |token|
      token = token.downcase

      if IGNORES.include?(token)
        token
      else
        token.capitalize
      end
    end.join(" ")
  end

end

Title.new("a title of a book").fix

你的出发点很好。以下是一些改进:

  • 比较总是小写的。这将简化if条件
  • 被忽略的项目列表是一个数组。这将简化if条件,因为你不需要为每个被忽略的字符串使用if(它们可能是数百个)
  • 我使用地图来替换令牌。使用具有枚举的块来循环项目
  • 是一种常见的Ruby模式

答案 1 :(得分:0)

def fix
   string.downcase.split(/(\s)/).map.with_index{ |x,i| 
     ( i==0 || x.match(/^(?:a|is|of|the|and)$/).nil? ) ? x.capitalize : x 
   }.join
end

符合所有条件:

  1. 全部小写< / LI>
  2. 将所有其他词汇大写
  3. 所有第一个单词都是大写的

  4. 解释

    1. string.downcase调用一个操作来制作您正在使用小写字母
    2. 的字符串
    3. .split(/(\s)/)获取小写字符串并将其在空白(空格,制表符,换行符等)上拆分为数组,使每个单词成为数组的元素;括号中的\s(分隔符)周围也会将其保留在返回的数组中,因此重新加入时我们不会丢失该空白字符
    4. .map.with_index{ |x,i|遍历返回的数组,其中x是值,i是索引号;每次迭代都返回一个新数组的元素;当循环完成后,你将有一个新数组
    5. ( i==0 || x.match(/^(?:a|is|of|the|and)$/).nil? )如果它是数组中的第一个元素(索引为0),或者单词匹配aisofthe,或and - 也就是说,匹配不是nil - 然后x.capitalize(大写单词),否则(它确实匹配忽略的单词)所以只返回单词/值,x
    6. .join获取我们的新数组并将所有单词再次组合成一个字符串
    7. 其他

      • 通常,正则表达式中括号内的内容被视为捕获组,这意味着如果内部模式匹配,则特殊变量将在正则表达式操作完成后保留该值。在某些情况下,例如\s我们想要捕获该值,因为我们重用它,在其他情况下,比如我们的忽略词,我们需要匹配,但不需要捕获它们。为了避免捕获匹配,您可以在捕获组的开头调整?:以告知正则表达式引擎不保留该值。这有很多好处超出了这个答案的范围。

答案 2 :(得分:0)

有两种方法可以解决这个问题:

  • 将字符串分解为单词,可能会修改每个单词并将单词重新组合在一起;或
  • 使用正则表达式。

我会对后者说些什么,但我相信你的练习涉及前者 - 这是你采取的方法 - 所以我会专注于此。

将字符串拆分为单词

您使用String#split(' ')将字符串拆分为单词:

str = "a title of a\t   book"
a = str.split(' ')
  #=> ["a", "title", "of", "a", "book"] 

这很好,即使有额外的空白,但通常写道:

str.split
  #=> ["a", "title", "of", "a", "book"] 

两种方式都与

相同
str.split(/\s+/)
  #=> ["a", "title", "of", "a", "book"] 

请注意,我已使用变量a表示数组已返回。有些人可能认为描述性不够,但我认为它比s更好,这有点令人困惑。 : - )

创建枚举器

接下来,您发送方法Enumerable#each_with_index以创建枚举器:

enum0 = a.each_with_index
  # => #<Enumerator: ["a", "title", "of", "a", "book"]:each_with_index> 

要查看枚举器的内容,请将enum0转换为数组:

enum0.to_a
  #=> [["a", 0], ["title", 1], ["of", 2], ["a", 3], ["book", 4]] 

您已使用each_with_index,因为第一个词 - 索引为0的词 - 与其他词的处理方式不同。没关系。

到目前为止,非常好,但此时您需要使用Enumerable#mapenum0的每个元素转换为适当的值。例如,第一个值["a", 0]将转换为&#34; A&#34;,下一个值将转换为&#34;标题&#34; &#34;&#34;&#34;&#34;。

因此,您需要将方法Enumerable#map发送到enum0

enum1 = enum.map
  #=> #<Enumerator: #<Enumerator: ["a", "title", "of", "a",
        "book"]:each_with_index>:map> 
enum1.to_a
  #=> [["a", 0], ["title", 1], ["of", 2], ["a", 3], ["book", 4]] 

如你所见,这会创建一个新的枚举器,它可以被认为是一个&#34;复合物&#34;枚举器。

enum1的元素将通过Array#each传递到块中。

调用枚举器并加入

您希望将第一个单词和除文章开头之外的所有其他单词大写。因此,我们必须定义一些文章:

articles = %w{a of it} # and more
  #=> ["a", "of", "it"]

b = enum1.each do |w,i|
  case i
  when 0 then w.capitalize
  else articles.include?(w) ? w.downcase : w.capitalize
  end
end
  #=> ["A", "Title", "of", "a", "Book"] 

最后我们加入数组,每个单词之间有一个空格:

b.join(' ')
  => "A Title of a Book" 

查看计算详情

让我们回到b的计算。 enum1的第一个元素被传递到块中并分配给块变量:

w, i = ["a", 0] #=> ["a", 0] 
w               #=> "a" 
i               #=> 0 

所以我们执行:

case 0
when 0 then "a".capitalize
else articles.include?("a") ? "a".downcase : "a".capitalize
end

返回"a".capitalize => "A"。同样,当enum1的下一个元素传递给块时:

w, i = ["title", 1] #=> ["title", 1] 
w               #=> "title" 
i               #=> 1 

case 1
when 0 then "title".capitalize
else articles.include?("title") ? "title".downcase : "title".capitalize
end

返回&#34;标题&#34;自articles.include?("title") => false起。下一个:

w, i = ["of", 2] #=> ["of", 2] 
w               #=> "of" 
i               #=> 2 

case 2
when 0 then "of".capitalize
else articles.include?("of") ? "of".downcase : "of".capitalize
end

返回&#34;&#34;自articles.include?("of") => true

链接操作

把这些放在一起,我们有:

str.split.each_with_index.map do |w,i|
  case i
  when 0 then w.capitalize
  else articles.include?(w) ? w.downcase : w.capitalize
  end
end
  #=> ["A", "Title", "of", "a", "Book"] 

替代计算

另一种不使用each_with_index的方法就是这样:

first_word, *remaining_words = str.split
first_word
  #=> "a" 
remaining_words
  #=> ["title", "of", "a", "book"] 

"#{first_word.capitalize} #{ remaining_words.map { |w|
  articles.include?(w) ? w.downcase : w.capitalize }.join(' ') }"
   #=> "A Title of a Book" 

使用正则表达式

str = "a title of a book"

str.gsub(/(^\w+)|(\w+)/) do
  $1 ? $1.capitalize :
    articles.include?($2) ? $2 : $2.capitalize
end
  #=> "A Title of a Book" 

正则表达式&#34;捕获&#34; [(...)]字符串[(^\w+)]或[|]一开头的单词,不一定位于字符串[(\w+)]的开头。两个捕获组的内容分别分配给全局变量$1$2

因此,单步执行字符串的单词,第一个单词"a"将被捕获组#1捕获,因此不会评估(\w+)。捕获组#1(所以$1 => nil)不会捕获每个后续单词,而是由捕获组#2捕获。因此,如果$1不是nil,我们会将(第一个)单词(句子)大写;否则我们将$2大写,如果该单词不是文章,如果它是文章则保持不变。

答案 3 :(得分:0)

这是问题的另一种可能解决方案

class Title
  attr_accessor :str
  def initialize(str)
   @str = str
  end

  def fix
    s = str.downcase.split(" ") #convert all the strings to downcase and it will be stored in an array
    words_cap = []
    ignore = %w( of a and the ) # List of words to be ignored
    s.each do |item|
      if ignore.include?(item) # check whether word in an array is one of the words in ignore list.If it is yes, don't capitalize. 
        words_cap << item

      else
        words_cap << item.capitalize
      end  
    end
    sentence = words_cap.join(" ") # convert an array of strings to sentence
    new_sentence =sentence.slice(0,1).capitalize + sentence.slice(1..-1) #Capitalize first word of the sentence. Incase it is not capitalized while checking the ignore list.
  end


end