EduBrick
классная работа

Срезы и развороты

Программирование на Python: от нуля до олимпиад

0/18решено 0 из 18до зачёта осталось 10

Чему научитесь

  • Вырезать любой кусок строки одним выражением
  • Разворачивать строку и её части срезом с отрицательным шагом
  • Делать циклические сдвиги и разрезы пополам
  • Считать границы так, чтобы не потерять символ на краю

Как устроено занятие

Сначала разбор с примерами и короткими проверками понимания — они нужны, чтобы поймать непонятое сразу, а не через три темы.

Дальше 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. Если в условии позиции нумеруются с единицы и надо взять с aa-й по bb-ю включительно, срез будет 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:] даёт последние kk символов — но только пока kk больше нуля. При k = 0 получится s[0:], то есть вся строка целиком: минус ноль — это ноль. Если kk может быть нулевым, этот случай проверяют отдельно.

тест

Проверка: что даст срез

Строка 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:]

Циклический сдвиг — первые kk символов переезжают в конец:

print(s[k:] + s[:k])

Если kk может превышать длину, сначала берут остаток: k = k % len(s). Лишние полные обороты ничего не меняют.

Проверить начало или конец.

if s[:len(part)] == part:      # то же, что s.startswith(part)
if s[len(s) - len(part):] == part:   # то же, что s.endswith(part)

Методы из прошлого занятия короче и понятнее — пользуйтесь ими. Но знать, что за ними стоит обычное сравнение срезов, полезно: в задачах, где кусок берётся не с края, методов не будет.

теория

Три ошибки этого занятия

Сдвиг на единицу. Условие нумерует позиции с единицы, срез — с нуля. Правильный перевод: «с aa-й по bb-ю включительно» — это 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 — не хватает одного символа. Объясните, почему так выходит, и приведите два способа исправить.

Войдите, чтобы ответить.