Regex that match exactly 3 identical consecutive numbers
Asked Answered
G

2

6

Good morning all,

I want to make a regex that match 3 same consecutive numbers. It should match only 3 numbers in a row (separated by a space), the numbers should be identical. If there are less or more than 3 same numbers, then the output should be false

I have tried this regex /.*(\d+) \1 \1(?!(\s\1))/

console.log(/.*(\d+) \1 \1(?!(\s\1))/.test('I am 42 42 4 hey yoo')); //false --> Correct
 
console.log(/.*(\d+) \1 \1(?!(\s\1))/.test('I am 42 42 42 hey yoo')); //true --> Correct

console.log(/.*(\d+) \1 \1(?!(\s\1))/.test('I am 42 42 42 4 hey yoo')); //true --> Correct

console.log(/.*(\d+) \1 \1(?!(\s\1))/.test('I am 42 42 42 42 hey yoo')); //true --> this output should be false since there are 4 same consecutive digits

Any advice, please?

Gunilla answered 23/7, 2023 at 1:56 Comment(5)
Does this answer your question? Regex to check for 4 consecutive numbersPiggy
@Mark, it is not immediately clear (and even quite a bit challenging) to modify linked answer for reality of this question, as we not talking about digits, but rather numbers.Langlauf
What about foo 42 42 42 bar 42 42 42 42? Should that match or not?Gnarl
To disallow @InSync's sample here a regex using just a lookahead to fail on four or more: ^(?!.*?(?: |^)(\d+)(?: \1){3}(?!\S)).*?(?: |^)(\d+)(?: \2){2}(?!\S)Amimia
@Thefourthbird's and my answer make different assumptions. For example, he matches the string, "I am^42 42 42 hey yoo" whereas I do not (as I require the first "42" to be preceded by a space or be at the beginning of the string). Neither set of assumptions is correct; it's whatever you want.Nicaragua
N
3

I have assumed that the three identical strings of digits are separated by one space, that the first of this group of three is at the beginning of the string or is preceded by a space that is not preceded by the same string and the last string of this group of three is at the end of the string or is followed by a space that is not followed by the same string.

You may attempt to match the following regular expression.

(?: |^)(\d+)(?<!(?: |^)\1 \1)(?: \1){2}(?![^ ]| \1(?: |$))

Demo

The regular expression can be broken down as follows. (Alternatively, hover the cursor over each part of the expression at the link to obtain an explanation of its function.)

(?: |^)     # match a space or the beginning of the string
(\d+)       # match one or more digits and save to capture group 1
(?<!        # begin a negative lookbehind
  (?: |^)    # match a space or the beginning of the string
  \1 \1      # match the content of capture group 1 twice, separated by a space
)           # end the negative lookbehind
(?: \1)     # match a space followed by the content of capture group 1
{2}         # execute the preceding non-capture group twice
(?!         # begin a negative lookahead
  [^ ]        # match a character other than a space
  |           # or
   \1         # match a space followed by the content of capture group 1
  (?: |$)     # match a space or the end of the string
)           # end the negative lookahead

Note that (?: .... ) denotes a non-capture group.

Nicaragua answered 23/7, 2023 at 5:57 Comment(4)
Your regex can be greatly simplified by replacing (?: |^) and (?: |$) with \b.Gnarl
@InSync, that would cause ”*1 1 1” to be matched (for example), which is inconsistent with my stated assumption concerning requirements.Nicaragua
@CarySwoveland Hi Cary, thanks a lot man. Your regex works successfully. Btw, if you have time, could you please show which part of my regex is wrong /.*(\d+) \1 \1(?!(\s\1))/. I don't understand why my regex doesn't workGunilla
Rol Co, I see a couple of problems. Firstly, if the string were, "12 12 12 12" you regex would match because the capture group would match the second "12". You need something to prevent the capture group from being preceded by the same. Secondly, the string, "12 12 12 123" should be matched, but is not matched by your regex. Depending on assumptions, /.*(\d+) \1 \1(?!\s\1\b)/ might be enough to deal with the second problem. Incidentally, I see no reason for \s\1 to be in a capture group.Nicaragua
S
2

In your pattern you are mixing spaces and \s which matches a whitespace character, and can also match a newline.

You can check if the first capture group value (\d+) is not preceded by the same value.

A version with word boundaries to prevent partial matches:

\b(\d+)\b(?<!\b\1 \1)(?: \1){2}\b(?! \1\b)

Regex demo

The pattern matches:

  • \b(\d+)\b Capture group 1, match 1+ digits between word boundaries
  • (?<!\b\1 \1) Negative lookbehind, assert not the same number before this number
  • (?: \1){2}\b Repeat 2 times a space and the group 1 value ending on a word boundary
  • (?! \1\b) Negative lookahead, assert not the group 1 value to the right followed by a word boundary

const regex = /\b(\d+)\b(?<!\b\1 \1)(?: \1){2}\b(?! \1\b)/;

console.log(regex.test('I am 42 42 4 hey yoo')); //false --> Correct
 
console.log(regex.test('I am 42 42 42 hey yoo')); //true --> Correct

console.log(regex.test('I am 42 42 42 4 hey yoo')); //true --> Correct

console.log(regex.test('I am 42 42 42 42 hey yoo')); //true --> this output should be false since there are 4 same consecutive digits

Or if a lookbehind is not supported, you could match 3 times the same number and optionally the rest of the repeating numbers in group 2.

In the result, you can check if there is either not a match, or a capture group 2 value:

\b(\d+)(?: \1){2}( \1)*\b

Regex demo

const regex = /\b(\d+)(?: \1){2}( \1)*\b/;
[
  'I am 42 42 4 hey yoo',
  'I am 42 42 42 hey yoo',
  'I am 42 42 42 4 hey yoo',
  'I am 42 42 42 42 hey yoo'
].forEach(s => {
  const m = s.match(regex);
  console.log(`${s} --> ${!(!m || m[2])}`);
})
Sophiasophie answered 23/7, 2023 at 9:10 Comment(11)
That, or /\b(\d+)(?: \1){2}\b( \1\b)?/ and !(m && !m[2]) since both branches look similar enough to be merged into one.Gnarl
@Gnarl That pattern will have a different match regex101.com/r/BjPjcD/1 Also using a single exclamation mark !(m && !m[2]) will give the opposite result.Sophiasophie
That m[2] presents means that there are more than 3, not? However, it's true that I did mess up the logic; the check should have been !m?.[2].Gnarl
@Gnarl No, m[2] Means that there is a group 2 value, so there are 3 of the same numbers.Sophiasophie
I'm referring to my m[2], which matches the fourth instance, if any: ( \1\b)?.Gnarl
@Gnarl I see what you mean, but I think that it will give a false positive in a string like this regex101.com/r/EQjh93/1 vs regex101.com/r/aTnH8m/1Sophiasophie
((?: \1\b)+)? then.Gnarl
@Gnarl In that case you could write it as \b(\d+)(?: \1){2}( \1)*\b and check for the group 2 valueSophiasophie
@Gnarl Shall I update the answer or do you want to make a post out of it?Sophiasophie
I see mine as an extension to yours, so update your answer, please.Gnarl
Hi @Thefourthbird, thanks a lot. Your regex \b(\d+)\b(?<!\b\1 \1)(?: \1){2}\b(?! \1\b) works successfully. Really appreciate your solution, manGunilla

© 2022 - 2024 — McMap. All rights reserved.