# list: создание, доступ, добавление, удаление, поиск и перебор<no value>

## Что мы изучим?

**list** (список) — самая распространённая структура данных в Python. Мы уже коротко им пользовались. Сегодня изучим его как следует:

- Создание и доступ к элементам.
- Добавление, удаление и изменение.
- Поиск.
- Перебор и сортировку.

## Зачем это нужно?

Почти каждая программа хранит **коллекции**: список студентов, товары в корзине, сегодняшние заказы, строки из файла, результаты запроса к базе данных (темы бэкенда и баз данных). Когда количество заранее неизвестно, нельзя завести отдельную переменную для каждого элемента (`student1`, `student2`...). Список хранит **столько, сколько нужно**, в определённом порядке.

## Объяснение с основ

### Что такое список?

**Список** (list) — **упорядоченная** коллекция значений:

- **Упорядоченная** — у каждого элемента есть фиксированная **позиция**, **индекс**.
- **Изменяемая** (mutable) — после создания можно добавлять, удалять и менять элементы.
- **Допускает повторы** — одно и то же значение может встречаться несколько раз.
- **Любой тип** — можно смешивать (`[1, "a", True]`), но обычно список хранит однородные элементы.

{{< box "analogy" "Аналогия: поезд" >}}
Список — поезд с пронумерованными вагонами. Каждый вагон (элемент) везёт груз (значение). Первый вагон — **номер 0**, а не 1. Можно прицепить вагон в конце (`append`), поставить вагон в середину (`insert`) или отцепить вагон (`remove`/`pop`).
{{< /box >}}

### Индексы

```text
fruits = ["apple", "banana", "cherry", "date"]
index:       0         1         2        3
negative:   -4        -3        -2       -1
```

- Отсчёт начинается с **0**.
- **Отрицательный** индекс считает с конца: `-1` = последний, `-2` = предпоследний.
- Несуществующий индекс → `IndexError`.

## Синтаксис

```python
# create
empty = []
numbers = [10, 20, 30]

# access and change
numbers[0]          # 10 - first item
numbers[-1]         # 30 - last item
numbers[1] = 25     # change an item
len(numbers)        # 3 - how many items

# add
numbers.append(40)       # add to the end
numbers.insert(0, 5)     # insert at index 0, pushing the rest right

# remove
numbers.remove(25)       # remove the FIRST item equal to 25
last = numbers.pop()     # remove and return the last item
first = numbers.pop(0)   # remove and return the item at index 0
del numbers[0]           # delete by index (returns nothing)
numbers.clear()          # remove everything

# search
30 in numbers            # True / False
numbers.index(30)        # index of the first 30 (ValueError if missing)
numbers.count(30)        # how many times 30 appears

# sort
numbers.sort()               # sorts THIS list in place
numbers.sort(reverse=True)   # descending
sorted(numbers)              # returns a NEW sorted list, original unchanged
```

**Метод** — функция, которая «принадлежит» объекту и вызывается через точку: `numbers.append(40)`. Подробно о методах — в теме ООП. Пока: «действие, которое список умеет делать сам над собой».

## Первый пример кода

`week-2/shopping_list.py`:

```python
shopping = ["milk", "bread", "eggs"]
print("Start:", shopping)

shopping.append("cheese")
print("After append:", shopping)

shopping.insert(1, "butter")
print("After insert:", shopping)

shopping.remove("bread")
print("After remove:", shopping)

print("First item:", shopping[0])
print("Last item:", shopping[-1])
print("Items count:", len(shopping))
```

**Вывод:**

```text
Start: ['milk', 'bread', 'eggs']
After append: ['milk', 'bread', 'eggs', 'cheese']
After insert: ['milk', 'butter', 'bread', 'eggs', 'cheese']
After remove: ['milk', 'butter', 'eggs', 'cheese']
First item: milk
Last item: cheese
Items count: 4
```

## Разбор кода построчно

| Строка | Что произошло со списком |
|---|---|
| `shopping = [...]` | Создан с 3 элементами: индексы 0, 1, 2. |
| `.append("cheese")` | Добавлен **в конец**. Теперь 4 элемента. |
| `.insert(1, "butter")` | Вставлен **на индекс 1**. То, что там было (`bread`), и всё после него сдвинулось вправо. |
| `.remove("bread")` | Удалён **по значению**: Python ищет первое вхождение `"bread"` и удаляет его. |
| `shopping[0]` | Первый элемент. |
| `shopping[-1]` | Последний элемент — без знания, сколько их. |
| `len(shopping)` | Количество элементов. |

**Обратите внимание:** `append`, `insert` и `remove` **меняют сам список** и не возвращают новый список. Поэтому не пишут `shopping = shopping.append(...)` (подробнее — в разделе ошибок).

## Ещё один пример

**Управление очередью на кассе**: перебор, поиск, `pop` и `enumerate`. `week-2/queue.py`:

```python
queue = ["dana", "avi", "noa", "avi", "yossi"]

print("People in queue:", len(queue))
print("Is noa waiting?", "noa" in queue)
print("noa's position:", queue.index("noa") + 1)
print("How many times avi:", queue.count("avi"))

served = queue.pop(0)
print("Now serving:", served)

print("--- Remaining queue ---")
for position, name in enumerate(queue, start=1):
    print(position, name)

queue.sort()
print("Alphabetical:", queue)
```

**Вывод:**

```text
People in queue: 5
Is noa waiting? True
noa's position: 3
How many times avi: 2
Now serving: dana
--- Remaining queue ---
1 avi
2 noa
3 avi
4 yossi
Alphabetical: ['avi', 'avi', 'noa', 'yossi']
```

**Разбор:**

- `"noa" in queue` — `in` проходит по списку и проверяет, есть ли значение. Возвращает `bool`.
- `queue.index("noa") + 1` — `index` возвращает индекс (2). Добавляем 1, потому что для людей «третье место» считается с 1.
- `queue.pop(0)` — **извлекает и возвращает** первого. В этом отличие от `remove`: `pop` отдаёт извлечённое значение, и им можно воспользоваться (`served`).
- `enumerate(queue, start=1)` — на каждом круге даёт **пару**: порядковый номер и элемент. `for position, name in ...` распаковывает пару в две переменные. Без `start=1` счёт начинается с 0.
- `queue.sort()` — сортирует **сам** список: текст — по алфавиту, числа — по возрастанию.

## Типичная ошибка

**Ошибка 1: присвоить результат метода, который меняет список на месте.**

```python
names = ["noa", "avi"]
names = names.append("dana")
print(names)
```

```text
None
```

`append` меняет список и **возвращает `None`**. Мы присвоили `None` в `names` и потеряли список! Исправление: просто `names.append("dana")`. То же самое для `sort()`, `insert()`, `remove()`.

**Ошибка 2: ошибка на единицу (off-by-one) на последнем индексе.**

```python
items = ["a", "b", "c"]
print(items[len(items)])
```

```text
IndexError: list index out of range
```

`len` равно 3, но последний индекс — **2**. Последний = `items[-1]` или `items[len(items) - 1]`.

**Ошибка 3: `remove` для несуществующего значения.**

```python
items.remove("z")
```

```text
ValueError: list.remove(x): x not in list
```

Сначала проверьте: `if "z" in items: items.remove("z")`.

**Ошибка 4: удалять из списка во время его перебора.**

```python
numbers = [1, 2, 2, 3]
for n in numbers:
    if n == 2:
        numbers.remove(n)
print(numbers)
```

```text
[1, 2, 3]
```

Мы ожидали `[1, 3]`. Когда удаляется элемент, все элементы сдвигаются влево, и цикл «перепрыгивает» следующий элемент. Безопасное решение: **построить новый список** из того, что нужно оставить:

```python
numbers = [1, 2, 2, 3]
kept = []
for n in numbers:
    if n != 2:
        kept.append(n)
print(kept)   # [1, 3]
```

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## Практика

Упражнения опираются только на то, что пройдено в этом уроке и в предыдущих. Сначала попробуйте сами, и только потом открывайте решение.

### Упражнение 1: Индексы

```python
days = ["Sun", "Mon", "Tue", "Wed", "Thu"]
```

Напечатайте, используя только индексы: первый день, последний день (с отрицательным индексом) и средний день.

**Ожидаемый вывод:**

```text
Sun
Thu
Tue
```

{{< solution >}}
```python
days = ["Sun", "Mon", "Tue", "Wed", "Thu"]

print(days[0])
print(days[-1])
print(days[2])
```

```text
Sun
Thu
Tue
```

Индексы начинаются с 0. Отрицательный индекс считает с конца: `-1` — последний элемент.
{{< /solution >}}

### Упражнение 2: Список покупок

Начните с `["milk", "bread"]` и выполните по порядку:

1. Добавьте `"eggs"` в конец.
2. Добавьте `"butter"` на первое место.
3. Удалите `"bread"`.
4. Напечатайте список и количество элементов.

**Ожидаемый вывод:**

```text
['butter', 'milk', 'eggs']
3
```

{{< solution >}}
```python
shopping = ["milk", "bread"]

shopping.append("eggs")
shopping.insert(0, "butter")
shopping.remove("bread")

print(shopping)
print(len(shopping))
```

```text
['butter', 'milk', 'eggs']
3
```

- `append` — в конец. `insert(0, ...)` — на определённую позицию.
- `remove` — удаляет **по значению** (первое вхождение).
{{< /solution >}}

### Упражнение 3: Очередь

```python
queue = ["dana", "avi", "noa", "avi", "yossi"]
```

1. Проверьте, есть ли `"noa"` в очереди, и напечатайте, на каком она месте (первое место — 1).
2. Напечатайте, сколько раз встречается `"avi"`.
3. Выведите из очереди первого человека и напечатайте `Now serving: <name>`, а также количество оставшихся.

**Ожидаемый вывод:**

```text
True
3
2
Now serving: dana
4
```

{{< solution >}}
```python
queue = ["dana", "avi", "noa", "avi", "yossi"]

print("noa" in queue)
print(queue.index("noa") + 1)
print(queue.count("avi"))

served = queue.pop(0)
print("Now serving:", served)
print(len(queue))
```

```text
True
3
2
Now serving: dana
4
```

`pop(0)` и удаляет, и **возвращает** элемент — поэтому его можно сохранить в переменной.
{{< /solution >}}

### Упражнение 4: Статистика по оценкам

```python
grades = [78, 92, 55, 88, 100, 43, 67]
```

Напечатайте самую высокую оценку, самую низкую, среднее (округлённое до одного знака) и новый список сдавших оценок (55 и выше) — не меняя `grades`.

**Ожидаемый вывод:**

```text
Highest: 100
Lowest: 43
Average: 74.7
Passed: [78, 92, 55, 88, 100, 67]
```

{{< solution >}}
```python
PASSING_GRADE = 55

grades = [78, 92, 55, 88, 100, 43, 67]

print("Highest:", max(grades))
print("Lowest:", min(grades))
print("Average:", round(sum(grades) / len(grades), 1))

passed = []
for grade in grades:
    if grade >= PASSING_GRADE:
        passed.append(grade)
print("Passed:", passed)
```

```text
Highest: 100
Lowest: 43
Average: 74.7
Passed: [78, 92, 55, 88, 100, 67]
```

Шаблон «новый список + цикл + `append`» — способ отфильтровать список, не трогая оригинал.
{{< /solution >}}

### Упражнение 5: Удаление во время цикла

```python
numbers = [1, 2, 2, 3, 2, 4]
for n in numbers:
    if n == 2:
        numbers.remove(n)
print(numbers)
```

Задумано было удалить все двойки, но одна осталась. Напишите функцию `remove_all(items, value)`, которая возвращает новый список без единого вхождения `value`.

**Ожидаемый вывод:**

```text
[1, 3, 4]
```

{{< solution >}}
Когда вы удаляете из списка **во время** его перебора, элементы сдвигаются влево, и цикл пропускает элемент, который переместился на место удалённого. Поэтому вместо этого строят новый список:

```python
def remove_all(items, value):
    result = []
    for item in items:
        if item != value:
            result.append(item)
    return result


print(remove_all([1, 2, 2, 3, 2, 4], 2))
```

```text
[1, 3, 4]
```
{{< /solution >}}

<!-- exercises:end -->

## Итоги

- **list** = упорядоченная изменяемая коллекция с повторами. Записывается через `[ ]`.
- Индекс начинается с **0**. `-1` = последний. Несуществующий индекс → `IndexError`.
- Добавление: `append` (в конец), `insert` (на позицию). Удаление: `remove` (по значению), `pop` (по позиции, возвращает элемент), `del`.
- Поиск: `in`, `index`, `count`. Перебор: `for`, а `enumerate` — когда нужен ещё и номер.
- Методы, которые меняют на месте (`append`, `sort`...), возвращают **`None`**. `sorted()` возвращает **новый** список.
- Не удаляйте из списка во время его перебора. Стройте новый список.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## Официальная документация

- [Python Tutorial — Lists](https://docs.python.org/3/tutorial/introduction.html#lists)
- [Python Tutorial — More on Lists](https://docs.python.org/3/tutorial/datastructures.html#more-on-lists)
- [Built-in Types — Lists](https://docs.python.org/3/builtins/stdtypes.html#lists)

<!-- docs:end -->
