Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ruby regex - how replace nth instance of a match in a string

Tags:

regex

ruby

In my app I need to be able to find all number substrings, then scan each one, find the first one that matches a range (such as between 5 and 15) and replace THAT instance with another string "X".

My test string s = "1 foo 100 bar 10 gee 1"

My initial pattern is any string of 1 or more digits, eg, re = Regexp.new(/\d+/)

matches = s.scan(re) gives ["1", "100", "10", "1"]

If I want to replace the Nth match, and only the Nth match, with "X" how do I?

For example if I want to replace the third match "10" (matches[2]) I can't just say s[matches[2]] = "X" because that does two replacements

"1 foo X0 bar X gee 1"

Any help would be appreciated!

like image 935
jpw Avatar asked Sep 21 '12 04:09

jpw


2 Answers

String#gsub has a form that takes a block. It yields to the block for each match, and replaces the match with the result of the block. So:

first = true
"1 foo 100 bar 10 gee 1 12".gsub(/\d+/) do |digits|
  number = digits.to_i
  if number >= 5 && number <= 15 && first
    # do the replacement
    first = false
    'X'
  else
    # don't replace; i.e. replace with itself
    digits
  end
end
# => "1 foo 100 bar X gee 1 12" 
like image 144
willglynn Avatar answered Nov 08 '22 06:11

willglynn


An alternate way is to construct number range using character class (if it is not too complicated)

>> s = "1 foo 100 bar 10 gee 1"
=> "1 foo 100 bar 10 gee 1"
>> s.sub(/(?<!\d)([5-9]|1[0-5])(?!\d)/, 'X')
=> "1 foo 100 bar X gee 1"
  • the negative lookarounds ensure that part of digit sequence are not matched
    • you can use \b instead of lookarounds if the numbers cannot be part of words like abc12ef or 8foo
  • ([5-9]|1[0-5]) will match numbers from 5 to 15


Initially, the title lead me to think that you want to replace Nth occurrence - for ex: N=2 means replace second occurrence of any digit sequence. For that you can use this:

# here the number inside {} will be N-1
>> s.sub(/(\d+.*?){1}\K\d+/, 'X')
=> "1 foo X bar 10 gee 1"
like image 24
Sundeep Avatar answered Nov 08 '22 06:11

Sundeep