Чему научитесь
- Вырезать любой кусок строки одним выражением
- Разворачивать строку и её части срезом с отрицательным шагом
- Делать циклические сдвиги и разрезы пополам
- Считать границы так, чтобы не потерять символ на краю
Как устроено занятие
Сначала разбор с примерами и короткими проверками понимания — они нужны, чтобы поймать непонятое сразу, а не через три темы.
Дальше 15 задач лестницей: разминка, основа, со звёздочкой. Занятие засчитывается, когда решено 10 — остальные не пропадают и учитываются отдельно.
После занятия — вторая часть, ещё 15 задач на те же приёмы в новых сюжетах.
Сколько это займёт
Примерно час-полтора вместе с задачами. Сроков нет: можно закрыть вкладку и вернуться когда удобно — прогресс сохранится.
Кусок строки одним движением
Чтобы взять из строки первые три символа, вы бы сейчас написали цикл. А можно так:
s = "abcdef"
print(s[0:3]) # abc
Это срез — кусок строки от одного номера до другого. Записывается двумя числами через двоеточие, и правило про границы ровно то же, что у range: левая входит, правая нет.
| срез | что берёт | результат для abcdef |
|---|---|---|
s[0:3] |
символы 0, 1, 2 | abc |
s[2:5] |
символы 2, 3, 4 | cde |
s[1:2] |
только символ 1 | b |
s[3:3] |
ничего | пустая строка |
Длина среза считается легко: b - a. Если в условии позиции нумеруются с единицы и надо взять с -й по -ю включительно, срез будет s[a-1:b] — и это то место, где чаще всего ошибаются.
Срез, как и методы, создаёт новую строку. Исходная остаётся нетронутой.
Границы можно не писать
Если срез идёт от начала или до конца, границу опускают:
s = "abcdef"
print(s[:3]) # abc — от начала
print(s[3:]) # def — до конца
print(s[:]) # abcdef — вся строка
Пропущенная левая граница означает «с самого начала», пропущенная правая — «до самого конца». Это не сокращение ради красоты: s[:k] и s[k:] — самая частая пара в задачах, потому что вместе они разрезают строку надвое.
Отрицательные номера работают и здесь:
print(s[-3:]) # def — последние три
print(s[:-1]) # abcde — всё, кроме последнего
print(s[1:-1]) # bcde — без первого и последнего
Одна ловушка. s[-k:] даёт последние символов — но только пока больше нуля. При k = 0 получится s[0:], то есть вся строка целиком: минус ноль — это ноль. Если может быть нулевым, этот случай проверяют отдельно.
Проверка: что даст срез
Строка s равна "python". Что вернёт s[1:4]?
Шаг и разворот
У среза есть третье число — шаг, ровно как у range:
s = "abcdef"
print(s[::2]) # ace — каждый второй
print(s[1::2]) # bdf — каждый второй, начиная со второго
А если шаг отрицательный, срез идёт справа налево. Отсюда самая известная запись в Python:
print(s[::-1]) # fedcba — строка наоборот
В занятии 13 разворот занимал три строки с циклом. Теперь это одно выражение, и работает оно быстрее.
Отсюда же короткая проверка на палиндром:
if s == s[::-1]:
print("YES")
Развернуть можно и кусок: s[2:5][::-1] — сначала вырезали, потом развернули. Порядок действий читается слева направо.
Срез не ошибается — и это важно
Обращение по номеру за границей строки роняет программу:
s = "abc"
print(s[10]) # IndexError
А срез — нет:
print(s[0:100]) # abc — взял всё, что было
print(s[10:]) # пустая строка
print(s[5:2]) # пустая строка
Срез никогда не падает. Если границы вышли за строку, он молча берёт то, что есть, а если брать нечего — возвращает пустую строку.
Это очень удобно: s[:k] работает при любом k, и проверять длину не нужно. Но у удобства есть обратная сторона: ошибка в границах не проявится сообщением. Программа не упадёт, просто выведет не то — короткий кусок вместо нужного или вовсе пустоту.
Поэтому со срезами полезно проверять себя на границах руками: подставить конкретные числа и посчитать длину результата. Она равна b - a, и если получилось не то, что ожидалось, ошибка именно здесь.
Проверка: длина среза
Строка имеет длину 10. Сколько символов вернёт срез s[2:7]?
Введите целое число.
Три приёма, которые стоит запомнить
Срезы превращают несколько типовых задач в одну строку кода.
Разрезать пополам.
half = len(s) // 2
left = s[:half]
right = s[half:]
Циклический сдвиг — первые символов переезжают в конец:
print(s[k:] + s[:k])
Если может превышать длину, сначала берут остаток: k = k % len(s). Лишние полные обороты ничего не меняют.
Проверить начало или конец.
if s[:len(part)] == part: # то же, что s.startswith(part)
if s[len(s) - len(part):] == part: # то же, что s.endswith(part)
Методы из прошлого занятия короче и понятнее — пользуйтесь ими. Но знать, что за ними стоит обычное сравнение срезов, полезно: в задачах, где кусок берётся не с края, методов не будет.
Три ошибки этого занятия
Сдвиг на единицу. Условие нумерует позиции с единицы, срез — с нуля. Правильный перевод: «с -й по -ю включительно» — это s[a-1:b]. Проверяйте на маленьком примере, а не на глаз.
Молчаливая пустота. Перепутанные границы дают не ошибку, а пустую строку. Если программа выводит пустоту или слишком короткий кусок — смотрите на числа в срезе, а не на остальной код.
s[-0:] вместо пустой строки. При k = 0 запись s[-k:] возвращает всю строку. Случай нуля проверяется отдельно.
Как проверять себя
- строка из одного символа — половины, края и середина вырождаются;
k = 0иkбольше длины — там, где число приходит из входных данных;- кусок с самого начала и до самого конца — то есть
a = 1иb = len(s).
Практика: пятнадцать задач
Лестница прежняя: пять разминочных, семь основных, три со звёздочкой. Зачёт при десяти решённых.
Почти всё здесь решается одним-двумя срезами — если правильно посчитать границы. Поэтому основная работа не в коде, а на бумаге: выпишите номера для короткого примера и проверьте, что срез берёт именно то, что нужно.
В задачах со звёздочкой срез оказывается внутри перебора: перебираются длины кусков или сдвиги, а срез каждый раз вырезает очередной вариант. Строки там короткие — ограничения это учитывают.
Разворот, который теряет символ
Задача: «дана строка, выведите её наоборот».
Ученик написал:
s = input()
print(s[len(s):0:-1])
На строке abcdef программа выводит fedcb — не хватает одного символа. Объясните, почему так выходит, и приведите два способа исправить.