Курс№24. Обработка строк из файла0%
Теория · номер 24 из 27

Обработка строк из файла

В файле одна длинная строка, надо найти самую длинную подстроку с нужным свойством. Разбираем двойной цикл, замены и окно из индексов.

7 разделов~35 минут1 первичный балл
  1. 1Что проверяет номер
  2. 2Двойной цикл с break
  3. 3Замены символов
  4. 4Арифметические выражения
  5. 5Окно из индексов: indexes и pop(0)
  6. 6Ловушки
  7. 7Домашка
Раздел 1 из 7

Что проверяет номер

Файл содержит одну строку на миллион символов, поэтому искать глазами бесполезно. Нужен способ, который проходит её быстро. Какой именно, зависит от условия, и ниже разобраны основные.

Длинная строка (до миллиона символов). Надо найти самую длинную (или самую короткую) подстроку с заданным свойством: пара/тройка встречается ровно k раз, нет двух одинаковых подряд, корректное арифметическое или римское число и т. п.

Простыми словами

Представь очень длинную ленту с буквами. Глазами её не прочитать, поэтому мы ведём по ней пальцем и считаем.

Самая простая идея: идём по буквам и держим счётчик текущей цепочки. Подошла буква, счётчик растёт. Не подошла, начинаем считать заново. Самое большое значение счётчика запоминаем. В строке AABBBA самая длинная цепочка одинаковых букв равна 3.

Если условие сложнее, например «подстрока содержит нужный кусок ровно k раз», помогает окно. Запоминаем места, где встретился кусок, и держим только последние k. Как только добавилось новое место, самое старое выбрасываем. Между первым и последним местом как раз лежит нужная подстрока, и остаётся сравнить её длину с лучшей.

Раздел 2 из 7

Двойной цикл с break

Начало подстроки перебираем внешним циклом, конец внутренним. break прерывает внутренний цикл, как только продолжать бессмысленно: так перебор остаётся быстрым.

Идея: фиксируем начало i, наращиваем конец n, и как только условие сломано, цикл обрываем. Уже найденный максимум m не даёт смотреть короткие кандидаты.

Максимум (не более 5 вхождений «2025», оканчивается на 2025):

python
s = open('24.txt').readline()
m = 0
for i in range(len(s)):
    for n in range(i + m, len(s)):
        c = s[i:n + 1]
        if c.count('2025') > 5: break
        if c[-4:] == '2025': m = max(m, len(c))
print(m)

range(i + m, ...) отсекает слишком короткие подстроки: нам нужна длина больше текущего m.

Минимум: конец идёт от начала, и как только условие выполнено, запоминаем длину и выходим из внутреннего цикла.

Сначала простой случай: один проход

Если просят самую длинную цепочку одинаковых букв, двойной цикл не нужен. Держим счётчик: буква совпала с прошлой, прибавляем единицу, не совпала, начинаем заново.

python
s = 'AABBBA'
k = 1
best = 1
for i in range(1, len(s)):
    if s[i] == s[i - 1]:
        k += 1
    else:
        k = 1
    best = max(best, k)
print(best)

Почему двойной цикл не зависает

Простой перебор всех подстрок на строке из миллиона символов считался бы вечно. Его спасают две вещи:

Запись s[i:n + 1] это кусок строки от i до n включительно, поэтому в конце стоит + 1.

Попробуй самучебная задача

Строка s = 'CABBAACBCABABBACABCAAB'. Найди наибольшую длину подстроки, в которой не больше двух букв A.

Подсказка

Внешний цикл по началу, внутренний считает буквы A и прерывается на третьей.

Раздел 3 из 7

Замены символов

Если несколько символов для задачи равноценны, удобно заменить их одним. Условие становится короче, а проверка проще.

Если несколько букв ведут себя одинаково, заменяем их на одну:

python
s = s.replace('0', 'N').replace('P', 'N')
m = 0
for i in range(len(s)):
    for n in range(i + m, len(s)):
        c = s[i:n + 1]
        if 'NN' in c: break
        m = len(c)
print(m)

Это «максимальная подстрока без двух подряд идущих N» (после замены).

Попробуй самучебная задача

Дана строка s = 'CABBAACBCABABBACABCAAB'. Какова наибольшая длина подстроки без буквы C?

Подсказка

max(len(x) for x in s.split('C'))

Попробуй самучебная задача

Для той же строки найди наибольшую длину цепочки из одинаковых букв, идущих подряд.

Подсказка

Один проход и счётчик: буква совпала с прошлой — прибавь, иначе начни с 1.

Раздел 4 из 7

Арифметические выражения

Если в строке ищут выражения, удобнее описать их образец регулярным выражением, чем разбирать символы вручную.

python
from re import finditer
num = r'(0|[1-9][0-9]*)'             # число без ведущих нулей
v = rf'{num}(\+{num})*'               # сумма чисел
print(max(len(x.group()) for x in finditer(v, s)))

Это образец суммы чисел без ведущих нулей. Если условие требует особое значение выражения, сначала пойми, как оно выглядит в записи, и опиши эту запись таким же образцом.

Регулярное выражение читается по кускам:

записьчто значит
[1-9]одна цифра от 1 до 9
[0-9]*любое число цифр, можно ни одной
0|[1-9][0-9]*либо ноль, либо число без ведущего нуля
\+сам знак плюс, с косой чертой, потому что просто + значит «повтор»
(...)*кусок в скобках повторяется сколько угодно раз

Запусти на короткой строке и посмотри, что находится:

python
from re import finditer
s = '12+05+7++30+4'
num = r'(0|[1-9][0-9]*)'
v = rf'{num}(\+{num})*'
for x in finditer(v, s):
    print(x.group())
Раздел 5 из 7

Окно из индексов: indexes и pop(0)

Этот приём нужен, когда просят подстроку с заданным числом вхождений. Запоминаем, где встретился образец, и держим в списке только последние k позиций.

Если условие «шаблон встречается ровно/не менее k раз, длина минимальна», двойной цикл не нужен. Идём по строке один раз и храним индексы последних k вхождений шаблона: когда их ровно k, подстрока идёт от первого индекса до конца последнего вхождения.

Код (шаблон из пяти букв, где совпадают 1-я, 3-я, 5-я и 2-я, 4-я, 170 вхождений, минимум):

python
s = open('24.txt').readline()
indexes = []
mn = float('inf')                                     # бесконечность
for i in range(len(s) - 4):
    if s[i] == s[i + 2] == s[i + 4] and s[i + 1] == s[i + 3]:
        indexes.append(i)
    if len(indexes) > 170:
        indexes.pop(0)                                # выкидываем самое раннее
    if len(indexes) == 170:
        start_i = indexes[0]
        end_i = indexes[-1] + 4
        mn = min(mn, end_i - start_i + 1)
print(mn)
Попробуй самучебная задача

Для той же строки s = 'CABBAACBCABABBACABCAAB' найди минимальную длину подстроки, в которой ровно 3 раза встречается AB.

Подсказка

Запомни индексы всех AB и смотри на каждые три подряд.

Раздел 6 из 7

Ловушки

Ловушка

s.count('AA') не считает перекрывающиеся вхождения (для AA в AAA даст 1, а их два). Для пар вроде AA надо брать (?=…).

Ловушка

Подстрока, которая оканчивается на нужную букву, обязана закончиться именно на ней: проверяй c[-1].

Ловушка

Римские числа: у числа должна быть корректная запись по всем правилам из условия, а не просто «из букв I V X L C D M».

Ловушка

Одиночный «0» в арифметике — тоже выражение со значением 0.

Ловушка

Файл одной строкой: читай readline(), не read() с переносами.

Раздел 7 из 7

Домашка

Задачи с настоящих экзаменов и из банка ФИПИ. Решай прямо здесь: кнопка «Решать здесь» открывает редактор, а код каждой задачи сохраняется.

Ещё задачи этого вида лежат в разделе «Задания», №24.

Задачи «Попробуй сам» учебные, в домашке задачи с настоящих экзаменов и из банка ФИПИ. Код в блоках можно менять и запускать, он выполняется прямо в браузере.