I wanted to mention a possible course of action when faced with a situation, as here, where a variable-length positive lookbehind is needed but the regex engine being used does not support that operation, but does support variable-length positive lookaheads, as does PCRE (PHP), for example.
I confess to mainly having written this answer to improve my own understanding of how regex engines operate.
Basic idea
- Reverse the string
- Use a regex with a corresponding positive lookahead to perform replacements of matches
- Reverse the resultant string
Example
Suppose we wish to convert the following strings:
a, bb, c, d
ee, f, g, h
i, j, kk, l
to the strings:
abb, ac, ad
eef, eeg, eeh
ij, ikk, il
We first reverse the original strings:
d ,c ,bb ,a
h ,g ,f ,ee
l ,kk ,j ,i
then match the regex:
(\w+)(?=.*,(\w+)$)|\s+,\w+$
and replace each match with $1+$2, to obtain:
da ,ca ,bba
hee ,gee ,fee
li ,kki ,ji
Lastly, reverse those strings:
abb, ac, ad
eef, eeg, eeh
ij, ikk, il
PCRE demo
The regex performs the following operations:
(\w+) # match 1+ word chars in cap grp 1
(?= # begin a positive lookahead
.*, # match 0+ chars (greedily), then ','
(\w+) # match 1+ word chars in cap grp 1
$
)
|
\s+,\w+
$
I will represent the spaces in the string "a, bb, c, d" with little smilies (☺), to make them more distinct, and display the string thusly:
a , ☺ b b , ☺ c , ☺ d
^
The spaces now represent the areas between adjacent characters. ^ is the initial location of the regex engine's pointer.
After (\w+) matches "a" at the beginning of the string (indicated by m below), "a" is saved to capture group 1. The positive lookahead begins right after that match:
a , ☺ b b , ☺ c , ☺ d
m^
The positive lookahead, (?=.*,(\w+)$) saves "d" to capture group 2.1 As the match was successful, the first match, "a" is replaced with $1+$2 #=> "ad" and the pointer moves back to its position before the lookahead was executed:
a , ☺ b b , ☺ c , ☺ d
^
There is now an attempt to match (\w+) with the part of the string that begins with the first comma. That fails, as does the or part of the regex, \s+,\w+$. The pointer is then advanced one character:
a , ☺ b b , ☺ c , ☺ d
^
That also fails, and the pointer is again advanced by one.
a , ☺ b b , ☺ c , ☺ d
^
(\w+) now matches "bb", which is saved to capture group 1, at which point:
a , ☺ b b , ☺ c , ☺ d
m m^
As before, the positive lookahead saves "d" to capture group 2 and the match, "bb" is replaced with $1+$2 #=> "bbd"
After two more matching failures we are at:
a , ☺ b b , ☺ c , ☺ d
^
For the same reasons as before, "c" is matched and replaced with $1+$2 => "cd" and we are now here:
a , ☺ b b , ☺ c , ☺ d
^
There are no more word strings that are followed by word strings to be matched, but the end of the string, ", d", now matches the or part of the regex, \s+,\w+$. That match is then replaced with $1+$2. This time, however, the two capture groups are empty, so the match is replaced with an empty string.
1 The comma is needed. Without it, .*, being greedy, would gobble up everything before the last word character. If the string ended ", cd", for example, capture group 2 would contain only "d".