3

我继承了 Jekyll 网站,而且我来自 .NET 世界,所以这对我来说是一个学习曲线。

这个 Jekyll 网站需要很长时间才能构建,我认为这是因为实际上有成千上万的类别标签需要删除这些页面。我能够获取所有类别的列表并创建一个 CSV,我想循环并确定是否仍然需要类别标签。CSV的结构是:

old_tag,new_tag

显然,我想根据这些更新标签(例如,将所有 C#、C-Sharp、C# 和 C Sharp 类别都设为 C-Sharp)。但是,我还想删除一些旧标签字段存在而新标签字段为空白的地方:

old_tag,new_tag
C#, C-Sharp
C Sharp, C-Sharp
Crazy, 
C #, C-Sharp

使用 Ruby 或 Python,我想弄清楚如何遍历 4000 多个降价文件并使用 CSV 有条件地更新每个文件。我的数据库人员无法想象这将如何处理平面文件。

4

2 回答 2

2

我建议从哈希开始,像翻译表一样使用它。哈希查找非常快,并且可以很好地组织您的标签及其替换。

hash = {
  # old_tag => new_tag
  'C#'      => 'C-Sharp',
  'C Sharp' => 'C-Sharp',
  'Crazy'   => '',
  'C #'     => 'C-Sharp',
}

您可以看到值中有很多冗余,可以通过反转哈希来修复,这可以很好地减少它:

hash = {
  # new_tag => old_tag
  'C-Sharp' => ['C#', 'C Sharp', 'C #'],
}

'Crazy'是一个异常值,但我们会处理它。

RubyString.gsub有一个很好但很少使用的特性,我们可以向它传递一个正则表达式和一个散列,它会将所有正则表达式匹配替换为散列中的等效值。我们可以轻松构建该正则表达式:

regex = /(?:#{ Regexp.union(hash.keys).source })/
=> /(?:C\-Sharp)/

现在,您可能会说,“但是等等,我还有很多标签要查找!”,并且由于哈希的构建方式,它们隐藏在值中。为了解决这个问题,我们将反转散列的键和值,将值数组分解为它们各自的元素:

reversed_hash = Hash[hash.flat_map{ |k,v| v.map{ |i| [i,k] } }]
=> {
         "C#" => "C-Sharp",
    "C Sharp" => "C-Sharp",
        "C #" => "C-Sharp",
}

通过合并“特殊情况”的第二个散列,添加'Crazy'很容易:

special_cases = {
  'Crazy' => ''
}

reversed_hash = Hash[hash.flat_map{ |k,v| v.map{ |i| [i,k] } }].merge(special_cases)
=> {
         "C#" => "C-Sharp",
    "C Sharp" => "C-Sharp",
        "C #" => "C-Sharp",
      "Crazy" => ""
}

将其与正则表达式构建代码一起使用:

regex = /(?:#{ Regexp.union(reversed_hash.keys).source })/
=> /(?:C\#|C\ Sharp|C\ \#|Crazy)/

这将使用自动生成的正则表达式找到标签。如果需要不区分大小写,请使用:

regex = /(?:#{ Regexp.union(reversed_hash.keys).source })/i

创建一些文本进行测试:

text =<<EOT
This is "#C#"
This is "C Sharp"
This is "C #"
This is "Crazy"
EOT
=> "This is \"#C#\"\nThis is \"C Sharp\"\nThis is \"C #\"\nThis is \"Crazy\"\n"

并测试gsub

puts text.gsub(regex, reversed_hash)

哪个输出:

This is "#C-Sharp"
This is "#C-Sharp"
This is "#C-Sharp"
This is "#"

现在,我不喜欢将大文件放入内存中,因为这不能很好地扩展。今天的机器通常有许多 GB 的内存,但我看到文件仍然超过机器中的 RAM。因此,File.readgsub建议使用File.foreach. 使用它会更改代码。

这是我的做法:

file_to_read = '/path/to/file/to/read'
File.open(file_to_read + '.new', 'w') do |fo|
  File.foreach(file_to_read) do |li|
    fo.puts li.gsub(regex, reversed_hash)
  end
end

File.rename(file_to_read, file_to_read + '.bak')
File.rename(file_to_read + '.new', file_to_read)

这将为每个处理的文件创建一个 .bak 版本,因此如果出现问题,您有一个备用方案,这始终是一个好习惯。


编辑:我忘记了 CSV 文件:

您可以使用 CSV 模块使用 Ruby 轻松读取/创建一个,但是我会使用 YAML 文件,因为它允许您在易于手动编辑或从文件生成的文件中轻松创建哈希布局。


编辑:更多关于 CSV、YAML 和从另一个生成一个

以下是如何读取 CSV 并将其转换为推荐的哈希格式:

require 'csv'

text = <<EOT
C#, C-Sharp
C Sharp, C-Sharp
Crazy,
C #, C-Sharp
EOT

hash = Hash.new{ |h,k| h[k] = [] }
special_cases = []
CSV.parse(text) do |k,v|
  (
    (v.nil? || v.strip.empty?) ? special_cases : hash[v.strip]
  ) << k.strip
end

从之前捡起:

reversed_hash = Hash[hash.flat_map{ |k,v| v.map{ |i| [i,k] } }].merge(Hash[special_cases.map { |k| [k, ''] }])
puts reversed_hash
# => {"C#"=>"C-Sharp", "C Sharp"=>"C-Sharp", "C #"=>"C-Sharp", "Crazy"=>""}

要将 CSV 文件转换为更可编辑和更有用的文件,请使用上面的代码创建hashand special_cases,然后:

require 'yaml'

puts ({
  'hash' => hash,
 'special_cases' => special_cases
}).to_yaml

看起来像:

---
hash:
  C-Sharp:
  - C#
  - C Sharp
  - ! 'C #'
special_cases:
- Crazy

其余的你可以从 YAML 文档中找到。

于 2012-12-20T00:05:52.823 回答
0

这是一种可能的方法;不确定它对大量数据的效果如何:

require "stringio"
require "csv"

class MarkdownTidy
  def initialize(rules)
    @csv = CSV.new(rules.is_a?(IO) ? rules : StringIO.new(rules))
    @from_to = {}.tap do |hsh|
      @csv.each do |from, to|
        re = Regexp.new(Regexp.escape(from.strip))
        hsh[re] = to.strip
      end
    end
  end

  def tidy(str)
    cpy = str.dup
    @from_to.each do |re, canonical|
      cpy.gsub! re, canonical
    end
    cpy
  end
end

csv = <<-TEXT
C#, C-Sharp
C Sharp, C-Sharp
Crazy, 
C #, C-Sharp
TEXT

markdown = <<-TEXT
C# some text C # some text Crazy
C#, C Sharp
TEXT

mt = MarkdownTidy.new(csv)
[markdown].each do |str|
  puts mt.tidy(markdown)
end

这个想法是,您将在最后用一个打开文件、读取它们然后将它们保存回磁盘的循环替换循环。

于 2012-12-20T03:59:04.957 回答