Примеры:
Некоторые регулярные выражения, которые я пробовал:
import re
m = re.search(r"(\d).*?\1", string)
print(m.group(1))
import re
m = re.search(r"(\d)(?!(\d).*?\2).*?\1", string)
print(m.group(1))
Да, строка может содержать любой символ.
Есть ли причина, по которой регулярное выражение необходимо, когда гораздо более простой (и более эффективный) метод, предложенный @blhsing, явно превосходит?
Нет особой причины, по которой необходимо регулярное выражение. Когда я впервые столкнулся с проблемой, моей первой мыслью было, что будет быстрое и простое решение для регулярных выражений — я не ожидал, что оно окажется таким сложным.
Одна идея: захватить конец строки и добавить его в отрицательный просмотр (здесь группа 2):
(\d)(?=.*?\1(.*))(?!.*?(\d).*?\3.+?\2$)
Таким образом, вы можете контролировать, где заканчивается подшаблон .*?(\d).*?\3
в отрицательном просмотре. Если .+?\2$
успешен, это означает, что есть еще одна цифра, которая повторяется перед цифрой в группе 1.
Я закрепил шаблон для демонстрации regex101 с помощью ^.*?
, но вам не нужно делать это с помощью re.search
метода.
Другой способ: переверните строку и найдите последнюю повторяющуюся цифру:
re.search(r'^.*(\d).*?\1', string[::-1]).group(1)
Кажется, это работает. Я подожду немного, прежде чем принять ответ, на случай, если обнаружу ошибку или кто-то придумает что-то попроще.
Отличная идея поработать с перевернутой струной! Думаю, не стоит лениться на втором .*?
, так как первый уже «загоняет вас в угол».
@DuesserBaest: точно, второй квантификатор не должен быть ленивым, поскольку он не должен быть жадным. Вы можете выбрать тот, который вам нужен.
Regex может быть неподходящим инструментом для этой задачи. Хотя регулярное выражение в ответе @CasimiretHippolyte работает, довольно неэффективно сканировать всю остальную часть строки для каждого символа в строке до тех пор, пока не будет найден соответствующий символ, что обходится средней временной сложностью O(n ^ 2).
Более эффективный подход с линейной временной сложностью — использовать набор для отслеживания встретившихся символов и возвращать первый символ, уже добавленный в набор:
def first_repeating_digit(string):
seen = set()
for digit in filter(str.isdigit, string):
if digit in seen:
return digit
seen.add(digit)
raise ValueError('No repeating digit found.')
так что:
for s in '0123123123', '01234554321':
print(s, first_repeating_digit(s))
выходы:
0123123123 1
01234554321 5
Демо здесь
Бенчмарк-тест результат:
blhsing 0123123123 1.2911038296297193
blhsing 01234554321 1.3835312821902335
CasimiretHippolyte 0123123123 3.6279739402234554
CasimiretHippolyte 01234554321 4.1985282939858735
Если бы вы могли использовать модуль регулярных выражений PyPi , вы могли бы использовать утверждение просмотра назад с бесконечным квантором, а затем использовать re.search, чтобы получить первое совпадение.
(\d)(?<=\1\d+)
(\d)
Захватите одну цифру в группе 1.(?<=
Позитивный взгляд назад: утверждать, что от текущей позиции влево
\1
Сопоставьте ту же цифру, что и в группе 1, используя обратную ссылку.\d+
Сопоставьте 1 или более цифр)
Закройте просмотрСм. демонстрацию регулярных выражений и демонстрацию Python.
Однострочное решение, подход аналогичный ответу @blhsing. Он получает первое вхождение с next(filter(...
из x[1]
(который является номером строки) в a[:x[0]]
, когда a
— это строка, а x[0]
— номер числа в строке, то есть a[x[0]]
это в основном все числа, предшествующие x[1]
.
a = '01234554321'
print(next(filter(lambda x: x[1] in a[:x[0]], enumerate(a)))[1])
Может ли строка содержать что-либо кроме цифр?