Regex (Perl / Java / PCRE2 v10.34+), 30 29 27 bytes
^(?!.*:(x(\2?+:.*\b)?){7}:)
Takes its input in unary, as a concatenation of strings of x
characters whose lengths represent the numbers, separated and enclosed on both sides by :
characters. Example: :x:xxxxxx:
represents [1, 6]
.
Try it online! - Perl
Try it online! - Java
Attempt This Online! - PCRE2 v10.40+
This is an adaptation of the 31 30 28 byte version below, but it prevents more than one split from occurring in a somewhat more complex manner. Upon matching a split, \2
captures the intervening elements, including commas – or just a single comma if there are no intervening elements.
How this prevents more than one split would be simple to explain if (?(2)\2)
were used instead of \2?+
. Then the mechanism would be that on subsequent attempts to match a splitting after one has already occurred, that would be forced to coincide with matching a duplicate element, or two commas in a row.
But \2?+
will successfully make an empty match if \2
is not an exact match at that position. However, note that if this did happen, it could only result in false negatives (returning "False" incorrectly) – never false positives – because it could make the pattern inside the negative lookahead match when it shouldn't, but it can't prevent a match that should have happened.
All lists of 4 or more numbers will return "False", so the addition of false negatives won't matter. And lists of 2 or fewer elements can only match one split anyway (where \2
captures a single comma alone), so no false negatives could result there.
So we only need to consider lists of 3 elements:
- Only two splits can occur at most, and if that is the case,
\2
will contain exactly one comma. The first split prevents a second split from being matched because \2?+,
would only match two commas in a row.
- Or if a split matches one intervening element, only one split can occur anyway, so there's no place for a second split to match.
And here is a test harness with all 156 lists of 3 or fewer elements, demonstrating this regex to work correctly:
Try it online! - Perl
Regex (Perl / Java / Pythonregex
/ Ruby / PCRE), 31 30 28 bytes
^(?!.*:(x\2?+(:).*\b|x){7}:)
Try it online! - Perl
Try it online! - Java
Try it online! / Attempt This Online! - Python (with regex
)
Try it online! - Ruby
Try it online! - PCRE
This is an adaptation of the 33 32 31 29 byte version below, but it prevents more than one split from occurring with \2?+(,)
, which forces a split to coincide with matching two commas in a row (which can never happen in the input specification) if a split has already occurred.
Regex (Perl / Boost / Python / Pythonregex
/ Ruby / PCRE / .NET), 33 32 31 29 bytes
^(?!.*:(?(1)x|x(:.*\b)?){7}:)
Try it online! - Perl
Try it online! - Boost
Try it online! / Attempt This Online! - Python
Try it online! / Attempt This Online! - Python (with regex
)
Try it online! - Ruby
Try it online! - PCRE
Try it online! - .NET
This works by asserting that no two numbers add up to exactly 7. It does this by consuming exactly 7 x
characters – a split is allowed to occur at any point within this, but not more than once.
^ # Anchor to start
(?! # Negative lookahead - assert that the following can't match:
.*: # Skip to after any occurrence of a bounding character,
# which denotes the beginning of a number's unary
# representation.
(?(1) # Conditional upon whether \1 is set:
# If \1 is set:
x # tail -= 1
|
# If \1 is unset:
# (This is only allowed to match once within this loop. There is no
# need to verify afterward that it matched exactly once, because the
# input specification allows us to assume there will be no numbers
# greater than 6 – so, 7 will never appear.)
x # tail -= 1
(
:.*\b # Complete the match for the current number, and
# skip to the beginning of any subsequent number's
# unary representation.
)? # Match the above optionally
){7} # Iterate the above exactly 7 times; this can never result in
# the 7th iteration ending in a split, as that would require
# matching 7 x's in a row, but the input is guaranteed not to
# contain a 7.
: # Assert that tail == 0 (i.e, that this is the end of a
# number's unary representation)
)
Strangely, this actually works with Python's re
library, even though (?(2)
...)
is a forward-declared conditional, and re
doesn't support forward-declared or nested backreferences; \2
in that position would throw an error.
Equally strangely, it works in Boost, even though that regex engine doesn't support nested or forward-declared backreferences either, and a \2
in that position would cause Boost to throw an error as well.
The previous 33 byte version did not work properly with Python's re
; it used a nested conditional, and Python silently treated that as a no-op, making the regex return some false negatives. But strangely, that version works in Pythonregex
, even though that engine doesn't support nested backreferences either.
Regex (Perl / Java / Pythonregex
/ Ruby / PCRE / .NET), 34 33 32 30 bytes
^(?!.*:(x(?!\2):.*\b()|x){7}:)
Try it online! - Perl
Try it online! - Java
Try it online! / Attempt This Online! - Python (with regex
)
Try it online! - Ruby
Try it online! - PCRE
Try it online! - .NET
This adds Java support to the above 33 32 31 29 byte version, by removing the use of the conditional (?(2)
...)
. Support for Python and Boost is dropped, due to the use of the nested backreference \2
.
Regex (ECMAScript 2018 / Pythonregex
/ .NET), 41 36 34 bytes
^(?!(.*:)(\b(?<=^\1x+).*:x|x){7}:)
Try it online! - ECMAScript 2018
Try it online! / Attempt This Online! - Python (with regex
)
Try it online! - .NET
^ # Anchor to start
(?! # Negative lookahead - assert that the following can't match:
(.*:) # Skip to after any occurrence of a bounding character,
# which denotes the beginning of a number's unary
# representation;
# \1 = the entire portion of the string that has been
# skipped, going all the way back to its beginning.
(
\b # Assert that we're on a word boundary
# The below is only allowed to match once within this loop. There is
# no need to verify afterward that it matched exactly once, because
# the input specification allows us to assume there will be no numbers
# greater than 6 (so, 7 will never appear).
(?<= # Lookbehind - evaluated from right to left (so in this
# listing, read it from bottom to top)
^ # Assert this is the start of the string
\1 # Match \1
x+ # Skip back to the beginning of the unary representation
# of the current number
)
.*: # Complete the match for the current number, and skip
# to the beginning of any subsequent number's unary
# representation.
x # tail -= 1
| # or...
x # tail -= 1 (and do nothing else)
){7} # Iterate the above exactly 7 times; this can never result in
# the 7th iteration ending in a split, as that would require
# matching 7 x's in a row, but the input is guaranteed not to
# contain a 7.
: # Assert that tail == 0 (i.e, that this is the end of a
# number's unary representation)
)
A previous 41 byte version using lookbehind appears to expose a bug in Pythonregex
:
^(?!(.*\b)x+(,.*\b)x+\b(?<=^\1(\2?x){7}))
Try it online! - ECMAScript 2018 - works
Attempt This Online! - Python (with regex
) - does not work
Try it online! - .NET - works
Upon investigation, the bug actually has nothing to do with lookbehind. I reported it here and it is now fixed.
Regex (ECMAScript or better), 73 69 46 41 40 39 37 bytes
^(?!.*:(?=x*(.*))(x(?=\1).+\b|x){7}:)
Try it online! - ECMAScript (SpiderMonkey)
Try it online! - ECMAScript 2018 (Node.js)
Try it online! - Perl
Try it online! - Java
Try it online! - Boost
Try it online! - Python
Try it online! / Attempt This Online! - Python (with regex
)
Try it online! - Ruby
Try it online! - PCRE
Try it online! - .NET
This now ports the 33 32 31 byte version's algorithm to support ECMAScript:
^ # Anchor to start
(?! # Negative lookahead - assert that the following can't match:
.*: # Skip to after any occurrence of a bounding character,
# which denotes the beginning of a number's unary
# representation.
(?=x*(.*)) # \1 = the entire string following the current number's
# unary representation
(
x # tail -= 1
# The below is only allowed to match once within this loop. There is
# no need to verify afterward that it matched exactly once, because
# the input specification allows us to assume there will be no numbers
# greater than 6 (so, 7 will never appear).
(?=\1) # Assert that the entire string following the current
# position matches what was captured in \1, which
# accomplishes two things – it asserts that we
# haven't skipped to another number yet, and that
# we've finished consuming the entire current number,
# i.e. that tail == 0.
.+\b # Skip to the beginning of any subsequent number's
# unary representation (the first character skipped
# will be a comma).
| # or...
x # tail -= 1 (and do nothing else)
){7} # Iterate the above exactly 7 times; this can never result in
# the 7th iteration ending in a split, as that would require
# matching 7 x's in a row, but the input is guaranteed not to
# contain a 7.
\b # Assert that tail == 0 (i.e, that this is the end of a
# number's unary representation)
)
The previous 73 69 byte version worked by treating every possible pair of numbers that add to 7 as a separate case, like the decimal version below:
^(?!(?=.*\bx\b).*x{6}|(?=.*\bxx\b).*\bx{5}\b|(?=.*\bxxx\b).*\bx{4}\b)
Regex (.NET), 46 bytes
^(?!.*\b(){7}(?<-1>x)+,.*\b(?<-1>x)+(?(1)^)\b)
Obsoleted by the 33 32 byte version above.
Regex (Perl / Boost / Python / Ruby / PCRE / .NET), 58 bytes
^(?!.*\b(x)?(xx)?(xxxx,.*\b|,.*\bxxxx)(?(2)|xx)(?(1)|x)\b)
Obsoleted by the versions above. Worked by capturing two numbers in binary, and asserting that the second number has all 3 of its lowermost bits flipped when compared to the first number (and asserting that this doesn't occur anywhere in the input).
Regex (ECMAScript or better), 38 37 bytes
^(?!(?=.*1).*6|(?=.*2).*5|(?=.*3).*4)
Takes its input in decimal. There is no need for delimiters, because the full range fits into a single digit.
Try it online! - ECMAScript / Perl / Java / Python / .NET
Pure regex is very limited in what math can be done on decimal input, so this version just asserts that no two numbers occur anywhere in the list that add up to 7, by exhaustively listing all the pairs that could do so.