Почему массивы начинаются с нуля
Самое очевидное объяснение: индекс — это смещение относительно начала массива. Так элементы массива легче адресовать в памяти.
Проверим это на C.
#include int main() < int data[3] = ; int i = 0; printf("Array address: %p\n", data); do < printf("Array[%u] = %p\n", i, (void *)(&data[i])); i++; >while(i
Array address: 0x7ffd7c514a6c
Array[0] = 0x7ffd7c514a6c
Array[1] = 0x7ffd7c514a70
Array[2] = 0x7ffd7c514a74
Как первый (нулевой) элемент, так и сам массив находятся по одному и тому же адресу, поскольку 0-й элемент удалён на 0 элементов от начала. Эта связь между указателями и массивами в C настолько тесная, что их даже можно рассматривать вместе.
Однако это ответ на вопрос «зачем», а не «почему». Нумеровать массивы с нуля стали не сразу. Удивительно, но развитие такого простого вопроса не умещается в предложении или абзаце.
Потому что так удобнее
К началу 1960-х годов сформировалось три подхода к организации структуры, которую сегодня мы называем статическим массивом:
-
Исчисление с 0. Нижняя граница массива начинается с нуля.
Непривычно для обывателя, если это не житель Германии, свыкшийся с нумерацией этажей в зданиях. Последний элемент массива из, скажем, 8 элементов имеет номер 7.
Не всё так однозначно. Единообразия даже у самых первых языков программирования не существовало:
- Нумерация с нуля: LISP 1.5, APL (допускает выбор при запуске программы).
- Нумерация с единицы: APL, Бейсик, ранний Фортран.
- Произвольные границы: Алгол-60, затем Фортран, CPL, SIMULA, CLU, PL/1, Кобол, Паскаль, Алгол-68, JOVIAL.
Конечно, это не самый сильный аргумент. Часто языки снабжались синтаксическим сахаром для нумерации с 1. К примеру, в Алголе массив V[0:3] начинается с 0, а массив V[3] — с 1.
Создатель языка APL Кеннет Айверсон в книге «A Programming Language» объясняет, почему далее по тексту он будет пользоваться нумерацией элементов массива с нуля. При этом язык допускает отсчёт как с единицы, так и с нуля. Айверсон приводит уже описанный выше аргумент о сдвигах в позиционной нумерации.
Тем не менее и в эпоху до C звучали предложения выбирать нумерацию с 0. Логично предположить, что это была историческая неизбежность, до которой оставалось несколько лет.
Потому что парусные регаты мешали вычислениям
В 1967 году Мартин Ричардс впервые реализует компилятор своего детища — BCPL (Basic Combined Programming Language). Этот язык программирования был призван исправить проблемы созданного в начале шестидесятых языка CPL путём отказа от технологий, затрудняющих компиляцию.
Первый компилятор BCPL был написан для операционной системы CTSS машины IBM 7094 Массачусетского технологического института. На тот момент компьютеры — это уже не целые комнаты и электронные лампы, но всё ещё огромные шкафы с транзисторами и консоли управления без экранов. Ресурсов серии 7090 хватило, чтобы запустить американца в космос. Но мощность измерялась в тысячах машинных слов, микросекундах тактов и килофлопсах, а цена — в миллионах долларов.
В пятидесятых и шестидесятых IBM выдавала институту щедрые скидки на свои научные компьютеры или даже предоставляла их бесплатно. В 1963 году в МТИ поставили IBM 7094. На компьютер уговор был такой: 8 часов в сутки получают специалисты института, 8 часов — другие колледжи и университеты Новой Англии, а третью смену отдавали самой IBM для личных нужд.
На этом особенности не кончались. Несколько раз в год IBM обсчитывала регаты: президент IBM соревновался в заплыве на больших яхтах в проливе Лонг-Айленд, и каждое из судов получало очки гандикапа по специальной сложной формуле. Когда в вычислительный центр приходило задание, операторам полагалось всё бросить и запустить вычисления этих очков.
Машинный зал с установленным компьютером IBM 7094 в Колумбийском университете США. Фотоархив
Вообще, и без этого был шанс не получить результат вычислений. Изначально 7094 работал в режиме пакетной обработки. Вспомогательная система на IBM 1401 загружала пакет задач с перфокарт на ленту, а затем запускала задачи одну за другой и записывала результаты для последующей печати. Для каждой задачи приводилась оценка времени. Если задача выходила за пределы отпущенного, её принудительно прерывали.
В итоге компиляция в BCPL была оптимизирована по максимуму. То есть и указатели на элементы массива должны максимально близко походить на машинный код, без необходимости вычитать единицу при указании на элемент массива. Хотя во время выполнения программы не играет роли схема организации массивов, нумерация с нуля могла появиться для оптимизации времени компиляции.
Впрочем, конкретно эта версия — лишь гипотеза Майка Хойе. Ей нет никаких подтверждений от собствено Ричардса или хотя бы упоминаний в литературе. Мартин Ричардс в переписке с Хойе лишь приводит общие соображения о том, что он хотел достичь близости к машинному коду, поэтому указатель p и p + 0 — это одна и та же переменная. Ни на какие яхты Ричардс не жалуется.
К тому же на момент появления первого компилятора BCPL уже была готова операционка Compatible Time-Sharing System. В ней на одной машине с разделением времени компьютер выполняет одну задачу за один раз, но с оптимизацией ввода и вывода, чтобы паузы одного пользователя заполнялись работой других. Это уже не былая пакетная обработка задач.
Точно известно, что в дальнейшем BCPL значительно повлиял на все современные языки программирования.
В 1969 году Кен Томпсон урезал функциональность BCPL до языка B. В дальнейшем, для развития операционной системы Unix, Деннис Ритчи улучшил B добавлением функций PDP-11, в результате чего и получился C. Полвека спустя список языков, на которые оказал влияние C, занимает в «Википедии» целую страницу.
В языке BCPL v!5 и 5!v совпадают, поскольку являются указателем на !(v+5) или !(5+v) . Аналогично в C v[5] эквивалентно 5[v] .
Потому что так предложил Дейкстра
В 1982 году Эдсгер Дейкстра опубликовал статью «Почему нумерация должна начинаться с нуля». В ней он низверг как нумерацию с единицы, так и произвольные границы индексов. Очевидно, что Дейкстра — не человек, который легко поддаётся влиянию C, но также он раскритиковал Алгол-60, своё собственное детище, и Паскаль.
Известно, что Дейкстра ближе к концу жизни всё больше писал от руки, а не набирал тексты на машинке. Архив рукописей Эдсгера Вибе Дейкстры
Если необходимо записать интервал натуральных чисел 2, 3, …, 12 без опухоли из точек, возможны четыре варианта:
Затем автор замечает, что для нижней границы предпочтителен знак «⩽», поскольку наименьшее натуральное число существует. В противном случае, если последовательность начинается с наименьшего натурального числа, нижняя граница не будет натуральным числом.
Затем статья делает вывод, что из соображений простоты для последовательности из N членов предпочтительнее диапазон 0 ⩽ i < N , а не 1 ⩽ i < N+1 .
Куда менее известный документ — это полушуточная техническая заметка от 1 апреля 1980 года IEN 137 под названием «О священных войнах и призыв к миру» [On Holy Wars and a Plea for Peace].
В заметке Дэнни Коэн приводит интересный аргумент: в системе счисления с основанием b при отсчёте с нуля первые b ^ N неотрицательных чисел представляются ровно N цифрами. Например, если речь про двоичную запись, то 2 ^ 3 = 8 , и восьмой элемент массива будет иметь номер 1112, а не 10002. Понятно, что с такими преобразованиями легко бы мог справиться компилятор.
Потому что так более элегантно
Это объяснение может раздражать субъективностью. Соображения о красоте у каждого свои и совпадать не обязаны. Но авторы языков программирования — тоже люди со своими предпочтениями.
В конце восьмидесятых Гвидо ван Россум при создании Python как учёл свой предыдущий опыт с языком ABC, так и задумал привлечь аудиторию хакеров от мира Unix и C.
С 1983 года Гвидо работал в Центре математики и информатики в Амстердаме над реализацией языка ABC. Проект ставил целью создать язык программирования, пригодный для обычных людей, но не настолько ужасно реализованный, как Бейсик. Нумерация массивов в ABC начиналась с единицы. Такой же схемы придерживались другие знакомые Россуму языки — Алгол, Фортран и Паскаль.
Однако в вышедшем в 1991 году Python нумерация начинается с нуля, а не единицы. Получилось так в результате долгих размышлений.
Одна из причин — слайсы. Чаще всего при создании слайса используются операции «получить первые n элементов» и «начиная с i, получить следующие n элементов». При этом первый случай эквивалентен i == первый индекс . Гвидо посчитал, что лучше, если обе операции возможны без лишней головной боли в виде коррекции +1 и −1.
Если первый элемент имеет номер 1, то возможно указывать первый элемент и число элементов, которые нужно получить. Россум уже был знаком с подобным по ABC и вполне мог бы положиться на этот опыт.
Тем не менее автора Python очаровал синтаксис слайсов полуоткрытого интервала, если нумерация начинается с нуля: a[:n] (или a[0:n] ) и a[i:i+n] . К примеру, строку a легко разбить на три части a[:i] , a[i:j] и a[j:] .
Заметим, что в воспоминаниях Гвидо не приводит ни призывы гениев информатики, ни практики других языков, ни принципы, заложенные мастодонтами компьютерных вычислений шестидесятых. Зато слово «элегантность» в пяти абзацах его объяснения встречается 3 раза.
Почему же массивы начинаются с нуля?
Так исторически сложилось.
Почему во многих языках индексирование начинается с 0?
Предположим, что у вас есть массив из целых чисел, каждое по четыре байта. Массив расположен в памяти последовательно. Чтобы получить элемент N, надо взят указатель на массив и прибавить к нему (N — 1) * 4 байт, или, если вести отсчет от нуля, просто N * 4 байта
14 авг 2016 в 18:12
@Etki: А почему не как ответ?
14 авг 2016 в 18:17
В электронике отсчет ножек многих микросхем тоже начинаются с 0. Я думал это связано.
14 авг 2016 в 18:17
На линейке первая цифра тоже 0! координата первого значения начинается с нулевой точки.
14 авг 2016 в 19:09
мне лень переводить, но большинство серьезных причин — приведено здесь quora.com/… И там ссылка на статью Дейкстры
14 авг 2016 в 19:38
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
В первую очередь потому, что с точки зрения реализации понятие «индекса» элемента в непрерывном агрегате напрямую связано с понятием «смещения» элемента от начала агрегата в памяти. Понятно, что смещение самого первого элемента агрегата равно нулю. Соответственно и индекс его разумно взять равным нулю.
Другими словами, как ни верти, но индекс первого элемента в агрегате все равно придется пересчитывать в значение 0 на машинном уровне. В такой ситуации реализация индексации с ненулевой базой будет всегда волей-неволей подразумевать неявное приведение ее к индексации с нулевой базой. В многих языках программирования просто не считают необходимым это делать. Зачем, действительно?
Отслеживать
ответ дан 14 авг 2016 в 23:03
AnT stands with Russia AnT stands with Russia
69k 3 3 золотых знака 62 62 серебряных знака 139 139 бронзовых знаков
При выделении динамической памяти в C/C++ возвращаемый указатель не обязательно указывает на начало выделенного из кучи участка памяти.
14 авг 2016 в 23:09
@hunter: А какое это имеет значение в данном случае? Главное, чтобы указатель указывал на начало пользовательской области памяти, т.е. на ту точку, от которой будут отсчитываться смещения. А то, что блок может также содерждать какие-то служебные области со служебными данными к данному вопросу не относится никак. Что именно вы хотели сказать?
14 авг 2016 в 23:11
то и хочу сказать что даже при выделении не фрагментированной области памяти из указателя на нулевой элемент рано или поздно может возникнуть необходимость получить указатель на ячейку памяти с меньшим адресом.
14 авг 2016 в 23:17
@hunter: Прекрасно. Но какое это имеет отношение к теме индексации? «Ячейка с меньшим адресом» не является частью пользовательских данных, и доступаться к ней путем отрицательной индексации пользовательских данных никто не будет (в здравом уме). Такая область памяти будет прекрасно доступна путем вычета некоего известного платформе смещения из указателя «пользовательского» уровня и все. Я по прежнему не вижу, какое это все имеет отношение к рассматриваемой теме или к моему ответу.
14 авг 2016 в 23:22
@avp: До языка С были языки B и BCPL с той же самой нулевой индексацией. Язык С унаследовал свой подход к массивам именно из B и BCPL, с одним важным изменением. В B и BCPL массивы физически реализовались через обычные указатели. В С физические указатели убрали, но стали их «эмулировать» (пресловутый «array type decay»). А все остальное в С осталось, как в B и BCPL именно для облегчения обратной совместимости с этими языками.
Почему индекс массива начинается с нуля ?
Почему счетчик в цикле for начинается с нуля, а не с единицы?
привет. почему когда надо сделать какое-то действие, например 4 раза, то пишут: for (int i=0.
Не могу понять, почему ввод строк начинается со 2-ой строки
не могу понять, почему ввод строк начинается со 2-ой строки #include <iostream>.
Почему после считывания матрицы из файла начинается повторное считывание?
Здравствуйте. ПРоблема такова, считывает размер матрицы и саму матрицу, после успешного считвания.
Неэпический
17849 / 10617 / 2049
Регистрация: 27.09.2012
Сообщений: 26,686
Записей в блоге: 1
потому что смещение адреса первого элемента массива от начала этого массива равно нулю
Регистрация: 07.04.2011
Сообщений: 29
потому что так заведено изначально
8864 / 6641 / 907
Регистрация: 14.02.2011
Сообщений: 23,372
Сообщение от sergey.shuvalov
Почему первый элемент массива обозначается 0 , а не 1.
ну потому что
Сообщение от Croessmah
потому что смещение адреса первого элемента массива от начала этого массива равно нулю
и это не только в программировании
например только родившийся ребенок
первый год жизни, но возраст 0 лет
Регистрация: 06.11.2011
Сообщений: 277
Croessmah,
А смещение адреса второго элемента,который называется «1», равно 1 ?
Неэпический
17849 / 10617 / 2049
Регистрация: 27.09.2012
Сообщений: 26,686
Записей в блоге: 1
Сообщение от sergey.shuvalov
1*размер элемента
8864 / 6641 / 907
Регистрация: 14.02.2011
Сообщений: 23,372
Сообщение от sergey.shuvalov
А смещение второго элемента,который называется «1», равно 1 ?
равно размеру элемента массива
например если массив из char то смешение равно 1 байту
если элемент массива строка из 100 char то будет 100 байт
в общем случае
i*sizeof(arr[0])
Регистрация: 06.11.2011
Сообщений: 277
Croessmah,
Размер типа элемента ?
Добавлено через 6 минут
А что нельзя написать компилятор, в котором прописать, что
номер массива равен смещению адреса + 1.
Это сложно ?
Неэпический
17849 / 10617 / 2049
Регистрация: 27.09.2012
Сообщений: 26,686
Записей в блоге: 1
Сообщение от sergey.shuvalov
Это сложно ?
Это нафиг никому не нужно, да и не удобно
8864 / 6641 / 907
Регистрация: 14.02.2011
Сообщений: 23,372
Сообщение от sergey.shuvalov
А что нельзя написать компилятор, в котором прописать, что
номер массива равен смещению адреса + 1.
Это сложно ?
А зачем ?
переучивать армию программистов? ломать логику ?
только потому что кто то не может понять?
этак и на линейке нужно будет выбросить 0 и начинать с 1 ( миллиметр? сантиметр? микрон?)
Неэпический
17849 / 10617 / 2049
Регистрация: 27.09.2012
Сообщений: 26,686
Записей в блоге: 1
Сообщение от sergey.shuvalov
А что нельзя написать компилятор
можно проще — написать свой класс и хоть с -3,45 начинайте
Регистрация: 06.11.2011
Сообщений: 277
Я понимаю для машинного кода 0, но для ООП языка первый элемент 0 — это не позорище ли ?
Единицу называть нулём, это всё равно что белое называть чёрным.
4267 / 2241 / 203
Регистрация: 26.08.2011
Сообщений: 3,802
Записей в блоге: 5
Сообщение от sergey.shuvalov
А что нельзя написать компилятор, в котором прописать, что
номер массива равен смещению адреса + 1.
ну и получите язык типа Паскаль, в котором более жесткий синтаксис, нежели в Си, например, и вымрет такой язык через некоторе время, как Паскаль
Добавлено через 3 минуты
Сообщение от sergey.shuvalov
для ООП языка первый элемент 0 — это не позорище ли ?
позорище не 0 в ООП, а программист, который не может понять почему 0, и быстро адаптироваться к этому
Почетный модератор
5850 / 2861 / 392
Регистрация: 01.11.2011
Сообщений: 6,907
Сообщение от sergey.shuvalov
А что нельзя написать компилятор
Попробуйте скооперироваться с GSSR.
Сообщение от ValeryS
переучивать армию программистов?
Скажите это армии дельфийских программистов.
А вообще это все
Регистрация: 06.11.2011
Сообщений: 277
Thinker,
Адаптироваться к тому, что 1=0 ?
8864 / 6641 / 907
Регистрация: 14.02.2011
Сообщений: 23,372
Сообщение от sergey.shuvalov
Я понимаю для машинного кода 0, но для ООП языка первый элемент 0 — это не позорище ли ?
а не позорище ли
первый сантиметр смешение 0?
первый год жизни смешение 0?
Все менять будем?
Пойми
первый элемент лежит по смешению 0 второй по смещению 1 и т.д.
а в скобках указывается смешение от начала
4267 / 2241 / 203
Регистрация: 26.08.2011
Сообщений: 3,802
Записей в блоге: 5
Сообщение от sergey.shuvalov
Thinker,
Адаптироваться к тому, что 1=0 ?
1 != 0, вы что-то знаете о частично упорядоченных множествах? а теорему Цермело? пронумеровать можно не только натуральными числами, а чем угодно. просто надо осознать почему с 0: для более гибкой и быстрой работы компилятора и программы.
Сообщение от SatanaXIII
Скажите это армии дельфийских программистов.
В смысле?
Делфи и Си будем совмещать ?
Меню пользователя ValeryS |
4267 / 2241 / 203
Регистрация: 26.08.2011
Сообщений: 3,802
Записей в блоге: 5
Сообщение от ValeryS
В смысле?
Делфи и Си будем совмещать
и получим ДЕЛСИ:D, который и дня не проживет
@ SatanaXIII
ValeryS, знаете почему математики с недоверием глядят на программистов? Потому что программисты знают, что x=x+1.
Сообщение от Thinker
и получим ДЕЛСИ, который и дня не проживет
«Убейте меня. » — чудовище такое. 😀
Меню пользователя @ SatanaXIII |
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
Почему индекс массива является недопустимым для данного массива
помогите разобраться пожалуйста выдает ошибку: индекс массива является недопустимым для данного.
Почему нумерация массива начинается с единицы при открытии файла с MultiSelect:=True?
Открываем файл через Application.GetOpenFilename с MultiSelect:=True, что должно создать нам массив.
Почему индекс находится вне массива?
В общем есть проблемка у меня — индекс находится вне массива (21 строка). Может здесь кто-нибудь.
Почему индекс находится вне границ массива?
Вот есть код, который определяется массив, затем он определяет его длину по числу введенных.
Основы индексации и срезов в Python
Все сталкиваются с индексами на самых ранних стадиях освоения языка, как правило, при изучении списков. Вероятно, вы и так знаете, что индексация в Python начинается с нуля. У нас есть список movies , тогда операция movies[0] вернёт первый элемент списка.
Да, для новичков считать от нуля до девяти при работе со списком из десяти элементов поначалу кажется немного странным. Python в этом не уникален — в большинстве языков программирования реализован такой же подход (C, C++, Java, C# и JavaScript).
Стоит обсудить не то, чем индексация в Python похожа на другие языки, а чем от них отличается. Например:
- Она почти никогда не используется в циклах. Да, мы можем перебирать индексы элементов объекта в цикле for вместо перебора собственно элементов, но это не норма.
- Можно использовать отрицательные индексы, они начинаются с -1. -1 возвращает последний элемент, -2 возвращает предпоследний и так далее.
- Для извлечения сразу нескольких элементов можно использовать расширенную форму индексации — срезы. Используя срезы в сочетании с отрицательными индексами можно, например, развернуть последовательность. Также можно указывать шаг среза для составления гибких правил извлечения нужных элементов.
Если вы новичок в Python, и вам пока не знакомы эти концепции, то в этой статье мы как раз рассмотрим несколько практических примеров.
Простая прямая индексация
Давайте начнём с нескольких простых примеров прямой индексации, используя список, кортеж и строку. Как показано ниже, индекс — это число, заключённое в квадратные скобки, которое мы ставим после составного объекта.
numbers = [42, 1941, 1066, 1969] indexes = "Всё очень просто!" names = ("Moran", "Székely", "Carlin") print(numbers[0]) last_index = len(indexes) - 1 print(indexes[last_index]) print(f"Самым смешным комиком оказался .")
Вывод:
42 ! Самым смешным комиком оказался Carlin.
Опять же, во всех случаях индекс первого элемента равен нулю, а последнего —длина объекта минус единица. Использование индекса за пределами этого диапазона приведёт к тому, что Python выдаст ошибку IndexOutOfBounds .
А теперь давайте обсудим нюансы индексации, которые специфичны именно для Python.
Если мы работаем с изменяемыми типами данных (те же списки), то индексы могут быть использованы не только для извлечения значений, но и для присваивания (замены элементов изменяемого объекта).
numbers = [1, 2, 8, 4] print(numbers) # Изменяем третий элемент списка numbers[2] = 3 print(numbers)
Вывод:
[1, 2, 8, 4] [1, 2, 3, 4]
Обратная индексация в Python
Обратная индексация в Python предполагает доступ к элементам при помощи отрицательных чисел. Она начинается с конца объекта и идёт в обратном порядке. То есть, мы можем получить последний элемент при помощи индекса -1. Доступ к предпоследнему элементу можно получить с помощью -2 и так далее.
Использование отрицательных индексов может быть полезно при работе со списками вариативной длины. Так удобно получать доступ к элементам из конца списка, не зная заранее длину списка.
Давайте возьмём последний символ из строки Zen of Python, используя прямую и обратную индексацию:
saying = "Simple is better than complex" # получаем последний элемент прямой индексацией print(saying[len(saying) - 1]) # используем обратную print(saying[-1])
Вывод:
Обратная индексация также может быть удобна в рамках среза для изменения порядка элементов объекта.
Работа с индексами в цикле for
Как мы упоминали выше, в общем случае индексы не используются в циклах Python хотя в некоторых языках без индексов цикл просто не написать. Вот, например, как это может выглядеть на C:
#include int main(void) < const int LEN = 3; char chars[LEN] = ; for(int i = 0; i < LEN; i++) < printf("Найден символ по индексу %d: %c\n", i, chars[i]); >>
Вывод:
Найден символ по индексу 0: A Найден символ по индексу 1: B Найден символ по индексу 2: C
В Python, конечно, можно выполнять итерации по списку гораздо проще:
chars = ["A", "B", "C"] for char_ in chars: print(char_)
Вывод:
A B C
Так всегда и нужно писать за исключением редких ситуаций, когда нам напрямую нужно оперировать с индексами в рамках логики какого-то алгоритма. Тут поможет функция enumerate , которая позволяет получить и индекс, и значение одновременно. Вот как мы можем получить тот же результат, что и в коде C:
chars = ["A", "B", "C"] for index, char in enumerate(chars): print(f"Найден символ по индексу : ")
Вывод:
Найден символ по индексу 0: A Найден символ по индексу 1: B Найден символ по индексу 2: C
Срезы Python: индексы на стероидах
Срезы — главное, что отличает функционал индексов в Python от многих других языков. Если индекс позволяет нам извлекать один элемент, то срезы позволяют нам извлекать (или присваивать) сразу несколько элементов. Как и в случае с индексами, выражение помещается после имени объекта в квадратные скобки и имеет следующий базовый синтаксис:
- Значение start — это целое число, которое является началом (левой границей) среза. Если его не ставить, то по умолчанию равен нулю, то есть началу последовательности.
- Значение stop — это целое число, представляющее собой конец среза (его правую границу). Очень важно помнить, что правая граница предполагает нужный вам последний индекс + 1. То есть правая граница сама по себе в результат не входит. Если её не ставить, то по умолчанию используется длина объекта («до самого конца»).
- Значение step — целое число, шаг среза, по умолчанию равен 1. Шаг среза последовательно прибавляется к каждому индексу от левой границы до правой, результирующие элементы будут в выборке. Т.е. если шаг равен 1, то берётся каждый элемент, если 2 — через один. А если шаг равен -1, то элементы выбираются справа налево.
Давайте посмотрим на это наглядно, начав со срезов с прямой индексацией:
numbers = [1, 2, 3, 4, 5] # срезы от нуля до двух, с явным или неявным началом среза print("Индексы от нуля до двух") print(numbers[0:3]) print(numbers[:3]) # вариант аналогичный предыдущему # Индексы от 3 до конца списка print("\nИндексы от 3 до конца списка") print(numbers[3:len(numbers)]) print(numbers[3:]) # Делаем неглубокую копию списка print("\nКопия списка") print(numbers[:]) # Получем все элементы через 1 print("\nС шагом 2:") print(numbers[::2])
Вывод:
Индексы от нуля до двух [1, 2, 3] [1, 2, 3] Индексы от 3 до конца списка [4, 5] [4, 5] Копия списка List copy: [1, 2, 3, 4, 5] С шагом 2: Step by two: [1, 3, 5]
Срезы могут быть удобными, например, для удаления фиксированного префикса из строк:
# Удаляем "id-" из строк в списке: order_items = ["id-999", "id-19098", "id-2"] cleaned = [item[3:] for item in order_items] print(cleaned)
Вывод:
['999', '19098', '2']
В срезах, конечно, можно использовать и обратную индексацию. Если задать шаг -1, то получим элементы в обратном порядке.
numbers = [1, 2, 3, 4, 5] print(numbers[::-1]) print(numbers[4:2:-1])
Вывод:
[5, 4, 3, 2, 1] [5, 4]
Отрицательный шаг в срезах в реальной практике используется нечасто, но на собеседованиях вы вполне можете натолкнуться на вопрос о том, как развернуть строку. Это можно сделать и при помощи цикла, но не такого идеального ответа от вас ожидают 🙂
Срезы для присваивания
Присвоение по срезу заменяет часть составного объекта содержимым другого составного объекта. Количество добавляемых элементов не обязательно должно соответствовать количеству элементов в срезе, т.к. список без проблем увеличиться или уменьшиться, если их будет больше или меньше.
count_to_ten = [num for num in range(1,11)] count_to_ten[3:6] = [20, 30] count_to_ten count_to_ten[6:8] = [100, 200, 300, 400] print(count_to_ten)
Вывод:
[1, 2, 3, 20, 30, 7, 100, 200, 300, 400, 10]
Напоминаем, что для строк и других неизменяемых типов данных присваивание по индексу/срезу работать не будет.
Задачки по индексации и срезам
Вот несколько упражнений, которые помогут вам закрепить эту тему.
1. Что мы получим в результате запуска приведённого ниже кода? Сможете ли вы заменить все строки, кроме импорта, на один print , который выведет аналогичную строку?
from string import ascii_uppercase subset = "" for idx, letter in enumerate(ascii_uppercase): if idx % 4 == 0: subset = subset + letter print(subset)
2. Используя ascii_uppercase , выведите алфавит в обратном порядке при помощи среза.
3. Находим иголку в стоге сена. При помощи срезов, метода index и функции len выведите строку «иголка», где бы она ни располагалась в example .
example = "сено сено сено иголка сено сено сено, привет, привет, пока."
4. При помощи среза из приведённого ниже списка выведите такой результат: [9, 6, 3]
count_to_ten = [num for num in range(1,11)] print(count_to_ten)
5. Из имеющегося списка при помощи индексов выведите на экран только слово «клубнику».
tokens = "Тут хоть где-нибудь можно купить клубнику?".split(" ") print(tokens)
6. Как думаете, что мы увидим в результате вызова claim.index(«Python») ?
claim = "В том материалы вы узнали про индексы и срезы в Python".split() print(claim)
7. Что увидим на экране в качестве вывода?
greeting = "Hello" print(greeting[4]) print(greeting[5])