JavaScript, 50 bytes
^(?!([^])+.*\1.*\1)[?!ZZ-__==''-22+,,//{50}$]{50}$
Try it on regex101
The key to beating jimmy23013's solution was to reduce the number of repetitions of each character from 3 to 2.
Explanation
^(?!([^])+.*\1.*\1)
- assert that no character in the string is repeated at least 3 times
([^])+
is equivalent to .*([^])
.
[^]
works because JavaScript allows empty character classes (unlike most other regex engines, at least by default). It is equivalent to [\W\w]
or .
with the /s
flag, i.e. it matches any character including newlines. For our purposes here, newlines don't matter, so it's equivalent to .
.
Using ^(?!([^])+.*\1.*\1)
instead of (?!.*([^])(.*\1){2})
or ^(?!(.)+.*\1.*\1)
eliminates the need for a third (
, )
, or .
, and this is the key to reducing the number of repetitions of every character from 3 to 2.
^
...[?!ZZ-__==''-22+,,//{50}$]{50}$
- assert that the entire string consists of the 25 characters ?
!
Z
[
\
]
^
_
=
'
(
)
*
+
,
-
.
/
0
1
2
{
5
}
$
and is 50 characters in length. Combined with the first assertion this guarantees that every character occurs exactly twice (due to the generalized pigeonhole principle, if any character occurred only once, some other character would have to occur three times).
Z-_
is the range Z[\]^_
. It's essential, because we already used [
, \
, ]
, and ^
twice each. We need to double the Z
and _
, because they aren't used elsewhere.
'-2
is the range '()*+,-./012
. It's essential, because we already used (
, )
, *
, .
, and 1
twice each. We need to double the '
, ,
, /
, and 2
, because they aren't used elsewhere.
There is no other way besides ranges to include the above in our character class, because octal or hexadecimal escape codes would require using \
, and a negative character class would require using ^
.
==
is extra padding to get us to 50 characters. It could be any arbitrary doubled character. We need it because if we went for {48}
, we'd need to include two 0
somewhere, which would bring our 48 back up to 50.
Note that this can be trivially rearranged to include the delimiters:
/^(?!([^])+.*\1.*\1)[?!ZZ-__==''-22+,,{50}$]{50}$/
Try it on regex101
Perl / PCRE, 57 bytes
I'm pretty sure it's impossible to do 2 repetitions in Perl/PCRE, because [^]
is not available (except with the PCRE2_ALLOW_EMPTY_CLASS flag), and the only other alternatives use \
– for example \C
, \N
, \X
, \S
, and [^\0]
. So, with 3 repetitions:
^(?!(.)*(.*\1){3})[[[-\]\]^^??!!'''-*.11{335577}$$-]{57}$
Try it on regex101
This is very similar to jimmy23013's JavaScript solution. It doesn't work under JavaScript however, because ^(?!(.)*(.*\1){3})
can always match on any string; it would capture an unset \1
by doing zero repetitions on (.)*
, and then \1
would match an empty string 3 times.
Making it portable to both JavaScript and Perl/PCRE by using (.)+
instead of (.)*
would end up making it 60 bytes, due to adding the +
character.
Alternatively:
((.)+(.*\2){3})?+^[[[-\]\]^^??+'''-**.22{335577}$$-]{57}$
Try it on regex101
This uses the possessive quantifier ?+
to atomically try to match a character repeated 4 times. If such a match is not found, it will be forced to do zero repeats (instead of one) and the ^
will match. Otherwise it will match something non-empty, and the ^
won't match.
(ABC|ACB|BAC|BCA|CAB|CBA)
but you wanted a generalized answer. \$\endgroup\$