如何在Ruby中完成这种复杂的搜索和替换操作?

时间:2014-10-04 16:22:51

标签: ruby

我有一个大文本文件。在这个文本文件中,我想用'菠菜'代替'pizza'一词,用'Spinach'代替'Pizza',用'旋转'代替'pizzing' - 除非这些词发生在花括号内的任何地方。因此{pizza},{giant.pizza}{hot-pizza-oven}应保持不变。

到目前为止,我提出的最佳解决方案是逐行遍历文件,发出一个正则表达式,在{或之后}之前检测所有内容,并在每个字符串上使用正则表达式。但这变得非常复杂和笨拙,我想知道这个问题是否有适当的解决方案。

4 个答案:

答案 0 :(得分:2)

这可以通过几个步骤完成。我会逐行遍历文件,并将每一行传递给此方法:

def spinachize line
  # list of words to swap
  swaps = {
    'pizza' => 'spinach',
    'Pizza' => 'Spinach',
    'pizzing' => 'spinning'
  }

  # random placeholder for bracketed text
  placeholder = 'fdjfafdlskdsfajkldfas'

  # save all instances of bracketed text
  bracketed_text = line.scan(/\{.*?\}/)

  # remove bracketed text from line
  line.gsub!(/\{.*?\}/, placeholder)

  # replace all swaps
  swaps.each do |original_text, new_text|
    line.gsub!(original_text, new_text)
  end

  # re-insert bracketed text
  line.gsub(placeholder){bracketed_text.shift}
end

上面的评论解释了我们的目标。以下是几个例子:

spinachize "Pizza is good, but more pizza is better"
 => "Spinach is good, but more spinach is better"

spinachize "Leave bracketed instances of {pizza} or {this.pizza} alone"
 => "Leave bracketed instances of {pizza} or {this.pizza} alone"

如您所见,您可以指定要交换的项目,或修改方法以从某个位置的数据库或平面文件中提取列表。占位符只需要是一些独特的东西,不会自然地出现在源文件中。

过程是这样的:从原始行中删除括号内的文本,并记住它以供日后使用。交换所有需要交换的文本,然后添加回括号内的文本。它不是单行,但它运行良好,易读且易于更新。

该方法的最后一行可能需要一些澄清。没有多少人知道“gsub”方法可以采用块而不是第二个参数。然后该块确定放置原始文本的内容。在这种情况下,每次调用块时,我都会删除保存的括号列表中的第一个项目,然后使用它。

答案 1 :(得分:1)

rules = {'pizza' => 'spinach','Pizza' => 'Spinach','pizzing' => 'spinning'}
regexp = /\{[^{}]*\}|#{rules.keys.join('|')}/m
puts(file.read.gsub(regexp) { |s| rules[s] || s })

这构造了一个正则表达式,它匹配括号中的字符串或要替换的字符串。然后我们通过一个块来运行它,该块用给定的值替换字符串,并保持括号内的字符串不变。使用/m标志,正则表达式可以容忍括号内的换行符 - 如果不会发生,则可以将其取出。无论哪种方式,都不需要逐行迭代。

答案 2 :(得分:0)

str = "Pizza {pizza} with spinach is not pizzing."
swaps = {'{pizza}'  =>'{pizza}',
         '{Pizza}'  =>'{Pizza}',
         '{pizzing}'=> '{pizzing}'
         'pizza'    => 'spinach',
         'Pizza'    => 'Spinach',
         'pizzing'  => 'spinning'}
regex = Regexp.union(swaps.keys)
p str.gsub(regex, swaps) # => "Spinach {pizza} with spinach is not spinning."

答案 3 :(得分:0)

我会为文件的每一行调用以下方法。

<强>代码

def doit(line)
  replace = {'pizza'=>'spinach', 'Pizza'=>'Spinach', 'pizzing'=>'spinning'}
  r = /\{.*?\}/
  arr= line.split(r).map { |str|
    str.gsub(/\b(?:pizza|Pizza|pizzing)\b/, replace) }
  line.scan(r).each_with_object(arr.shift) { |str,res|
    res << str << arr.shift }
end

<强>实施例

doit("Pizza Primastrada's {pizza} is the best {pizzing} pizza in town.")
  #=> "Spinach Primastrada's {pizza} is the best {pizzing} spinach in town."
doit("{Pizza Primastrada}'s pizza is the best pizzing {pizza} in town.")
  #=> "{Pizza Primastrada}'s spinach is the best spinning {pizza} in town." 

<强>解释

line = "Pizza Primastrada's {pizza} is the best {pizzing} pizza in town."
replace = {'pizza'=>'spinach', 'Pizza'=>'Spinach', 'pizzing'=>'spinning'}
r = /\{.*?\}/
a = line.split(r)
  #=> ["Pizza Primastrada's ", " is the best ", " pizza in town."]
b = a.map { |str| str.gsub(/\b(?:pizza|Pizza|pizzing)\b/, replace) }
  #=> ["Spinach Primastrada's ", " is the best ", " spinach in town."]
keepers = line.scan(r)
  #=> ["{pizza}", "{pizzing}"]
keepers.each_with_object(b.shift) { |str,res| res << str << b.shift }
  #=> "Spinach Primastrada's {pizza} is the best {pizzing} spinach in town."

嵌套大括号

如果您希望允许嵌套大括号,请将正则表达式更改为:

r = /\{[^{}]*?(?:\{.*?\})*?[^{}]*?\}/
doit("Pizza Primastrada's {{great {great} pizza} is the best pizza.")
  #=> "Spinach Primastrada's {{great {great} pizza} is the best spinach."

你提到了字符串

{words,salad,#{1,2,3} pizza|}

在评论中。如果这是用单引号括起来的字符串的一部分,那不是问题。但是,如果用双引号括起来,#将引发语法错误。如果英镑字符被转义(\#),也没问题。