4

I have a list of inputs in google sheets,

Input Desired Output "To demonstrate only not an input" The repeated letters
Outdoors Match o
dog No Match
step No Match
bee Match e
Chessboard Match s
Cookbooks Match o, k

How do I verify if all letters are unique in a string without splitting it?

In other words if the string has one letter or more occurred twice or more, return TRUE

My process so far

I tried this solution in addition to splitting the string and dividing the length of the string on the COUNTA of unique letters of the string, if = 1 "Match", else "No match"

Or using regex
I found a method to match a letter is occure in a string 2 times this demonstration with REGEXEXTRACT But wait what needed is get TRUE when the letters are not unique in the string

=REGEXEXTRACT(A1,"o{2}?")

Returns:

oo

Something like this would do

=REGEXMATCH(Input,"(anyletter){2}?")

OR like this

=REGEXMATCH(lower(A6),"[a-zA-Z]{2}?")

Notes

  • The third column, "Column C," is only for demonstration and not for input.
  • The match is case insensitive
  • The string doesn't need to be splitted to aviod heavy calculation "I have long lists"
  • Avoid using lambda and its helper functions see why?
  • Its ok to return TRUE or FALSE instead of Match or No Match to keep it simple.

More examples

Input Desired Output
Professionally Match
Attractiveness Match
Uncontrollably Match
disreputably No Match
Recommendation Match
Interrogations Match
Aggressiveness Match
doublethinks No Match
Osm
  • 2,699
  • 2
  • 4
  • 26
  • Why "without splitting"? – TheMaster Oct 17 '22 at 18:41
  • 2
    For this the most straightforward approach would be to use named groups (which allows you to match a pattern (like a char) then give it a name and then match something using it (like the same pattern twice)). This is an example https://regex101.com/r/f6caNA/1 unfortunately it seems Google Sheets does not support this regex feature... sad but maybe an extension could support it. – Daniel Cruz Oct 17 '22 at 18:46
  • Guys I would add a bounty if it solved with a simple regex (☞゚ヮ゚)☞ – Osm Oct 17 '22 at 19:32
  • `=INDEX(IF(A2:A="","",REGEXMATCH(A2:A,"(?i)(?:a.*a|b.*b|c.*c|d.*d|e.*e|f.*f|g.*g|h.*h|i.*i|j.*j|k.*k|l.*l|m.*m|n.*n|o.*o|p.*p|q.*q|r.*r|s.*s|t.*t|u.*u|v.*v|w.*w|x.*x|y.*y|z.*z)")))` .... Unfortunately no backreference possible with RE2. At least we can cut out halve the alternation using the `(?i)` modifier to match case-insensitive. – JvdV Oct 17 '22 at 19:36
  • 3
    I suppose the above can also be written like `=INDEX(IF(A2:A="","",REGEXMATCH(A2:A,"(?i)(?:"&TEXTJOIN("|",1,REPLACE(REPT(CHAR(SEQUENCE(26,1,65)),2),2,0,".*"))&")")))` – JvdV Oct 17 '22 at 19:41
  • @Osm Keep your promise and offer the bounty to JvDv – TheMaster Oct 17 '22 at 20:10
  • @TheMaster, No worries, but I suppose this is what OP is looking for? Ticks the boxes. – JvdV Oct 17 '22 at 20:14
  • Thanks @JvdV , If it done with a single `REGEXMATCH` I will add a second bounty, I was close with `a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z` rept x2 but Im tired need some sleep. – Osm Oct 17 '22 at 20:38
  • 1
    @Osm Isn't JvDv's first formula a single regexmatch? – TheMaster Oct 19 '22 at 04:26
  • And straight to the second bounty: the winner answer is decided if it is able to return the desired output  with a single `REGEXMATCH` and stick to the question requirements better than @JvdV soloution `=INDEX(...,REGEXMATCH(A2:A,"(?i)(?:a.*a|b.*b|...z.*z)")))` otherwise JvdV is rewarded. – Osm Oct 21 '22 at 14:29
  • Define "unique". What would be the output of `bebe`? – TheMaster Oct 21 '22 at 19:55
  • `bebe`'s output is `TRUE` the letters "b" and "e" is occurred twice or more -- in other words if the string is has one letter or more of letters that occurs atleast twice or more, return `TRUE` or `match` @TheMaster – Osm Oct 21 '22 at 20:11
  • To do this with a single short regex, use `.*?(?.).*?\k.*` and convert your data to lowercase before matching. The only Google Sheets built-in function that can use that regex is `query()`. – doubleunary Oct 24 '22 at 14:31
  • After testing the formulas with a 50K row, this is what i [got](https://i.imgur.com/cHs85Yl.png)  -  Remember that the time is measured using the phone's stopwatch, which is "not that accurate", but the difference is noticeable -- see the [example sheet](https://docs.google.com/spreadsheets/d/1ZZSV1mUFMBNPoKAz9CbmhyFlZAT-ox8hFPDRhugsR3A/copy) – Osm Oct 26 '22 at 22:20
  • @Osm Didn't test my recursion? – TheMaster Oct 27 '22 at 06:53
  • With some proposed solutions, test results will vary widely depending on the test corpus. For example, adding a `unique()` in doubleu2 cuts the runtime to 0.628 seconds, making it 8x faster than JvdV2. I do not think the question specifies formula performance as a criteria, and it does not seem to mention the requirement to run as an array formula either. – doubleunary Oct 27 '22 at 11:00
  • 1
    @doubleunary *making it 8x faster than JvdV2* I would expect if some kind of `vlookup+unique()` added to JvDv2's sample as well, it'll still be faster. *I do not think the question specifies formula performance as a criteria,* I would argue "Performance" would fall under the spirit of "to aviod heavy calculation" (and "avoid lambdas") in the question. On the other hand, if we were to go by the law and not the spirit, any answer not using `REGEXMATCH` should be instantly disqualified(including yours) as the banner clearly states "more better ``REGEXMATCH`` answer"(not regex answers) – TheMaster Oct 27 '22 at 11:15
  • @doubleunary Arrayformula is not clearly mentioned, which is my fault, but it is hinted at in this remarks `Avoid using lambda...` and `The string doesn't need to be splitted to aviod heavy calculation "I have long lists` – Osm Oct 27 '22 at 11:52
  • @doubleunary `Unique()` cannot be used since each row of the array is required, and the data is expected to have minimal duplicate words"Row in an array", if this the case with all formulas and using `lookup` we expect to see the same faster calculation improvement. – Osm Oct 27 '22 at 11:52
  • OP Test [Results](https://i.imgur.com/5ERqZ1A.png) update See the [link](https://docs.google.com/spreadsheets/d/1ZZSV1mUFMBNPoKAz9CbmhyFlZAT-ox8hFPDRhugsR3A/edit?usp=sharing) – Osm Oct 27 '22 at 12:31
  • @Osm The unique loop-hole exists only because you have repeated rows, non randomized sample(unlike [my pseudo random sample](https://docs.google.com/spreadsheets/d/1LLV1BvhOEimHt8V-Qa_8sTwf0-2hLPkhvWpspOTLGG0/edit#gid=631945707)). If this is not your real world scenario, you should update your sample to be more random. If you will indeed have multiple repeated rows, like your sample, almost all answers can be modified to accommodate that. – TheMaster Oct 27 '22 at 12:46
  • I agree with TheMaster, the sample data should be realistic. Osm's current sample data is repetitive, but it is unclear whether the real data is like that or not. – doubleunary Oct 27 '22 at 21:07
  • 1
    Final OP [Test results](https://i.imgur.com/lWQD5uJ.png) - or see link to [spreadsheet](https://docs.google.com/spreadsheets/d/1E9IBC7zUUKwB-bQCHGaoALi4nIlHUnBE2y_iEaWjTDs/edit?usp=sharing) named functions included. – Osm Oct 28 '22 at 10:57
  • 1
    Final OP Test [results](https://i.imgur.com/2ylokSi.png) - or see link to [spreadsheet](https://docs.google.com/spreadsheets/d/1E9IBC7zUUKwB-bQCHGaoALi4nIlHUnBE2y_iEaWjTDs/edit?usp=sharing) named functions included. - @TheMaster is copied correctly now with extra test just in case. – Osm Oct 28 '22 at 20:32

7 Answers7

6

You are explicitly asking for an answer using a single regular expression. Unfortunately there is no such thing as a backreference to a former capture group using RE2. So if you'd spell out the answer to your problem it would look like:

=INDEX(IF(A2:A="","",REGEXMATCH(A2:A,"(?i)(?:a.*a|b.*b|c.*c|d.*d|e.*e|f.*f|g.*g|h.*h|i.*i|j.*j|k.*k|l.*l|m.*m|n.*n|o.*o|p.*p|q.*q|r.*r|s.*s|t.*t|u.*u|v.*v|w.*w|x.*x|y.*y|z.*z)")))

Since you are looking for case-insensitive matching (?i) modifier will help to cut down the options to just the 26 letters of the alphabet. I suppose the above can be written a bit neater like:

=INDEX(IF(A2:A="","",REGEXMATCH(A2:A,"(?i)(?:"&TEXTJOIN("|",1,REPLACE(REPT(CHAR(SEQUENCE(26,1,65)),2),2,0,".*"))&")")))

EDIT 1:

The only other reasonable way to do this (untill I learned about the PREG supported syntax of the matches clause in QUERY() by @DoubleUnary) with a single regex other than the above is to create your own UDF in GAS (AFAIK). It's going to be JavaScript based thus supporting a backreferences. GAS is not my forte, but a simple example could be:

function REGEXMATCH_JS(s) {
  if (s.map) {
    return s.map(REGEXMATCH_JS);
  } else {
    return /([a-z]).*?\1/gi.test(s);
  }
}

The pattern ([a-z]).*?\1 means:

  • ([a-z]) - Capture a single character in range a-z;
  • .*?\1 - Look for 0+ (lazy) characters up to a copy of this 1st captured character with a backreference.

The match is global and case-insensitive. You can now call:

=INDEX(IF(A2:A="","",REGEXMATCH_JS(A2:A)))

EDIT 2:

For those that are benchmarking speed, I am not testing this myself but maybe this would speed things up:

=INDEX(REGEXMATCH(A2:INDEX(A:A,COUNTA(A:A)),"(?i)(?:a.*a|b.*b|c.*c|d.*d|e.*e|f.*f|g.*g|h.*h|i.*i|j.*j|k.*k|l.*l|m.*m|n.*n|o.*o|p.*p|q.*q|r.*r|s.*s|t.*t|u.*u|v.*v|w.*w|x.*x|y.*y|z.*z)"))

Or:

=INDEX(REGEXMATCH(A2:INDEX(A:A,COUNTA(A:A)),"(?i)(?:"&TEXTJOIN("|",1,REPLACE(REPT(CHAR(SEQUENCE(26,1,65)),2),2,0,".*"))&")")) 

Or:

=REGEXMATCH_JS(A2:INDEX(A:A,COUNTA(A:A)))

Respectively. Knowing there is a header in 1st row.

JvdV
  • 70,606
  • 8
  • 39
  • 70
  • 1
    This solution `=INDEX(...REGEXMATCH...REPLACE(REPT` returns the result pretty fast, and ticked out each of the question requirements as promised first bounty is yours! – Osm Oct 21 '22 at 14:22
  • @Osm, thanks for the recognition. Also, since you haven't specified wheather or not you'd allow GAS, I did edit my answer to comply with you asking to find a 'better' (or shorter) way doing this with a single regex. Hopefully it helps. – JvdV Oct 21 '22 at 15:13
  • You don't need the brackets around the a-x chain of letter alternations – Bohemian Oct 22 '22 at 08:40
  • We do try to avoid confounding factors when benchmarking. So, the formulas are modified to remove cosmetics. In my [benchmark](https://docs.google.com/spreadsheets/d/1LLV1BvhOEimHt8V-Qa_8sTwf0-2hLPkhvWpspOTLGG0/edit#gid=631945707), the array size is fixed based on A2. Feel free to **duplicate the sheet/Tab** and test, if needed. – TheMaster Oct 23 '22 at 21:07
5

Benchmark:

Created a benchmark here.

Methodology:

  • Use NOW() to create a timestamp, when checkbox is clicked.
  • Use NOW() to create another timestamp, when the last row is filled and the checkbox is on.
  • The difference between those two timestamps gives time taken for the formula to complete.
  • The sample is a random data created from Math.random between [A-Za-z] with 10 characters per word.

Results:

Formula Round1 Round2 Avg % Slower than best
Sample size 10006
[re2](a.*a|b.*b)JvDv 0:00:19 0:00:19 0:00:19 -15.15%
[re2+recursion]MASTERMATCH_RE2 0:00:27 0:00:24 0:00:26 -54.55%
[Find+recursion]MASTERMATCH 0:00:17 0:00:16 0:00:17 0.00%
[PREG]Doubleunary 0:00:57 0:00:53 0:00:55 -233.33%

Conclusion:

This varies greatly based on browser/device/mobile app and on non-randomized sample data. But I found PREG to be consistently slower than


Use .

This seems extremely faster than the regex based approach. Create a named function:

Name:

MASTERMATCH

Arguments(in this order):

word

The word to check

start

Starting at

Function:

=IF(
  MID(word,start,1)="",
  FALSE,
  IF(
    ISERROR(FIND(MID(word,start,1),word,start+1)),
    MASTERMATCH(word,start+1),
    TRUE
  )
)

Usage:

=ARRAYFORMULA(MASTERMATCH(A2:INDEX(A2:A,COUNTA(A2:A)),1))

Or without case sensitivity

=ARRAYFORMULA(MASTERMATCH(lower(A2:A),1)) 

Explanation:

It recurses through each character using MID and checks whether the same character is available after this position using FIND. If so, returns true and doesn't check anymore. If not, keeps checking until the last character using recursion.


Or with , Create a named function:

Name:

MASTERMATCH_RE2

Arguments(in this order):

word

The word to check

start

Starting at

Function:

IF(
  MID(word,start,1)="",
  FALSE,
  IF(
    REGEXMATCH(word,MID(word, start, 1)&"(?i).*"&MID(word,start,1)),
    TRUE,
    MASTERMATCH_RE2(word,start+1)
  )
)

Usage:

=ARRAYFORMULA(MASTERMATCH_RE2(A2:A,1))

Or

=ARRAYFORMULA(MASTERMATCH_RE2(lower(A2:A),1)) 

Explanation:

It recurses through each character and creates a regex for that character. Instead of a.*a, b.*b,..., it takes the first character(using MID), eg: o in outdoor and creates a regex o.*o. If regex is positive for that regex (using REGEXMATCH), returns true and doesn't check for other letters or create other regexes.


Uses , but it's efficient. Loop through each row and every character with MAP and REDUCE. REPLACE each character in the word and find the difference in length. If more than 1, don't check length anymore and return Match

=MAP(
  A2:INDEX(A2:A,COUNTA(A2:A)),
  LAMBDA(_,
    REDUCE(
      "No Match",
      SEQUENCE(LEN(_)),
      LAMBDA(a,c,
        IF(a="Match",a,
          IF(
            LEN(_)-LEN(
              REGEXREPLACE(_,"(?i)"&MID(_,c,1),)
            )>1,
            "Match",a
          )
        )
      )
    )
  )
)

If you do run into lambda limitations, remove the MAP and drag fill the REDUCE formula.

=REDUCE("No Match",SEQUENCE(LEN(A2)),LAMBDA(a,c,IF(a="Match",a,IF(LEN(A2)-LEN(REGEXREPLACE(A2, "(?i)"&MID(A2,c,1),))>1,"Match",a))))

The latter is preferred for conditional formatting as well.

TheMaster
  • 45,448
  • 6
  • 62
  • 85
  • Handel this [Error](https://i.imgur.com/IX5VLDc.png): Function SEQUENCE parameter 1 value is 0. It should be greater than or equal to 1. -------- Im using Conditional formatting with a two lists of words with unique letter and non unique letters, if you concerned. – Osm Oct 17 '22 at 19:23
  • 1
    I modified the data to be numbers for readability instead of letters and added `&""` for the `REGEX` to treat it as text --- about see if it works i think no chance with just [100,000 rows](https://i.imgur.com/GEjQNrm.png) --- aside of this hitting spacebar in win10 start by mistake nice to know its [international sloth day](https://i.imgur.com/dgoD9E9.png) :D @Themaster – Osm Oct 20 '22 at 19:26
  • The text shown is from trying a small range, and the tab is is unuseable, on the opposit JvdV formula work faster , try `=ArrayFormula(REDUCE("No Match",SEQUENCE(LEN(F1:F&"")),LAMBDA(a,c,IF(a="Match",a,IF(LEN(F1:F&"")-LEN(REGEXREPLACE(F1:F&"", "(?i)"&MID(F1:F&"",c,1),))>1,"Match",a)))))` --- where F1:F is `=SEQUENCE(100000)` to keep it simple. for me just calculating for a long time without displaying the result. – Osm Oct 20 '22 at 19:42
  • @Osm Checkout my recursion. It's faster than any regex(including JvDv) - at least reduces the time by 50%. – TheMaster Oct 22 '22 at 04:36
  • This implementation of named function is great; an explanation is required. – Osm Oct 27 '22 at 12:01
  • @Osm I already have a separate "Explanation" sub section under each formula. If you don't understand anything, ask a specific question in the comments, I can edit to clarify. – TheMaster Oct 27 '22 at 12:08
  • Describe how the `MASTERMATCH` is padded with various functions and used inside. sure we can get a general use of this way elsewhere. – Osm Oct 27 '22 at 13:25
5

As Daniel Cruz said, Google Sheets functions such as regexmatch(), regexextract() and regexreplace() use RE2 regexes that do not support backreferences. However, the query() function uses Perl Compatible Regular Expressions that do support named capture groups and backreferences:

=arrayformula( 
  iferror( not( iserror( 
    match( 
      to_text(A3:A), 
      query(lower(unique(A3:A)), "where Col1 matches '.*?(?<char>.).*?\k<char>.*' ", 0), 
      0 
    ) 
  ) / (A3:A <> "") ) ) 
)

In my limited testing with a sample size of 1000 heterograms, pangrams, words with diacritic letters, and 10-character pseudo-random unique values from TheMaster's corpus, this PREG formula ran at about half the speed of the JvdV2 RE2 regex.

With Osm's sample of 50,000 highly repetitive sample values, the formula ran at 8x the speed of JvdV2.

A PREG regex is slower than a RE2 regex, but has the benefit that you can more easily check all characters for repeats. This lets you work with corpuses that include diacritic letters, numbers and other non-English alphabet characters:

Input Output
Professionally TRUE
disreputably FALSE
Abacus TRUE
Élysée TRUE
naïve Ï TRUE
määräävä TRUE
121 TRUE
123 FALSE

You can also easily state which specific characters to check by replacing <char>. with something like <char>[\wéäåö] or <char>[^-;,.\s\d].

doubleunary
  • 13,842
  • 3
  • 18
  • 51
  • This was extremely slower than @JvDv formula. – TheMaster Oct 22 '22 at 04:20
  • @TheMaster that does not match my test results. See [Heterogram benchmark](https://docs.google.com/spreadsheets/d/1LLV1BvhOEimHt8V-Qa_8sTwf0-2hLPkhvWpspOTLGG0/edit#gid=908279550). – doubleunary Oct 22 '22 at 08:26
  • Thanks, Prima facie, I don't what's different except the size and testing method(My sample was 100000, which showed human noticeable differences, ~30s for JvDv, and yours didn't finish at 2+ minutes). I'll check again (will test your sample as well). – TheMaster Oct 22 '22 at 08:58
  • I added my benchmark in your sheet. Duplicate my tab to make modifications and check. I posted my results [here](https://stackoverflow.com/a/74102047) – TheMaster Oct 23 '22 at 15:27
  • 1
    Thanks. There was an error in my timer function. In reality, the PREG regex runs at about half the speed of JvdV's RE2 with a sample size of 1000. Edited the answer. Did some testing on your tab as well. With a sample size of 1000, JvdV1 is a bit faster than MASTERMATCH(). With a sample size of 10004, MASTERMATCH() clearly beats JvdV1. I suspect that the largish size of the sheet and the use of `indirect()` may affect test results at smaller sample sizes even when not all data rows are referenced. Added test results in your tab (sorry! Didn't notice your request to duplicate in time.) – doubleunary Oct 23 '22 at 17:55
4

try:

=INDEX(IF(IFERROR(LEN(REGEXREPLACE(A1:A6, "[^"&C1:C6&"]", )), -1)>=
 (LEN(SUBSTITUTE(C1:C6, "|", ))*2), "Match", "No Match"))

enter image description here


update

create a query heat map, filter it and vlookup back row position

=INDEX(LAMBDA(a, IF(""<>IFNA(VLOOKUP(ROW(a), 
 SPLIT(QUERY(QUERY(FLATTEN(ROW(a)&"​"&REGEXEXTRACT(a, REPT("(.)", LEN(a)))), 
 "select Col1,count(Col1) where Col1 matches '.*\w+$' group by Col1"), 
 "select Col1 where Col2 > 1", ), "​"), 2, )), "Match", "No Match"))
 (A2:INDEX(A:A, MAX((A:A<>"")*ROW(A:A)))))

enter image description here

case insensitive would be:

=INDEX(LAMBDA(a, IF(""<>IFNA(VLOOKUP(ROW(a), 
 SPLIT(QUERY(QUERY(FLATTEN(ROW(a)&"​"&LOWER(REGEXEXTRACT(a, REPT("(.)", LEN(a))))), 
 "select Col1,count(Col1) where Col1 matches '.*\w+$' group by Col1"), 
 "select Col1 where Col2 > 1", ), "​"), 2, )), "Match", "No Match"))
 (A2:INDEX(A:A, MAX((A:A<>"")*ROW(A:A)))))

enter image description here

player0
  • 124,011
  • 12
  • 67
  • 124
  • 1
    Close to 4782969 rows splited. With `LEN` between {2,7} ,I did't reach that number yet @Player0 – Osm Oct 20 '22 at 16:27
  • Tested with a 53,100 ROWS Taking too long to return [nothing](https://i.imgur.com/bouOfEQ.png), but surprisingly [after a while](https://i.imgur.com/2Jb08ch.png) it dose, how! Im not sure about that. @Player0 – Osm Oct 21 '22 at 14:12
  • There is a [mismach of the rows size](https://i.imgur.com/ZwfF7ZC.png) – Osm Oct 21 '22 at 14:45
  • @Osm you can fix the mismatch if you change `A2:` to `A1:` – player0 Oct 21 '22 at 14:53
  • @Osm also, there are ways how to reach 4782969 rows :) – player0 Oct 22 '22 at 02:46
2

Just to illustrate another method - not likely to be scaleable - try to substitute the second occurrence of the letter:

=ArrayFormula(if(isnumber(xmatch(len(A2)-1,len(substitute(upper(A2),char(sequence(1,26,65)),"",2)))),"Match","No match"))

enter image description here


If splitting were permitted, I would favour use of Frequency for speed, e.g.

=ArrayFormula(max(frequency(code(mid(upper(A2),sequence(len(A2)),1)),sequence(1,26,65)))>1)
Tom Sharpe
  • 30,727
  • 5
  • 24
  • 37
  • Hi @Tom-Sharpe thanks for the response, what needed is: return FALSE when the strings is composed of unique letters and TRUE when the is duplicate letters. not only 2 letters it could be more. to note but it [works as expected](https://i.imgur.com/QgDRFE5.png) – Osm Oct 20 '22 at 22:04
0

You can give a try by using this RegEx : /(\w).*?\1/g in the REGEXMATCH function in google sheets.

Explanation :

(\w) - matches word characters (a-z, A-Z, 0-9, _), If you are sure that input will contain only alphabets then you can also use ([a-zA-Z]); then

.*? - zero or more characters (the ? denotes as optional that means it can match for consecutive as well as non-consecutive); until

\1 - it finds a repeat of the first matched character.

Live Demo : regex101

Debug Diva
  • 26,058
  • 13
  • 70
  • 123
  • 3
    Hi @Rohìt-Jíndal thank you for your response , see [Error](https://i.imgur.com/Lof1rVV.png) Function REGEXMATCH parameter 2 value "/(\w).*?\1/g" is not a valid regular expression. – Osm Oct 20 '22 at 15:52
0

Coming after the battle ^^ Why not simply compare the number of unique letters in the string and its original length ?

=COUNTUNIQUE(split(regexreplace(A2;"(.)"; "$1_"); "_")) < LEN(A2)

All my tests seem fine.

(split() provided by this answer)

Philippe
  • 1,134
  • 12
  • 22
  • 1 - Its need to be an arrayformula, 2 - no splitting is not allowed only if calculation time is low, 3 - this is what i attempted: quoting from the question `I tried this solution in addition to splitting the string and dividing the length of the string on the COUNTA of unique letters of the string...` – Osm Oct 28 '22 at 14:27
  • @Osm, thanks for this comment. You're right, I only hope my answer could help somebody else in a near or far future, who wonders how to easily get a match when there are duplicate letters in a string ;) – Philippe Oct 28 '22 at 22:38