Основы программирования
Массивы (списки)
**Загадка:** У тебя 1000 оценок студентов. Создавать 1000 переменных? А как потом найти среднюю оценку? Или топ-10? Массивы (списки) - ответ на все эти вопросы.
Список (list) - это коллекция элементов под одним именем. Вместо `grade1, grade2, grade3...` пишешь `grades = [85, 90, 78, ...]` - и работаешь со всеми сразу.
Цели урока
- Понять что такое список и как его создать
- Освоить индексацию, срезы, добавление и удаление
- Научиться основным методам и операциям
- Познакомиться с list comprehensions
Предварительные знания
- Переменные и типы (урок 3)
- Циклы (урок 7)
- Функции (урок 8)
Все данные в реальных приложениях - это коллекции. Лента Instagram - список постов. Плейлист Spotify - список треков. Корзина - список товаров.
- **Соцсети**: список друзей, постов, комментариев
- **E-commerce**: товары в корзине, история заказов
- **Игры**: инвентарь, рекорды, уровни
- **ML**: датасеты - это списки данных
От перфокарт Холлерита и FORTRAN до APL, list Python и NumPy ndarray
Идея однородного контейнера данных старше самих компьютеров. В 1890 году Герман Холлерит использовал перфокарты для переписи населения США, и каждая карта была фактически вектором фиксированной длины. В 1957 году Джон Бэкус в IBM выпустил FORTRAN, первый язык с настоящим массивом как первоклассной конструкцией: DIMENSION A(100). Массивы там были числовые, многомерные, с быстрым доступом по индексу. В 1962 году Кеннет Айверсон опубликовал книгу A Programming Language, а к 1966 году в IBM реализовали APL. Он был полностью построен вокруг массивов: одна операция применялась сразу ко всему массиву. Этот стиль позже повлиял на MATLAB, R, и через них на NumPy. C от Ритчи (1972) сделал массивы по сути указателями на смежную память, отсюда и совместимость C и Fortran по бинарному layout. Python с 1991 года использует list: это не массив, а динамический вектор указателей на объекты, поэтому в нём можно держать смешанные типы и менять размер. Для научных вычислений этого было мало. В 1995 году Джим Хугунин выпустил Numeric (предшественник NumPy), в 2001 появился numarray. В 2006 году Трэвис Оливант объединил оба проекта в NumPy 1.0, и его ndarray стал стандартом для ML и data science. Современный Python 3.12 принёс PEP 646 (variadic generics для типизированных массивов), а в 3.13 (октябрь 2024) free-threaded режим позволяет NumPy работать с массивами параллельно без GIL.
Создание и основы
**Список (list)** - упорядоченная изменяемая коллекция элементов. Элементы могут быть любого типа.
Создание списков
Разные способы
```python # Литерал - квадратные скобки numbers = [1, 2, 3, 4, 5] names = ["Алиса", "Боб", "Вика"] mixed = [1, "hello", True, 3.14] # Разные типы - можно! # Пустой список empty = [] empty2 = list() # Из строки chars = list("hello") # ['h', 'e', 'l', 'l', 'o'] # Из range nums = list(range(5)) # [0, 1, 2, 3, 4] ```
Основные свойства
Длина, проверки
```python fruits = ["яблоко", "банан", "вишня"] print(len(fruits)) # 3 - длина списка print("банан" in fruits) # True - есть ли элемент? print("груша" in fruits) # False print(fruits) # ['яблоко', 'банан', 'вишня'] ```
**list vs array:** В Python список (list) - универсальная коллекция. Настоящие массивы (array) есть в NumPy для числовых вычислений. Для большинства задач list достаточно.
Что выведет len([1, [2, 3], 4])?
Индексация и срезы
Индексация списков работает так же, как у строк. Индексы начинаются с 0!
Индексация
Доступ к элементам
```python colors = ["red", "green", "blue", "yellow"] # 0 1 2 3 # -4 -3 -2 -1 print(colors[0]) # "red" - первый print(colors[2]) # "blue" - третий print(colors[-1]) # "yellow" - последний print(colors[-2]) # "blue" - предпоследний # Изменение элемента colors[0] = "pink" print(colors) # ['pink', 'green', 'blue', 'yellow'] ```
Срезы
Извлечение части списка
```python nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] print(nums[2:5]) # [2, 3, 4] - с индекса 2 до 5 (не включая) print(nums[:3]) # [0, 1, 2] - первые 3 print(nums[-3:]) # [7, 8, 9] - последние 3 print(nums[::2]) # [0, 2, 4, 6, 8] - каждый второй print(nums[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] - реверс # Срезы возвращают НОВЫЙ список! first_half = nums[:5] print(first_half) # [0, 1, 2, 3, 4] ```
Что выведет: [1,2,3,4,5][1:4]?
Методы списков
Списки имеют множество методов для модификации. В отличие от строк, списки **изменяемы** (mutable).
| Метод | Описание | Пример |
|---|---|---|
| .append(x) | Добавить в конец | [1,2].append(3) → [1,2,3] |
| .insert(i, x) | Вставить на позицию i | [1,3].insert(1, 2) → [1,2,3] |
| .extend(list) | Добавить все элементы | [1].extend([2,3]) → [1,2,3] |
| .pop() | Удалить последний | [1,2,3].pop() → 3, list=[1,2] |
| .pop(i) | Удалить по индексу | [1,2,3].pop(0) → 1 |
| .remove(x) | Удалить первое x | [1,2,1].remove(1) → [2,1] |
| .sort() | Сортировать на месте | [3,1,2].sort() → [1,2,3] |
| .reverse() | Перевернуть на месте | [1,2,3].reverse() → [3,2,1] |
| .index(x) | Найти индекс x | ["a","b"].index("b") → 1 |
| .count(x) | Посчитать x | [1,1,2].count(1) → 2 |
Методы в действии
Модификация списка
```python tasks = ["проснуться", "завтрак"] # Добавление tasks.append("работа") print(tasks) # ['проснуться', 'завтрак', 'работа'] tasks.insert(1, "душ") # Вставка на позицию 1 print(tasks) # ['проснуться', 'душ', 'завтрак', 'работа'] # Удаление done = tasks.pop() # Удаляет и возвращает последний print(done) # 'работа' print(tasks) # ['проснуться', 'душ', 'завтрак'] # Сортировка nums = [3, 1, 4, 1, 5] nums.sort() print(nums) # [1, 1, 3, 4, 5] ```
**Методы изменяют сам список!** `.sort()` сортирует на месте и возвращает None. Если нужна копия: `sorted_copy = sorted(nums)`.
x = x.append(4) сохранит список с добавленным элементом
x.append(4) изменяет x на месте. Присваивание даст None.
append возвращает None, не новый список. Правильно: просто x.append(4) без присваивания.
Что выведет? ```python x = [1, 2, 3] y = x.append(4) print(y) ```
Перебор списков
for - идеальный инструмент для работы со списками. Несколько способов перебора.
Способы перебора
Разные паттерны
```python fruits = ["яблоко", "банан", "вишня"] # Простой перебор for fruit in fruits: print(fruit) # С индексом (enumerate) for i, fruit in enumerate(fruits): print(f"{i}: {fruit}") # По индексу (редко нужно) for i in range(len(fruits)): print(fruits[i]) # Два списка параллельно (zip) names = ["Алиса", "Боб"] ages = [25, 30] for name, age in zip(names, ages): print(f"{name}: {age} лет") ```
Фильтрация и преобразование
Создание новых списков
```python numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # Фильтрация: только чётные evens = [] for n in numbers: if n % 2 == 0: evens.append(n) print(evens) # [2, 4, 6, 8, 10] # Преобразование: квадраты squares = [] for n in numbers: squares.append(n ** 2) print(squares) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] ```
Что выведет? ```python for i, x in enumerate(['a', 'b'], start=1): print(i, x) ```
List Comprehensions
**List comprehension** - элегантный способ создания списков в одну строку. Pythonic way!
Синтаксис
Вместо цикла - одна строка
```python # Обычный способ squares = [] for x in range(5): squares.append(x ** 2) # List comprehension squares = [x ** 2 for x in range(5)] print(squares) # [0, 1, 4, 9, 16] # С условием (фильтрация) evens = [x for x in range(10) if x % 2 == 0] print(evens) # [0, 2, 4, 6, 8] # Преобразование строк names = ["alice", "bob", "charlie"] upper_names = [name.upper() for name in names] print(upper_names) # ['ALICE', 'BOB', 'CHARLIE'] ```
Сложные comprehensions
Условия и вложенность
```python # if-else в comprehension nums = [1, 2, 3, 4, 5] result = ["чётное" if x % 2 == 0 else "нечётное" for x in nums] print(result) # ['нечётное', 'чётное', 'нечётное', 'чётное', 'нечётное'] # Вложенный цикл (декартово произведение) colors = ["red", "blue"] sizes = ["S", "M"] combos = [f"{c}-{s}" for c in colors for s in sizes] print(combos) # ['red-S', 'red-M', 'blue-S', 'blue-M'] ```
**Читаемость важнее!** Если comprehension сложный и нечитаемый - лучше обычный цикл. Comprehensions - для простых преобразований.
Что выведет: [x*2 for x in [1,2,3] if x > 1]?
Функции для списков
Python предоставляет полезные встроенные функции для работы со списками.
| Функция | Описание | Пример |
|---|---|---|
| len() | Длина списка | len([1,2,3]) → 3 |
| sum() | Сумма элементов | sum([1,2,3]) → 6 |
| min() | Минимальный элемент | min([3,1,2]) → 1 |
| max() | Максимальный элемент | max([3,1,2]) → 3 |
| sorted() | Новый отсортированный | sorted([3,1,2]) → [1,2,3] |
| reversed() | Итератор в обратном | list(reversed([1,2])) → [2,1] |
| all() | Все True? | all([True, True]) → True |
| any() | Хотя бы один True? | any([False, True]) → True |
Практическое применение
Статистика оценок
```python grades = [85, 92, 78, 90, 88, 76, 95] print(f"Всего оценок: {len(grades)}") # 7 print(f"Сумма: {sum(grades)}") # 604 print(f"Средняя: {sum(grades)/len(grades):.1f}") # 86.3 print(f"Минимум: {min(grades)}") # 76 print(f"Максимум: {max(grades)}") # 95 # Все сдали? (>= 60) all_passed = all(g >= 60 for g in grades) print(f"Все сдали: {all_passed}") # True # Есть отличники? (>= 90) has_excellent = any(g >= 90 for g in grades) print(f"Есть отличники: {has_excellent}") # True ```
Что вернёт: all([1, 0, 2])?
Связь с другими темами
Списки опираются на циклы и функции, и ведут к словарям, тестам и алгоритмам:
- Циклы — for и list comprehension это главные способы обхода списка
- Функции — len, sum, sorted, map, filter работают с любым iterable
- Рекурсия — Список естественно разбивается на голову и хвост для рекурсии
- Объекты и словари — list часто содержит словари (JSON-данные), словарь часто содержит списки
- Тестирование — Списки удобны для assertEqual и data-driven тестов
Итог
- Идея однородного вектора началась с перфокарт Холлерита в 1890 году, FORTRAN (Бэкус, IBM, 1957) сделал массив первоклассной конструкцией языка
- APL Кеннета Айверсона (1962, IBM) построен полностью вокруг операций над массивами, идеи дошли через MATLAB и R до NumPy
- C (Ритчи, 1972) свёл массивы к указателям на смежную память, бинарный layout совпадает с Fortran до сих пор
- Python list это динамический вектор указателей на объекты с 1991 года, можно смешивать типы, но память не сплошная
- NumPy 1.0 (Трэвис Оливант, 2006) объединил Numeric (Хугунин, 1995) и numarray, ndarray стал стандартом для ML и научных вычислений
Связанные уроки
- prog-07-loops — Циклы это способ проходить по элементам массива
- prog-12-objects — Объекты хранят данные по ключу а массивы по порядку
- ds-01-arrays — Курс структур данных углубляет внутренности массивов
- la-01-vectors-intro — Вектор математически это индексированный массив чисел
- alg-04-basic-sorts — Алгоритмы сортировки переставляют элементы массива