Dan Tappin
Dan Tappin

Reputation: 3032

Rails / Ruby method / regex to increment numeric part of string?

Related to an old PHP question here:

How to increment numeric part of a string by one?

I can have any random format of strings (invoice numbers) like this:

I would like to take these as an input and output:

The last numeric portion is incremented by one. In the case where there is no match it returns "" OR just adds a "-1" to the end. I can handle the logic on my end if its easier to just return "".

On the ruby / rails side it would seem to be like this:

"foo".gsub(/(o+)/, '\1\1\1')
#=> "foooooo"

I just can't seem to figure out the regex logic.

Upvotes: 1

Views: 97

Answers (1)

Cary Swoveland
Cary Swoveland

Reputation: 110675

You are looking for the method String#succ.

def increment_numeric(str)
  str.gsub(/\d+/) { |s| s.succ }
end
increment_numeric "INV-005"  #=> "INV-006"
increment_numeric "INV-5"    #=> "INV-6"
increment_numeric "5567"     #=> "5568"
increment_numeric "5567-1A"  #=> "5568-2A"
increment_numeric "567-001A" #=> "568-002A"
increment_numeric "INVOICE"  #=> "INVOICE"

The operative line can be written more succinctly as follows:

str.gsub(/\d+/, &:succ)

In view of the asker's comment below, the following might be more appropriate.

R = /\d+\z|(?<=\d)\p{L}\z/
def increment_numeric(str)
  str.sub(R) { |s| s.succ }
end
increment_numeric "INV-005"   #=> "INV-006"
increment_numeric "INV-5"     #=> "INV-6"
increment_numeric "5567"      #=> "5568"
increment_numeric "5567-01"   #=> "5567-02"
increment_numeric "5567-1A"   #=> "5567-1B"
increment_numeric "567-001A"  #=> "567-001B"
increment_numeric "A567-001B" #=> "A567-001C"
increment_numeric "INVOICE-1" #=> "INVOICE-2"
increment_numeric "INVOICE"   #=> "INVOICE"

The regular expression reads can be written in free spacing mode to make it self-documenting:

R =
/
\d+\z    # match one or more digits at the end of the string
|        # or
(?<=\d)  # assert that the following match is preceded by a digit
\p{L}\z  # match one Unicode letter at the end of the string
/x       # invoke free-spacing regex definition mode

(?<=\d) is a positive lookbehind.

There is at most one substitution so either gsub or sub can be used.

Upvotes: 4

Related Questions