Вы можете использовать отрицательный прогноз , который цитирует a подходящее имя :
r'(([a-z]+),?(?!.*\2))+'
Часть (?!.*\2)
гарантирует, что совпадающее имя не появится позже. Это служит примером того, как использовать негативную перспективу. Полное регулярное выражение в соответствии с требованиями OP:
re.match(r'^(([A-Z]{3,20})(?![A-Z,]*,\2,),)+$', string + ',')
Обратите внимание, что это проверяет string + ','
, то есть искусственную запятую, чтобы избежать путаницы с двумя разными группами для первой и последующих частей.
Я полагаю, что негативное предвидение является коротким замыканием в том смысле, что оно терпит неудачу, как только встречает совпадение. Это означает, что производительность в худшем случае должна быть O (N ^ 2). Мы можем проверить это, сгенерировав строки, которые содержат только уникальные имена, и измерить производительность регулярных выражений. Квадратичная подгонка дает t = a*N**2 + c
и a = 0.06 us, c = 3 us
.
код
from random import choice, sample
from string import ascii_lowercase as lowercase
import re
import time
N = 500
data = [None] * N
for i in range(N):
length = choice(range(3, 21))
x = ''.join(sample(lowercase, length))
while sample in data:
length = choice(range(3, 21))
x = ''.join(sample(lowercase, length))
data[i] = x
pattern = re.compile(r'^(([a-z]+),?(?!.*\2))+$')
timings = []
for i in range(2, N+1):
print('Begin iteration. ', end='', flush=True)
string = ','.join(data[:i])
print(f'Run for {i} unique names (lenght = {len(string)}) ... ', end='', flush=True)
t1 = time.clock()
m = re.match(pattern, string)
t2 = time.clock()
print('done.', end='', flush=True)
assert m is not None
timings.append(t2 - t1)
print(' End iteration.', flush=True)