0

Using Rails 5.0.1 with Ruby 2.4. How do I find the index in a string of where the nth occurrence of a regex ends? If my regex were

/\-/

and my string where

str = "a -b -c"

and I were looking for the last index of the second occurrence of my regex, I would expect the answer to be 5. I tried this

str.scan(StringHelper::MULTI_WHITE_SPACE_REGEX)[n].offset(1)

but was greeted with the error

NoMethodError: undefined method `offset' for "             ":String

In the above, n is an integer that represents the nth occurrence of the regex I wish to scan for.

Dave
  • 15,639
  • 133
  • 442
  • 830
  • I assume the `offset` you're looking for is from [MatchData](https://ruby-doc.org/core-2.4.0/MatchData.html#method-i-offset), in which case check out ["How do I get the match data for all occurrences of a Ruby regular expression in a string?"](https://stackoverflow.com/questions/6804557/how-do-i-get-the-match-data-for-all-occurrences-of-a-ruby-regular-expression-in). Once you have the `MatchData` for all occurrences you can index into the array and get it for a specific occurrence. (Though in the given regex you'd want `offset(0)` since there aren't any additional capture groups in the regex) – Simple Lime Jul 10 '17 at 21:17
  • Thanks. I assume you're talking about manipulating the answer in your link -- "my_string.to_enum(:scan, my_regex).map { Regexp.last_match }", but I'm not clear how I would change that to accommodate the nth match that I found. – Dave Jul 10 '17 at 21:21
  • `map` returns an array so it would just be `my_string.to_enum(:scan, my_regex).map { Regexp.last_match }[n - 1].offset(0)` should get you what you want if I understand everything correctly (n - 1 for the nth offset because of 0-based indices). so `str.to_enum(:scan, /\-/).map { Regexp.last_match }[1].offset(0)` => `[5, 6]` – Simple Lime Jul 10 '17 at 21:24
  • @SimpleLime, "my_string.to_enum(:scan, my_regex).map { Regexp.last_match }[n - 1].offset(0)" seems to be working. If you want to put that as an answer, I'll accept. – Dave Jul 10 '17 at 22:28
  • Sure thing, didn't put it as an answer initially because I wasn't sure if this was close enough to be a 'duplicate' of that question or not, but having refined that other answer a little for your case probably needs to be in an answer not the comments – Simple Lime Jul 10 '17 at 23:01

3 Answers3

0

One way of doing this:

def index_of_char str, char, n
  res = str.chars.zip(0..str.size).select { |a,b| a == char }
  res[n]&.last
end

index_of_char "a -b -c", '-', 0
#=> 2

index_of_char "a -b -c", '-', 1
#=> 5

index_of_char "a -b -c", '-', 2
#=> nil

index_of_char "abc", '-', 1
#=> nil

Further optimisations can be made.

Sagar Pandya
  • 9,323
  • 2
  • 24
  • 35
0

Sorry about the speedy read earlier. Maybe this method can help you locate the index of the nth occorunce of an element. Although I could not find a way to do this with strictly regex in ruby. Hope this helps.

def index_of_nth_occorunce(string, element, nth_occurunce)
  count = 0
  string.split("").each_with_index do |elm, index| 
    count += 1 if elm == element
    return index if count == nth_occurunce
  end
end

index_of_nth_occorunce("a -b -c", "-", 2) #5

After doing some further digging I may have found the answer you are looking for in this stack post (ruby regex: match and get position(s) of). Hope this also helps.

nth_occurence = 2 
s = "a -b -c"
positions = s.enum_for(:scan, /-/).map { Regexp.last_match.begin(0) }
p positions[nth_occurence - 1] # 5
kparekh01
  • 221
  • 1
  • 7
0

From my comments that grew from a link to a related question:

The answer to that question

"abc12def34ghijklmno567pqrs".to_enum(:scan, /\d+/).map { Regexp.last_match }

Can easily be adapted to get the MatchData for a single item

string.to_enum(:scan, regex).map { Regexp.last_match }[n - 1].offset(0)

to find the nth match in a string.

Simple Lime
  • 10,790
  • 2
  • 17
  • 32