Return 来自 ruby 的替换数组

Return array of replacements from ruby

我想将字符串 foofoofoo、映射 foobar 和 return 所有单独的替换作为一个数组 - ['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')

这有效(至少对于这个测试用例),但似乎太冗长和老套。我可以做得更好吗,也许可以使用 string#gsub 块或类似的东西?

I want to take the string foofoofoo, map foo to bar, and return all individual replacements as an array - ['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"]

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

我不认为 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

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 宽度,所以我们将重新捕获 lookahead 的内容,并计算新的宽度那。

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

编辑:稍微调整一下,我们甚至可以支持正确的 gsub-like 行为:

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"]

(免责声明:最后一个片段无法处理模式使用后视或先行检查匹配区域之外的情况。)