На этой неделе мы работаем с вложенными структурами данных. В основном все инструкции и операции, которые мы будем использовать, нам уже знакомы. Основная сложность будет в том, чтобы применять их к каким-то новым, сложным, составным элементам. Начнем мы со списка списков. Как мы с вами уже говорили, элементом списка может быть что угодно, то есть любой (изменяемый, не изменяемый) тип данных: неважно. Все может быть внутри списка, и достаточно популярная структура — это список списков. Представьте, что вот мой список "students", и мы храним в нем информацию о студентах. Информации о студентах много: например, мы хотим хранить: имя студента, год рождения и образовательную программу, на которой этот студент или студентка учится. Логично, что каждого студента описывать списком, в котором хранится три элемента: имя, год, программа. Студентов у нас может быть много. Пока двое: Ольга и Вениамин. Давайте посмотрим, что в таком сложном списке будет первым элементом. Обратимся к нему по индексу: "students ноль". Итак: я обратилась к нулевому элементу и получила вот этот список — логично, ведь если мы проверим длину нашего списка, мы увидим, что в нем два элемента, то есть наш список состоит из списков. Мы не считаем здесь каждый элемент, строку или целое число, как отдельный элемент списка: они уже внутри вложенных списков. Если я, например, посмотрю длину элемента "students ноль", то я увижу там три элемента. Действительно: строка, целое число, строка; а в основном списке элементов два. Давайте проверим, обратимся ко второму студенту. Тут все логично. Индексация: нулевой, первый — Вениамин. На самом деле тут интересный момент: мы можем использовать так называемую двойную, тройную и так далее — индексацию. Например, я хочу получить имя моего второго студента. Соответственно, я обращаюсь сначала к списку, который его описывает, и в этом списке мне нужен нулевой элемент — ведь я вижу, что строка с именем хранится под нулевым индексом. Соответственно, сначала обратились к списку, а внутри этого списка обратились к имени. Можем пойти даже еще глубже в этом примере, строка ведь тоже поддерживает индексацию: давайте достанем первую букву его имени. Скопирую все, еще раз. Обращаемся ко второму списку (к вот этому), во втором списке обращаемся к имени, и теперь в имени я хочу еще обратиться к первой букве — "в". Таким образом, все эти вложенные структуры поддерживают многоуровневую индексацию. Иногда это очень удобно, и этим можно пользоваться. Давайте в наш список добавим нового студента. Как нам это сделать? Итак: элементом у нас является список, соответственно, я сделаю "students.append", только я добавляю не строку или число, а я добавляю список, в котором лежат данные, описывающие мою новую студентку, печатаю мой список — и вот теперь студентов у меня трое: Ольга, Вениамин и Евгения. Естественно, мы можем считывать списки внутрь списка с помощью функции "input", разбивая ее по разделителям. Давайте это сделаем. Я назову это "new student" (чтобы не запутаться, сначала в строку сохраним), "input". Давайте элементы списка будут разделиться запятой — вернее, то, что нам нужно будет сделать отдельными элементами списка. Давайте это сделаю в новой ячейке, чтобы несколько раз не вводить, и вот у меня такая студентка, Софья, запускаю и ввожу. Но: сейчас будет неправильно, потому что я забыла поставить разделитель, и у меня будет разделение по пробелам — давайте это исправим. В split добавляю запятую и буду печатать сразу список, чтобы проверить, что я правильно все ввела или нет. У меня получился список, описывающий новую студентку. Перед тем, как добавлять в мой большой список, я еще переведу ее год рождения в целое число, ведь в остальных данных у меня две строки и одно целое число. New student, обращаюсь к году рождения (это у меня второй элемент для Python первый) и его же перезаписываю целым числом. Готово! Теперь в мой список добавлю новую студентку, и выведем его. Теперь их четверо. Вложенный список, в котором внутри четыре списка; каждый список описывает отдельного студента: они все однотипные. И кстати, это ситуация, с которой вы чаще всего будете сталкиваться. Конечно, вы можете хранить все что угодно: можете даже хранить список, целые числа, строки — все вперемешку, но обычно (вы уже наверное поняли, что в программировании мы работаем с какими-то структурированными данными, потому что у нас задачи такие, что нужно применять какие-то одни и те же операции) и скорее всего у вас будет список списков — одинаковая структура. Например, у меня каждый список описывает студента по определенному шаблону. Естественно, эти все операции могли сразу вот это с помощью метода "append" добавить в список студентов, но тут я боялась ошибиться на вводе и не хотела перезаписывать переменную, поэтому сделала поэтапно. Естественно, мы можем просто сделать "students.append" и это передать как аргумент. Потом в том же объекте не забыть перезаписать строку "число" на число формата "целое число". Давайте теперь попробуем такую сложную структуру перебрать в цикле: посмотрим, что будет. "For student in students" — давайте будем печатать список, с которым мы работаем, чтобы убедиться, что все хорошо. Итак: логично цикл "for" перебирает элементы основной последовательности или коллекции, которую мы сюда передаем; и у нас логично в переменную student на каждом шаге цикла попадает список — например, список, который описывает Ольгу, Вениамина и так далее. Теперь давайте прямо внутри цикла будем обращаться к каждому элементу: давайте будем печатать имя студента (я не буду здесь все красиво оформлять, но вы потом можете попробовать). Student, имя у меня под индексом ноль. Дальше я буду печатать (давайте я удалю комментарии, будет очень длинный код), дальше я буду печатать возраст студента, и его я буду вычислять, потому что у меня есть год рождения, и мне надо из текущего года вычесть второй элемент нашего списка. Будем, давайте, печатать программу обучения. Это третий элемент, с точки зрения Python второй. Здесь я буду печатать такую техническую строку — я напечатаю отбивку из 10 дефисов, чтобы нам с вами было просто виднее, где в цикле мы закончили работу с одним списком вернулись в начало цикла, в переменную попал новый список, и мы теперь работаем с ним. Давайте сначала я напишу здесь "break", чтобы мы убедились, что все работает на одном элементе, только для Ольги. Цикл остановит свое выполнение после одного прохода. Итак: в переменную "student" у меня попал этот список, вывели; дальше напечатали имя студента, возраст и программу обучения, и нашу техническую отбивку что, с этим студентом мы закончили работать. Вроде все корректно. Убираю "break", запускаю для всех. И вот мы смотрим: например, дальше в цикл пришел вот этот список, мы теперь обратились к его элементам по индексу, то есть у нас теперь нет двойной индексации. Почему? Потому что у нас список полностью приходит в переменную "student", и уже в этом списке мы работаем только с ним изолированно, обращаемся к его элементам по индексу — только к тем, к которым нам нужно. То есть мы могли бы, например, не печатать программу, если нам это неинтересно. И давайте теперь сделаем еще одну вещь: на самом деле мы можем использовать вложенный цикл for и в каждом списке перебрать его элементы, не обращаться к ним по индексу. Например, для каждого студента мы хотим напечатать всю информацию про него. Давайте это сделаем. Опять в цикле for перебираем списки нашего большого списка "in students" — будем печатать такую строчку, что мы печатаем информацию о студенте, чтоб было посимпатичней (оно у меня заготовлено). Теперь в цикле для каждого студента, давайте "item" это назовем, "for item in student" напечатаем этот элемент, а потом опять напечатаем отбивку. Теперь я не буду писать рост, возраст, имя, потому что я не обрабатываю каждый элемент отдельно, я просто их всех как-то унифицировано вывожу. И вот здесь, давайте, буду печатать опять отбивку, чтоб было видно, что с одним студентом мы закончили работать и перешли к другому. Вот все получилось. Сначала мы перебираем, в переменную "student" у меня попадает весь список (дайте сюда я ее добавлю, чтобы мы точно увидели, как это работает). Соответственно, в переменную "student" у меня попадает список, и потом уже в этом списке "student" я перебираю элементы и печатаю их, каждый с новой строки. Как только элементы в этом списке, во вложенном, закончились, я печатаю отбивочку, чтобы мне было понятно: ага, с этим студентом мы закончили работать, вернулись в начало нашего главного цикла, получили новый список следующего студента и теперь работаем с ним; то есть вложенные циклы. И последнее, что мы здесь еще посмотрим — то, что мы на самом деле можем спокойно, конечно же, перебирать при необходимости объекты в цикле "for i in range" (не в цикле, а в конструкции — корректнее сказать). И снова, например, важно сохранить нумерацию студентов. Запускаю цикл в интервале длины студентов: здесь буду печатать, о каком студентом мы печатаем информацию. Теперь "for item in". Смотрите, а теперь у меня возвращается индексация, потому что теперь я элементы по индексам здесь перебираю, в моем главном списке, "print item", и печатаю отбивку — 10 дефисов. Опять же, это просто техническое для нас, чтобы мы видели, как оно работает. Отбивку я напечатала не там, потому что я напечатала ее нечаянно после каждого элемента каждого списка, а хотела после каждого студента, сдвигая ее на один отступ влево. Запускаю заново: вот теперь все получилось. У нас теперь есть нумерация наших студентов. Обратите внимание: ее я откорректировала на единицу, чтобы было нам, людям, понятно, а не только компьютерам. Дальше: теперь у меня мой каждый список, маленький, он не пришел в переменную "student", но я к нему обратилась по индексу в моем главном списке и перебрала точно так же элементы, как в примере выше. Напечатала их — закончила работу со студентом. Перешла дальше: теперь у меня здесь индекс один, напечатали двоечку, потому что откорректировали; и теперь для Вениамина перебрали всю информацию и напечатали ее. Мы с вами посмотрели, как работать с вложенными списками — то, что мы можем добавлять туда элементы, просто мы теперь добавляем целый список, а не какую-то отдельную строку или число; и посмотрели, как с помощью циклов "for", простого и вложенного перебирать элементы внутри таких конструкций.