We can try it differently. The attempt you made allows several strings to slip through which you do not want. Namely, all with repetitions. In the following case, I'm experimenting a bit with PowerShell to show a solution. First, we need all the possible strings that we can expect as input:
$tests = 'TPI'[0..2]|%{$a=$_;"$a"; 'TPI'[0..2]|%{$b=$_;"$a$b"; 'TPI'[0..2]|%{"$a$b$_"}}} | sort
This gives the following sequence of values (I will format them one line, but they come out one after each line):
$tests I II III IIP IIT IP IPI IPP IPT IT ITI ITP ITT P PI PII PIP PIT PP PPI PPP PPT PT PTI PTP PTT T TI TII TIP TIT TP TPI TPP TPT TT TTI TTP TTT
This, of course, is also what regular expression
^(?i:[TPI]){1,3}$
will match.
We can limit what we want to match using the so-called negative lookahead statement, which will only match the following text, but will not really match the text itself, which will allow it to be captured by the template that you have above. This can be done using (?!) , Where you insert some subexpression after ! . Let's try and limit the input that does not start with two I , two P or two T :
$tests -match '^(?!II|PP|TT)(?i:[TPI]{1,3})$' I IP IPI IPP IPT IT ITI ITP ITT P PI PII PIP PIT PT PTI PTP PTT T TI TII TIP TIT TP TPI TPP TPT
As you can see, these results have disappeared. We can simplify this if we use a capture group and a backlink. Brackets usually (unless they start with (? ) Capture what is matched inside them, and you can use it after matching to extract parts from a match or to replace. But you can also use it in the template itself on many machines with regular expressions (in fact, I think that there is no mechanism that allows negative display, not a backlink in the template). So, II|PP|TT can be written as (.)\1 , which simply says "letter, follows exactly the same letter, "as \1 is the reverse with ylkoy, pointing out that it was correlated with (.) .
Now we still have several values that we do not need, namely all with two identical letters at positions 2 and 3, as well as at positions 1 and 3. We can get rid of the first:
$tests -match '^(?!.?(.)\1)(?i:[TPI]{1,3})$' I IP IPI IPT IT ITI ITP P PI PIP PIT PT PTI PTP T TI TIP TIT TP TPI TPT
.? at the beginning now says: “coincidence with the symbol or not”, which, therefore, extends what we had to two, exclude matches with repetitions at the end. For the second set, we just need to eliminate matches that look like (.).\1 , i.e. A letter, and then another, and then a repetition of the first. We can extend the regex above by simply adding another one .? , i.e. an optional letter between the capture group and the backlink:
$tests -match '^(?!.?(.).?\1)(?i:[TPI]{1,3})$' I IP IPT IT ITP P PI PIT PT PTI T TI TIP TP TPI
Now this is exactly the set that you wanted to present. Last regex
^(?!.?(.).?\1)(?i:[TPI]{1,3})$
It's shorter than before, that's for sure. Could this be easier to discuss, as this may require some explanation of what he is doing. This is probably even more suitable for a more succinct approach in another answer. It’s shorter, really, but this is my answer, and we argue for votes, I just have to say that I don’t like it ;-) ... just kidding. But for such things, I assume that separating the base schema from exceptions really makes sense for readability.
Another option might be to check the base pattern with regex, i.e. your initial approach. And then use the code to reject duplicates, which might look something like
($s.ToLowerInvariant().ToCharArray() | select -Unique).Count -eq $s.Length
depending on your language - provided that it simplifies and reads these things.