Веду на сто
Учебная статья

Python: индексы и срезы

Нумерация с нуля, правая граница среза, соседние элементы и длина непрерывного фрагмента.

Строка хранит символы по порядку, список — элементы. Для обращения к конкретному месту используется его номер внутри последовательности — индекс. Он отсчитывается от нуля. Срез выбирает сразу несколько элементов; его границы устроены иначе, чем включённые начало и конец фрагмента в условии задачи.

Индекс обозначает один элемент

s = "ABCB"
print(s[0])
print(s[3])
print(len(s))

Программа печатает A, B, затем 4. Длина равна четырём, но последний индекс равен трём. Если длина обозначена n, допустимые неотрицательные индексы идут от 0 до n-1. Поэтому s[len(s)] обращается уже за конец строки и вызывает IndexError.

Отрицательные индексы отсчитываются с конца: s[-1] означает последний символ, s[-2] — предпоследний. Это удобно при намеренном обращении к хвосту. Но выражение s[r-1] при r == 0 прочитает последний символ вместо отсутствующего предыдущего. Нужна отдельная проверка r > 0.

Правая граница среза исключена

Запись s[a:b] выбирает индексы от a включительно до b исключительно. Значение b показывает, где остановиться, поэтому сам элемент с таким индексом не входит.

s = "ABCB"
print(s[1:3])
print(s[:2])
print(s[2:])

Результаты: BC, AB, CB. Пропущенное начало означает начало последовательности, пропущенный конец — её конец. Срез s[2:2] пуст: между одинаковыми границами нет элементов. Срез s[2:20] вернёт CB, потому что слишком далёкая правая граница ограничивается концом строки. Отдельный индекс s[20] при этом недопустим.

Включённые границы фрагмента

В алгоритмах поиска часто l обозначает индекс первого символа, а r — последнего, причём оба включены. Чтобы получить именно этот фрагмент, нужна запись s[l:r+1]. Добавление единицы переводит включённый конец в исключённую границу среза.

s = "ABCBCC"
l = 1
r = 4
print(s[l:r+1])
print(r-l+1)

Получаются BCBC и 4. Формула длины r-l+1 учитывает обе конечные позиции. При l=r остаётся один символ, что сразу объясняет необходимость единицы. У среза с обычными границами s[a:b] длина равна b-a, если 0\le a\le b\le n.

Соседние элементы и шаг

Двухбуквенная пара, заканчивающаяся на индексе r, записывается как s[r-1:r+1]. Пара, начинающаяся на индексе l, — s[l:l+2]. Эти срезы отвечают на разные вопросы: что добавилось справа и что исчезнет при удалении левого символа.

s = "ABCBCC"
for r in range(1, len(s)):
    print(s[r-1:r+1])

Будут напечатаны AB, BC, CB, BC, CC. Функция range() тоже исключает правую границу: индекс len(s) не появляется.

У списка чисел те же границы. Если соседняя пара начинается на индексе i, её элементы — a[i] и a[i+1]. Поэтому начала перебираются через range(len(a)-1): последний элемент отдельно уже не начинает пару. У списка из четырёх чисел будут три соседние пары, а у списка из одного числа — ни одной. Это отличается от выбора любых двух элементов, где соседство не требуется.

Третье поле среза задаёт шаг. Например, s[::2] берёт индексы 0,2,4 и даёт ACC. Это подпоследовательность, а не непрерывный фрагмент исходной строки.

Возможные ловушки

Замена r+1 на r исключает последний выбранный символ. Невнимательное использование отрицательного индекса соединяет начало с концом. Срез через один символ пропускает позиции и меняет предмет поиска. Короткая строка с подписанными индексами позволяет отличить эти ситуации до запуска на большом файле.

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