如何替换ruby中最后一次出现的子串?

我想替换ruby中最后一个子字符串。 什么是最简单的方式? 例如,在abc123abc123中 ,我想将最后一个abc替换为ABC 。 我怎么能这样做?

在大量数据流中搜索时,使用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类(将标志reversed设置为true,并且当设置它时所有函数都向后运行) ),截至目前,效率低下。

而且,正则Regex模式不能简单地逆转。 这个问题只是要求替换子串的最后一次出现,所以,这没关系,但是需要更强大的东西的读者不会从最投票的答案中受益(截至本文撰写时)

怎么样

 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" 
 "abc123abc123".gsub(/(.*(abc.*)*)(abc)(.*)/, '\1ABC\4') #=> "abc123ABC123" 

但可能还有更好的方法……

编辑:

……克里斯在下面的评论中提供了。

所以,因为*是一个贪婪的运算符,以下就足够了:

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

EDIT2:

还有一个解决方案巧妙地说明了Ruby中的并行数组赋值:

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

这是另一种可能的解决方案:

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

你可以使用gsub和greedy regexp实现这一点.*像这样:

 'abc123abc123'.gsub(/(.*)abc/, '\1ABC') 
 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 

简单高效:

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

我已经使用了这个方便的助手方法:

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

如果你想让它更多Rails-y,请在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 

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

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

匹配一个“abc”然后断言((?=)是正向预测),直到字符串末尾的其他字符都不是“abc”。