简体   繁体   English

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

[英]How to replace the last occurrence of a substring in ruby?

I want to replace the last occurrence of a substring in Ruby.我想替换 Ruby 中最后一次出现的子字符串。 What's the easiest way?最简单的方法是什么? For example, in abc123abc123 , I want to replace the last abc to ABC .例如,在abc123abc123 中,我想将最后一个abc替换为ABC How do I do that?我怎么做?

How about怎么样

new_str = old_str.reverse.sub(pattern.reverse, replacement.reverse).reverse

For instance:例如:

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"

But probably there is a better way...但可能有更好的方法......

Edit:编辑:

...which Chris kindly provided in the comment below. ...克里斯在下面的评论中亲切地提供了这一点。

So, as * is a greedy operator, the following is enough:因此,由于*是一个贪婪的运算符,因此以下内容就足够了:

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

Edit2:编辑2:

There is also a solution which neatly illustrates parallel array assignment in Ruby:还有一个解决方案可以巧妙地说明 Ruby 中的并行数组分配:

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

Since Ruby 2.0 we can use \\K which removes any text matched before it from the returned match.从 Ruby 2.0 开始,我们可以使用\\K从返回的匹配中删除在它之前匹配的任何文本。 Combine with a greedy operator and you get this:结合贪婪的运算符,你会得到这个:

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

This is about 1.4 times faster than using capturing groups as Hirurg103 suggested, but that speed comes at the cost of lowering readability by using a lesser-known pattern.这比 Hirurg103 建议的使用捕获组快大约 1.4 倍,但这种速度是以使用鲜为人知的模式降低可读性为代价的。

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

When searching in huge streams of data, using reverse will definitively* lead to performance issues.在大量数据流中搜索时,使用reverse肯定会* 导致性能问题。 I use string.rpartition *:我使用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"

The same code must work with a string with no occurrences of sub_or_pattern :相同的代码必须处理没有出现sub_or_pattern的字符串:

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

* rpartition uses rb_str_subseq() internally. * rpartition内部使用rb_str_subseq() I didn't check if that function returns a copy of the string, but I think it preserves the chunk of memory used by that part of the string.我没有检查该函数是否返回字符串的副本,但我认为它保留了该字符串部分使用的内存块。 reverse uses rb_enc_cr_str_copy_for_substr() , which suggests that copies are done all the time -- although maybe in the future a smarter String class may be implemented (having a flag reversed set to true, and having all of its functions operating backwards when that is set), as of now, it is inefficient. reverse使用rb_enc_cr_str_copy_for_substr() ,这表明副本一直rb_enc_cr_str_copy_for_substr() ——尽管将来可能会实现一个更智能的String类(将一个标志reversed设置为 true,并且在设置时使其所有功能向后运行),截至目前,它是低效的。

Moreover, Regex patterns can't be simply reversed.此外, Regex模式不能简单地反转。 The question only asks for replacing the last occurrence of a sub-string, so, that's OK, but readers in the need of something more robust won't benefit from the most voted answer (as of this writing)这个问题只要求替换最后一次出现的子字符串,所以,没关系,但是需要更强大的东西的读者不会从投票最多的答案中受益(在撰写本文时)

Here's another possible solution:这是另一种可能的解决方案:

>> s = "abc123abc123"
=> "abc123abc123"

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

>> s
=> "abc123ABC123"

您可以使用String#sub和 greedy regexp .*来实现这一点,如下所示:

'abc123abc123'.sub(/(.*)abc/, '\1ABC')

simple and efficient:简单高效:

s = "abc123abc123abc"
p = "123"
s.slice!(s.rindex(p), p.size)
s == "abc123abcabc"
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

I've used this handy helper method quite a bit:我经常使用这个方便的辅助方法:

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

If you want to make it more Rails-y, extend it on the String class itself:如果你想让它更加 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

Then you can just call it directly on any String instance, eg "fooBAR123BAR".gsub_last("BAR", "FOO") == "fooBAR123FOO"然后你可以直接在任何 String 实例上调用它,例如"fooBAR123BAR".gsub_last("BAR", "FOO") == "fooBAR123FOO"

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM