Подтвердить что ты не робот

Как заменить последнее вхождение подстроки в ruby?

Я хочу заменить последнее вхождение подстроки в ruby. Какой самый восточный путь? Например, в abc123abc123 я хочу заменить последний abc на ABC. Как я могу это сделать?

4b9b3361

Ответ 1

При поиске в огромных потоках данных использование 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 нельзя просто вернуть. Вопрос только просит заменить последнее вхождение подстроки, так что это нормально, но читатели, нуждающиеся в чем-то более надежном, не получат выгоду от самого голосованного ответа (на момент написания этой статьи).

Ответ 2

Как насчет

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"

Ответ 3

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

Но, вероятно, есть лучший способ...

Edit:

... который Крис любезно предоставил в комментарии ниже.

Итак, поскольку * является жадным оператором, достаточно:

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

Edit2:

Существует также решение, которое аккуратно иллюстрирует назначение параллельного массива в Ruby:

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

Ответ 4

Здесь другое возможное решение:

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

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

>> s
=> "abc123ABC123"

Ответ 5

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

Ответ 6

простой и эффективный:

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

Ответ 7

Я использовал этот удобный вспомогательный метод совсем немного:

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"

Ответ 8

Вы можете добиться этого с помощью gsub и greedy regexp .* следующим образом:

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