【问题标题】:Return array of replacements from ruby从 ruby​​ 返回替换数组
【发布时间】:2017-01-24 00:09:06
【问题描述】:

我想获取字符串 foofoofoo,将 foo 映射到 bar,并将所有单独的替换作为数组返回 - ['barfoofoo', 'foobarfoo', 'foofoobar']

这是我最好的:

require 'pp'
def replace(string, pattern, replacement)
  results = []
  string.length.times do |idx|
    match_index = (Regexp.new(pattern) =~ string[idx..-1])
    next unless match_index
    match_index = idx + match_index
    prefix = ''
    if match_index > 0
      prefix = string[0..match_index - 1]
    end

    suffix = ''
    if match_index < string.length - pattern.length - 1
      suffix = string[match_index + pattern.length..-1]
    end

    results << prefix + replacement + suffix
  end
  results.uniq
end

pp replace("foofoofoo", 'foo', 'bar')

这可行(至少对于这个测试用例),但看起来太冗长和hacky。我可以做得更好吗,也许通过使用带有块或类似的string#gsub

【问题讨论】:

  • 我不认为 Ruby 提供了开箱即用的功能。
  • @JaredBeck 只是为了澄清一下,输入字符串作为示例给出 - 真正的问题是支持任意字符串,并使用提供的替换替换任何索引处的模式匹配。因此,例如,replace('foofof', 'f|o|fo', 'x') 应该产生 ['xoofof', 'xofof', 'fxofof', 'fxxfof', 'foxfof', &lt;etc.&gt;]
  • 请根据您最后的评论更新您的问题。此外,'f|o|fo' 是一个字符串,而不是一个模式。如果你想要一个模式,你应该使用/f|o|fo/

标签: ruby string-substitution


【解决方案1】:

pre_match$`)和post_match$')很容易做到:

    def replace_matches(str, re, repl)
      return enum_for(:replace_matches, str, re, repl) unless block_given?
      str.scan(re) do
        yield "#$`#{repl}#$'"
      end
    end

    str = "foofoofoo"

    # block usage
    replace_matches(str, /foo/, "bar") { |x| puts x }

    # enum usage
    puts replace_matches(str, /foo/, "bar").to_a

编辑:如果你有重叠的匹配,那么它会变得更难,因为正则表达式并没有真正具备处理它的能力。所以你可以这样做:

def replace_matches(str, re, repl)
  return enum_for(:replace_matches, str, re, repl) unless block_given?
  re = /(?=(?<pattern>#{re}))/
  str.scan(re) do
    pattern_start = $~.begin(0)
    pattern_end = pattern_start + $~[:pattern].length
    yield str[0 ... pattern_start] + repl + str[pattern_end .. -1]
  end
end

str = "oooo"
replace_matches(str, /oo/, "x") { |x| puts x }

这里我们滥用了正向预测,它是 0 宽度,所以我们可以得到重叠匹配。但是,我们还需要知道我们匹配了多少个字符,因为现在匹配是 0 宽度,我们不能像以前那样做,所以我们将重新捕获前瞻的内容,并计算新的宽度那个。

(免责声明:每个字符仍然只匹配一次;如果您想考虑每个字符的多种可能性,例如在您的 /f|o|fo/ 情况下,它会使事情变得更加复杂。)

编辑:稍作调整,我们甚至可以支持适当的类似 gsub 的行为:

def replace_matches(str, re, repl)
  return enum_for(:replace_matches, str, re, repl) unless block_given?
  new_re = /(?=(?<pattern>#{re}))/
  str.scan(new_re) do
    pattern_start = $~.begin(0)
    pattern_end = pattern_start + $~[:pattern].length
    new_repl = str[pattern_start ... pattern_end].gsub(re, repl)
    yield str[0 ... pattern_start] + new_repl + str[pattern_end .. -1]
  end
end

str = "abcd"
replace_matches(str, /(?<first>\w)(?<second>\w)/, '\k<second>\k<first>').to_a
# => ["bacd", "acbd", "abdc"]

(免责声明:最后一个 sn-p 无法处理模式使用后向或前向检查匹配区域之外的情况。)

【讨论】:

    【解决方案2】:

    我不认为 Ruby 提供了这种开箱即用的功能。但是,这是我的两分钱,可能更优雅:

    def replace(str, pattern, replacement)
      count = str.scan(pattern).count
      fragments = str.split(pattern, -1)
    
      count.times.map do |occurrence|
        fragments[0..occurrence].join(pattern)
          .concat(replacement)
          .concat(fragments[(occurrence+1)..count].to_a.join(pattern))
      end
    end
    

    【讨论】:

    • 这是解决上述问题的一种非常好的方法。请参阅我的问题下的评论 - 我实际上需要支持可能从任何索引开始发生的匹配,因此,例如,replace('oooo', 'oo', 'x') 应该返回 ['xoo', 'oxo', 'oox']
    【解决方案3】:

    我想获取字符串 foofoofoo,将 foo 映射到 bar,并将所有单独的替换作为数组返回 - ['barfoofoo', 'foobarfoo', 'foofoobar']

    如果我们假设输入总是恰好是“foofoofoo”(三个“foo”),那么问题就很简单了,所以我们假设有一个或多个“foo”。

    def possibilities(input)
      n = input.length / 3
      n.times.map { |i| 
        (['bar'] + Array.new(n - 1, 'foo')).rotate(-i).join 
      }
    end
    
    possibilities "foo"
    # ["bar"]
    possibilities "foofoo"
    # ["barfoo", "foobar"]
    possibilities "foofoofoo"
    # ["barfoofoo", "foobarfoo", "foofoobar"]
    

    有一些解决方案会使用更少的内存,但这个似乎很方便。

    【讨论】:

      猜你喜欢
      • 2014-10-03
      • 2016-11-18
      • 2014-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-28
      • 1970-01-01
      • 2013-12-25
      相关资源
      最近更新 更多