Задание 24
Обработка символьной информации
Фрагменты строки: текущая длина цепочки, образцы и частоты, скользящее окно, позиции и границы, корректные выражения по шаблону.
7 подтем · 54 задачи + 12 дополнительных · высокий · ≈ 18 мин
Подтема 1 из 7
Строка и цепочка
Что спрашивают в задании 24, как прочитать строку из файла и найти самую длинную цепочку за один проход.
Как устроена тема
В задании 24 дают файл — одну длинную строку примерно из миллиона символов. Нужно найти в ней самый длинный (иногда самый короткий) кусок, идущий подряд, с заданным свойством. Вот задача, которую мы решим в этой теме:
«Текстовый файл 24-6.txt состоит из десятичных цифр и знаков «+» и «*» (сложения и умножения). Определите максимальное количество символов в непрерывной последовательности, являющейся корректным арифметическим выражением с целыми неотрицательными числами (без знака). В этом выражении никакие два знака арифметических операций не стоят рядом, в записи чисел отсутствуют незначащие (ведущие) нули. В ответе укажите количество символов.»
Перебрать все куски строки не получится — их сотни миллиардов. Поэтому строку проходят один раз и по дороге помнят только то, что нужно для ответа. Разберём, что значат слова условия:
| В условии | Что это значит |
|---|---|
| текстовый файл из 10⁶ символов | одна строка; кусков в ней около 5 · 10¹¹ — перебор не успеет |
| идущие подряд, непрерывная последовательность, подстрока | кусок строки без пропусков: символы пропускать и переставлять нельзя |
| максимальное количество символов | длина самого длинного подходящего куска |
| корректное арифметическое выражение | число, знак, число, знак, …, число — ни двух знаков рядом, ни знака на краю |
| нет незначащих (ведущих) нулей | число не начинается с 0, кроме самого числа 0 |
| если таких несколько, … | правило выбора при равной длине — его выполняют дословно |
Начнём с простых цепочек — на них видна главная идея одного прохода, — потом дойдём до окон, позиций и выражений. Тема состоит из шести подтем и дополнительного урока — у каждой своя страница:
- Строка и цепочка — ты сейчас здесь: чтение файла и самая длинная цепочка.
- Условия на соседей — одинаковые подряд, соседи различны, запретные пары.
- Образцы и блоки — DOGDOG…, цепочки пар, палиндромы.
- Скользящее окно — не более K и ровно K букв во фрагменте.
- Позиции и границы — K подряд идущих вхождений, особые начало и конец.
- Числа и выражения — регулярные выражения; решим задачу темы: ответ 172.
- Выражения с условиями и баланс — дополнительный урок для сложных вариаций.
Подряд — без пропусков
Что значит «идущие подряд символы»?
Кусок строки без пропусков: символы берут один за другим, ничего не выкидывая из середины. В строке AAAXAAAA самая длинная цепочка букв A — четыре, а не семь: чтобы получить семь, пришлось бы выкинуть X. Такой кусок ещё называют подстрокой или фрагментом.
В Python к символу обращаются по номеру — индексу. Счёт идёт с нуля: s[0] — первый символ, s[-1] — последний, len(s) — длина. Кусок вырезают s[a:b]: от a включительно до b не включительно.
s = 'CABAC'print(s[0], s[4], s[-1], len(s))print(s[1:4])Срез s[1:4] взял символы с номерами 1, 2 и 3 — правая граница не входит:
Если кусок задан двумя включёнными концами left и right, его длина — right - left + 1: символы с номерами 3, 4, 5 и 6 — это четыре символа, хотя 6 − 3 = 3.
Чтение файла
Как прочитать строку из файла?
Одной строкой: s = open('24.txt').readline().strip(). Без strip() в конце останется перевод строки — лишний символ. читает первую строку файла вместе с невидимым символом перевода строки, убирает его. В учебном файле 24.txt одна строка — XXAXXXXBXX:
s = open('24.txt').readline().strip()print(s, len(s)) line = open('24.txt').readline()print(len(line), len(line.strip()))С переводом строки символов 11, без него — 10. Если ищем «символы, отличные от A», перевод строки тоже посчитается — и ответ станет на единицу больше. Поэтому strip() пишем всегда.
Тонкости:[:-1] и файлы из многих строк
readline()[:-1] отрезает последний символ, даже если перевода строки в файле нет, — тогда пропадёт настоящий символ данных. strip() убирает только пробелы и переводы строк, а в файлах задания 24 их внутри строки не бывает.
Если в файле много независимых строк, их перебирают циклом for line in open('24.txt') и каждую очищают strip() отдельно.
Текущая и лучшая длина
Как найти самую длинную цепочку букв X, не перебирая все куски строки?
Идти по строке и помнить два числа: current — длину цепочки, которая кончается на текущем символе, и best — лучшую длину из уже виденных. Подходящий символ продлевает цепочку на единицу, неподходящий обрывает её: current = 0. После каждого символа best сравнивается с current.
s = open('24.txt').readline().strip() current = 0best = 0for c in s: if c == 'X': current += 1 else: current = 0 best = max(best, current) print(best)Пройди строку XXAXXXXBXX по шагам: заливка — текущая цепочка, скобка — лучшая из найденных.
- До цикла: current = 0, best = 0.
- s[0] = 'X': 'X' подходит: началась новая цепочка. current = 1, best = 1 — новый лучший результат.
- s[1] = 'X': 'X' подходит: цепочка продолжилась. current = 2, best = 2 — новый лучший результат.
- s[2] = 'A': 'A' не подходит и не входит ни в одну цепочку: сброс до 0. current = 0, best = 2.
- s[3] = 'X': 'X' подходит: началась новая цепочка. current = 1, best = 2.
- s[4] = 'X': 'X' подходит: цепочка продолжилась. current = 2, best = 2.
- s[5] = 'X': 'X' подходит: цепочка продолжилась. current = 3, best = 3 — новый лучший результат.
- s[6] = 'X': 'X' подходит: цепочка продолжилась. current = 4, best = 4 — новый лучший результат.
- s[7] = 'B': 'B' не подходит и не входит ни в одну цепочку: сброс до 0. current = 0, best = 4.
- s[8] = 'X': 'X' подходит: началась новая цепочка. current = 1, best = 4.
- s[9] = 'X': 'X' подходит: цепочка продолжилась. current = 2, best = 4.
Сравнивать best с current нужно на каждом шаге, а не только при обрыве. Иначе цепочка в самом конце строки в ответ не попадёт: после неё обрыва уже не будет. Строка проходится один раз — миллион символов программа обработает за доли секунды.
Что выводить
В условии просят не длину самой длинной цепочки X, а номер её первого символа, а если таких цепочек несколько — последней из них. Что меняется?
Меняется только то, что запоминаем: вместе с длиной храним начало лучшей цепочки, а правило «при равенстве» записываем явно. Перед кодом ответь на три вопроса: какие куски подходят, что сравниваем, что выводим.
s = 'XXAXXBXXAX'current = 0best_length = 0best_start = 0for i in range(len(s)): if s[i] == 'X': current += 1 else: current = 0 if current > 0 and current >= best_length: best_length = current best_start = i - current + 1 print(best_length, best_start)Цепочка, которая кончается на символе i, начинается на i - current + 1. Проверка current > 0 не даёт «цепочке» нулевой длины на символе A занять место ответа. Три цепочки длины 2 начинаются с 0-го, 3-го и 6-го символа. Знак >= пускает более позднюю цепочку при равенстве, поэтому выбрана последняя. Для первой из равных нужен строгий >.
Нумерацию берут из условия: если символы считают с единицы, к индексу Python прибавляют 1.
Итого
- Подрядкусок без пропусков — подстрока
- Чтение файла
s = open('24.txt').readline().strip() - Длина кускаright − left + 1 при включённых концах
- currentдлина цепочки, которая кончается на текущем символе
- bestобновлять после каждого символа
- При равенстве«>=» — последний из равных, «>» — первый
Проверьте себя
Ответь сначала сам, потом открой ответ.
s = 'QWERTY' чему равны s[1], s[-1], s[2:5] и len(s)?ОтветСкрыть ответ
s[2:5] берёт символы с номерами 2, 3 и 4.BBBABBBBAB найди самую длинную цепочку букв B, идущих подряд. Почему ответ не 7?ОтветСкрыть ответ
XYZX с переводом строки в конце. Сколько символов, отличных от X, насчитает программа, если забыть strip()?ОтветСкрыть ответ
best только тогда, когда цепочка обрывается. Что она напечатает для XXAXXX, если ищем цепочку X?ОтветСкрыть ответ
ОтветСкрыть ответ
Практика
Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.
Сколько всего подстрок
Сколько непустых подстрок у строки из 8 символов? А у строки из 300 000 символов?
Введите два числа через пробел в том порядке, в каком их спрашивают.
Подсказка
Подстрока задаётся парой границ left ≤ right: n · (n + 1) / 2 пар.
Решение
36 = 8 · 9 / 2; у строки из 300 000 символов — 45000150000.
Решение программой
for n in (8, 300_000): print(n * (n + 1) // 2)Ответ: 36 45000150000
Длина по включённым границам
Фрагмент начинается на позиции left = 4 и заканчивается на позиции right = 9, обе включены. Найди его длину, количество символов строго между концами и разность номеров.
Введите три числа через пробел: длину, количество символов строго между концами и разность номеров.
Подсказка
Обе границы входят во фрагмент: длина на единицу больше разности номеров.
Решение
Длина 6, строго между 4 символа, разность номеров 5: 9 − 4 + 1, 9 − 4 − 1 и 9 − 4.
Решение программой
left, right = 4, 9print(right - left + 1, right - left - 1, right - left)Ответ: 6 4 5
K чаще, чем L
Файл 24-3.txt содержит строки из заглавных латинских букв. Определите количество строк, в которых буква K встречается больше раз, чем буква L.
Введите число.
Подсказка
Каждую строку очисти strip() и сравни два count.
Решение
425: для каждой строки line.rstrip('\r\n') сравниваем count('K') и count('L'); строгое «больше» не засчитывает строки с равными количествами.
Решение программой
count = 0with open('24-3.txt') as f: for line in f: line = line.rstrip('\r\n') if line.count('K') > line.count('L'): count += 1print(count)Ответ: 425
Цепочка X
В строке AXXXBXXXXXAX найди самую длинную цепочку X.
Введите число.
Подсказка
current растёт на X и сбрасывается до 0 на другой букве.
Решение
5: цепочка XXXXX на позициях 5–9.
Решение программой
s = 'AXXXBXXXXXAX'current = best = 0for c in s: current = current + 1 if c == 'X' else 0 best = max(best, current)print(best)Ответ: 5
Без буквы Y
Текстовый файл 24-1.txt состоит из символов X, Y и Z. Определите максимальное количество идущих подряд символов, среди которых нет буквы Y.
Введите число.
Подсказка
Обновляй ответ после каждого символа, а не только при разрыве.
Решение
47: самый длинный фрагмент без Y стоит в самом конце файла — программа, обновляющая ответ только при разрыве, его потеряет.
Решение программой
s = open('24-1.txt').readline().strip() current = best = 0for c in s: current = current + 1 if c != 'Y' else 0 best = max(best, current)print(best)Ответ: 47
Без X и Y
В строке ABYCDXEFGHYXI найди самый длинный фрагмент без букв X и Y.
Введите число.
Подсказка
Цепочка допустимых символов: X и Y обрывают её, сброс до 0.
Решение
4: EFGH.
Решение программой
s = 'ABYCDXEFGHYXI'parts = s.replace('Y', 'X').split('X')print(max(len(p) for p in parts))Ответ: 4
Самая правая из длинных
В строке YXXYXXYXX найди номер первого символа самой длинной цепочки X; если таких несколько — самой правой. Нумерация с нуля. Каким будет ответ при нумерации с единицы?
Введите два числа через пробел: номер при нумерации с нуля, затем с единицы.
Подсказка
При равной длине бери цепочку правее: сравнение >=.
Решение
7: цепочки XX начинаются на позициях 1, 4, 7. При нумерации с единицы — 8.
Решение программой
s = 'YXXYXXYXX'current = best = start = 0for i in range(len(s)): current = current + 1 if s[i] == 'X' else 0 if current > 0 and current >= best: best = current start = i - current + 1print(start, start + 1)Ответ: 7 8