如何在Ruby中替换最后一个子字符串?

38

我想在Ruby中替换最后一次出现的子字符串。最简单的方法是什么? 例如,在abc123abc123中,我想将最后一个abc替换为ABC。应该如何实现?

10个回答

40

怎么样?

new_str = old_str.reverse.sub(pattern.reverse, replacement.reverse).reverse
例如:
irb(main):001:0> old_str = "abc123abc123"
=> "abc123abc123"
irb(main):002:0> pattern="abc"
=> "abc"
irb(main):003:0> replacement="ABC"
=> "ABC"
irb(main):004:0> new_str = old_str.reverse.sub(pattern.reverse, replacement.reverse).reverse
=> "abc123ABC123"

清晰易懂的答案。 - Iulian Onofrei

22
"abc123abc123".gsub(/(.*(abc.*)*)(abc)(.*)/, '\1ABC\4')
#=> "abc123ABC123"

但可能有更好的方法...

编辑:

...Chris在下面的评论中友情提供了。

因此,由于*是贪婪运算符,所以下面的内容足够了:

"abc123abc123".gsub(/(.*)(abc)(.*)/, '\1ABC\3')
#=> "abc123ABC123"

编辑2:

还有一个解决方案,它很好地演示了Ruby中的并行数组赋值:

*a, b = "abc123abc123".split('abc', -1)
a.join('abc')+'ABC'+b
#=> "abc123ABC123"

15
由于贪婪匹配,只需使用str.sub(/(.*)abc/, '\1ABC')即可。 - Chris Johnsen
非常感谢。我也认为这个问题可以通过正则表达式解决,但不知道如何操作。你做到了。再次感谢! - Just a learner
在 @ChrisJohnsen 的回答基础上进行改进:class String def sub_last(str_match, str_sub) self.sub(/(.*)#{Regexp.quote(str_match)}/, '\1' + str_sub) end end - coconup

19

自从 Ruby 2.0 版本以后,我们可以使用 \K 命令,该命令会将其前面匹配到的任何文本从返回的匹配结果中移除。结合贪婪操作符使用,您可以这样做:

'abc123abc123'.sub(/.*\Kabc/, 'ABC')
#=> "abc123ABC123"

这比使用Hirurg103建议的捕获组要快大约1.4倍,但是以使用较不常见的模式为代价降低了可读性。

关于\K的更多信息:https://www.regular-expressions.info/keep.html


6
你可以使用 String#sub 和贪婪正则表达式 .* 来实现此目的:
'abc123abc123'.sub(/(.*)abc/, '\1ABC')

1
为什么要使用gsub而不是sub?你只会替换一个出现的实例(这就是重点)。 - Henrik supports the community
1
@Henriksupportsthecommunity 你是对的 - 已经修复了。谢谢! - Hirurg103

5
这里有另一个可能的解决方案:
>> s = "abc123abc123"
=> "abc123abc123"

>> s[s.rindex('abc')...(s.rindex('abc') + 'abc'.length)] = "ABC"
=> "ABC"

>> s
=> "abc123ABC123"

5

当在大量数据流中搜索时,使用reverse一定会导致性能问题。我使用string.rpartition

sub_or_pattern = "!"
replacement = "?"
string = "hello!hello!hello"

array_of_pieces = string.rpartition sub_or_pattern
( array_of_pieces[(array_of_pieces.find_index sub_or_pattern)] =  replacement ) rescue nil
p array_of_pieces.join
# "hello!hello?hello"

同一段代码必须适用于不包含sub_or_pattern的字符串:

string = "hello_hello_hello"
# ...
# "hello_hello_hello"

*rpartition内部使用rb_str_subseq()。我没有检查该函数是否返回字符串的副本,但我认为它会保留字符串的这部分所使用的内存块。reverse使用rb_enc_cr_str_copy_for_substr(),这表明复制操作一直在进行——尽管也许将来可能会实现更智能的String类(设置为true的标志reversed,并且当设置为真时,所有函数都会反向运行),但目前它是低效的。

此外,Regex模式不能简单地反转。问题只要求替换子字符串的最后一个出现位置,因此这没问题,但需要更健壮的读者将无法从最受欢迎的答案中受益(截至本文撰写之时)。


2

简单而高效:

s = "abc123abc123abc"
p = "123"
s.slice!(s.rindex(p), p.size)
s == "abc123abcabc"

4
这并没有回答这个问题。 - aross
1
解决了我的问题!就像grosser所说的那样,简单而高效。 - Fábio Araújo

1
我已经使用了这个方便的辅助方法很多次:

def gsub_last(str, source, target)
  return str unless str.include?(source)
  top, middle, bottom = str.rpartition(source)
  "#{top}#{target}#{bottom}"
end

如果你想让它更符合Rails的风格,可以在String类本身上进行扩展:
class String
  def gsub_last(source, target)
    return self unless self.include?(source)
    top, middle, bottom = self.rpartition(source)
    "#{top}#{target}#{bottom}"
  end
end

然后您可以在任何字符串实例上直接调用它,例如 "fooBAR123BAR".gsub_last("BAR", "FOO") == "fooBAR123FOO"

1
string = "abc123abc123"
pattern = /abc/
replacement = "ABC"

matches = string.scan(pattern).length
index = 0
string.gsub(pattern) do |match|
  index += 1
  index == matches ? replacement : match
end
#=> abc123ABC123

0
.gsub /abc(?=[^abc]*$)/, 'ABC'

匹配 "abc",然后断言 ((?=) 是正向先行断言),直到字符串结尾没有其他字符是 "abc"。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接