迭代具有多个全大写名称的 CSV 并将它们更改为正确的大写

Iterating through a CSV that has a number of all-caps names and changing them to proper capitalization

我有一大堆 table 的名字,其中一些全大写。我想遍历 table 并将全部大写的名称变成常规大写的名称。我有几个问题:

  1. 我很确定我没有正确地将 CSV 转换为数组以供 Ruby 处理
  2. 当我使用一些虚拟数据作为我放入 .each 循环的数组时,我的 .downcase!.capitalize!似乎适用于 |word|虽然 .each 循环在数组上 运行 但数组末尾不包含固定词

这是我尝试将我拥有的 CSV 文件转换为 Ruby 数组:

require "csv"

puts "What is the name of the CSV you want to clean?"
print ">> "
csv_name = $stdin.gets.chomp

array = CSV.parse(File.open(csv_name, "r+"))

这是虚拟数据数组,带有 .each 循环,它应该更正数组中每个全大写条目的大写,并且似乎这样做了,只是它没有坚持:

array = [["Thing", "Whatsit", "Object", "Foo", "Bar", "Baz", "Let", "Get", "Met"],
[1, 2, 3, "NULL", 5, 6, "Stuff", "test", "CRAZY"],
["THING", "LADY", "TERROR OF THE NIGHT", "MIGHTY", "nothing", "SYCOPHANT", "NEXT", "WILD", "WIFE"]]

array[0..(array.count - 1)].flatten.each do |word|
  if word.to_s == word
    if word.upcase == word
      ary = word.split
      ary[0..(ary.count-1)].each do |w|
        w.downcase!.capitalize!
      end
      word = ary.join(" ")
      puts word
      word
    else
      word
    end
    word
  else
    word
  end
end

print array

如有任何建议,我们将不胜感激!

我想这可能是您要找的:

array = array.flatten.map{|c| c.is_a?(String) ? c.split.map(&:capitalize).join(' ') : c}

你可以在解析CSV的时候使用自定义转换器来处理进来的数据。CSV库自带了一套标准的转换器,可以帮助解析整数、日期等,但是你可以自由定义定制的。

require 'csv'
CSV::Converters[:fixcaps] = ->(v) { v.capitalize rescue v }
CSV.parse(File.read(csv_name), converters: :fixcaps)
=> [["Header1", "Header2", "Header3"], ["Thing", "1", "Thing"], ["Whatsit", "2", "Lady"], ["Object", "3", "Terror of the night"],  ...]

(我把测试数据精简了一些,所以和你给的不完全吻合。)

客户转换器简单地进行小写和大写。在出现错误的情况下(主要是因为字段是 nil,转换器只是 return 原始数据作为后备。

编辑

我错过了问题的一部分,其中只有 all-caps 个单词需要修复。有几种方法可以通过正则表达式实现这一点,但这里是一个:

CSV::Converters[:fixcaps] = ->(field) do
  field.gsub(/(?<!\S)[[:upper:]]+(?!\S)/) { |w| w.capitalize }
rescue
  field
end 

此外,在您的一条评论中,您提到真实数据有 headers,最终您希望 re-write 作为 CSV。你可以很容易地处理这两个问题:

require 'csv'
CSV::Converters[:fixcaps] = ->(v) { v.capitalize rescue v }
data = CSV.parse(File.read(csv_name), converters: :fixcaps, headers: true)
File.write('output.csv', data.to_csv)

在这种情况下,指定 headers: true 会导致 CSV.parse 到 return 一个 CSV::Table object 而不是原始嵌套数组(在 运行 东西通过我们的自定义转换器)。 CSV::Table#to_csv然后以CSV格式写出数据,headers先。

如果需要,您始终可以在 CSV::Table 上调用 .to_a 以获得等效的嵌套数组,但如果您有其他处理要做,Table 提供了用于操作的工具数据(例如,在 row/column 方向之间切换,只处理某些列等)比只处理嵌套数组更容易理解。

创建 CSV 文件

假设我们希望读取一个 CSV 文件,其内容由 str:

的内容给出
str =<<~END
Fld1,Fld2,Fld3,Fld4,Fld5
Thing A MA-Jig,Whatsit,Object,FoO AND 1 GOO,Bar
1,NULL,Stuff,test,CRAZY
LADY,TERROR OF THE NIGHT,nothing,LEFT,nEXT
END

File.write('in.csv', str)
  #=> 140

让我们确认它是否正确写入。

puts File.read('in.csv')
Fld1,Fld2,Fld3,Fld4,Fld5
Thing A MA-Jig,Whatsit,Object,FoO AND 1 GOO,Bar
1,NULL,Stuff,test,CRAZY
LADY,TERROR OF THE NIGHT,nothing,LEFT,nEXT

读取 CSV 文件

这里的技巧是定义一个 自定义转换器 来对具有 属性 的所有字符串执行转换,即字符串中包含的所有字母均为大写。题目要求这些字符串全部大写

自定义转换器是在读取文件时转换某些字段内容的过程。 Here 是自定义转换器的一些示例。我们想要的自定义转换器可以这样写

convert_all_caps = ->(value) do
  value.gsub(/\S+/) do |word|
    word == word.upcase ? word.capitalize : word
  end
end
  #=> #<Proc:0x0000583f969bdf88@(irb):467 (lambda)>

正则表达式/\S/匹配除空格以外的一个或多个字符。

请注意,lambda 有一个可选的第二个参数,它是传递值的 header(例如,"Fld2")的名称。如果需要,这允许使用 case 语句以不同的方式转换不同的字段。

这里有两个使用这个 lambda 的例子。

convert_all_caps.call('Thing A MA-Jig')
  #=> "Thing a MA-Jig"
convert_all_caps.call('FoO AND 1 GOO')
  #=> "FoO And 1 Goo" 

如果我们使用此自定义转换器读取我们的 CSV 文件,我们将获得以下内容。

csv = CSV.read('in.csv', :headers => true, converters: [convert_all_caps])
  #=> #<CSV::Table mode:col_or_row row_count:4> 
csv.to_a
  #=> [["Fld1", "Fld2", "Fld3", "Fld4", "Fld5"],
  #    ["Thing A MA-Jig", "Whatsit", "Object", "FoO And 1 Goo", "Bar"],
  #    ["1", "Null", "Stuff", "test", "Crazy"],
  #    ["Lady", "Terror Of The Night", "nothing", "Left", "nEXT"]]

CSV::read

写一个 CSV 文件

如果我们希望将这些转换后的值写入另一个 CSV 文件,最有效的方法是读取输入文件 line-by-line 并将每一行写入输出文件。我们可以这样做。

headers = CSV.open('in.csv') { |csv| csv.first }
  #=> ["Fld1", "Fld2", "Fld3", "Fld4", "Fld5"]
CSV.open('out.csv', "wb") do |csv_out|
  csv_out << headers
  CSV.foreach('in.csv', :headers => true,
    converters: [convert_all_caps]) { |csv_in| csv_out << csv_in }
end
  #=> 140 

参见 CSV::open and CSV::foreach

让我们确认输出文件是否正确写入。

puts File.read('out.csv')
Fld1,Fld2,Fld3,Fld4,Fld5
Thing A MA-Jig,Whatsit,Object,FoO And 1 Goo,Bar
1,Null,Stuff,test,Crazy
Lady,Terror Of The Night,nothing,Left,nEXT