📦 Репозиторий занятия 40

Урок 40. Модуль collections

Как работать с репозиторием

Каждая карточка говорит, о чём файл, что он выводит и что в нём искать. Код виден прямо здесь: его можно скопировать одной кнопкой, скачать файл или открыть его целиком.

Маршрут изучения

  1. Прочитайте описание: по нему уже понятно, о чём файл и что он выведет.
  2. Предскажите вывод: сравните своё предположение со строкой «Что выводит».
  3. Запустите: скачайте файл или скопируйте код кнопкой и выполните его у себя.
  4. Измените: поменяйте одно условие или значение и объясните новый результат.

Файлы: рекомендуемый порядок

1
MarkdownРазбор концепции78 строк

Модуль time: работа с текущим временем и таймерами

less_21__time_collections_OrderedDict_Counter__lru_cache/theory_01__time.md

Обзор ключевых функций модуля time — time.time() (unix-таймстамп), time.sleep() (пауза выполнения), time.strftime()/time.strptime() (преобразование времени в строку и обратно по заданному формату), time.localtime() (структура локального времени) и time.perf_counter() как самый точный монотонный таймер для замеров производительности.

  • time.time() — секунды с начала эпохи (1970-01-01 UTC)
  • time.sleep(secs) — приостановка выполнения программы
  • time.strftime(format, t) — время → строка по заданному формату
  • time.strptime(string, format) — строка → структура времени
  • time.localtime([secs]) — секунды → struct_time локального времени
  • time.perf_counter() — точный монотонный таймер для измерения производительности
Показать начало файла (78 строк всего)
## Модуль `time`

Предоставляет функции для работы со временем: 
* получение текущего времени,
* паузы выполнения программы, 
* работу с таймерами 
* преобразование времени между различными форматами,
* и т.д. и т.п.

---

## **Основные функции**

1. **`time.time()`**

   * Возвращает текущее время в секундах с начала эпохи (обычно 1 января 1970 года, UTC).
   * Пример:

      ```python
     
      import time
      print(time.time())  # 1700000000.123456
      ```

2. **`time.sleep(secs)`**

   * Приостанавливает выполнение программы на указанное количество секунд.
   * Пример:

       ```python
       import time
       time.sleep(2)  # пауза на 2 секунды
       ```

3. **`time.strftime(format, t)`**

   * Преобразует структуру времени `t` в строку по заданному формату.
   * Пример:

       ```python
       import time
     
       t = time.localtime()
       print(time.strftime("%Y-%m-%d %H:%M:%S", t))  # '2025-10-19 14:23:01'
       ```
     
[Таблица перекодировки](https://docs.python.org/3.12/library/time.html#time.strftime)

4. **`time.strptime(string, format)`**

   * Преобразует строку времени в структуру времени по заданному формату.
   * Пример:

      ```python
      import time
     
      t = time.strptime("2025-10-19 14:23:01", "%Y-%m-%d %H:%M:%S")
      print(t)
      ```

…
Проверьте себя: Почему для измерения времени выполнения кода рекомендуют time.perf_counter(), а не time.time()?
2
MarkdownРазбор концепции148 строк

Модуль collections: OrderedDict, Counter, defaultdict, namedtuple

less_21__time_collections_OrderedDict_Counter__lru_cache/theory_02__collections.md

Обзор альтернативных структур данных стандартной библиотеки. OrderedDict — словарь с гарантированным порядком и методами move_to_end()/popitem(last=...); Counter — подсчёт элементов с most_common() и арифметикой над счётчиками (+, -, &, |); defaultdict — словарь с автоматическим значением по умолчанию через callable-фабрику, сравнивается с setdefault(); namedtuple — именованный кортеж с доступом к полям по имени.

  • OrderedDict — move_to_end(key, last=True), popitem(last=True/False)
  • Counter(iterable) — подсчёт частоты, most_common(n), elements()
  • Counter: арифметика +, -, & (min по ключу), | (max по ключу) между счётчиками
  • defaultdict(int)/defaultdict(list)/defaultdict(callable) — автосоздание значения при обращении
  • defaultdict vs setdefault() — когда срабатывает и что принимает как значение по умолчанию
  • namedtuple('Point', ['x', 'y']) — доступ к полям по имени p.x, p.y
Показать начало файла (148 строк всего)
# Модуль `collections`

Это стандартный модуль Python, который предоставляет альтернативные структуры данных  
к встроенным типам (list, dict, tuple, set), с дополнительной функциональностью.  
Он упрощает работу с данными и делает код более читаемым и эффективным.

## 1. `OrderedDict`
   
Словарь, который запоминает порядок добавления элементов (более корректно, чем обычный dict).  

Поддерживает все методы `dict`, плюс два дополнительных метода:

* `move_to_end(key, last=True)` — перемещает элемент в конец или начало,
* `popitem(last=True)` — удаляем пару из конца (`last=True`) или начала (`last=False`)

---

## 2. `Counter`

Counter — это подкласс словаря (`dict)`, специально предназначенный для подсчёта количества   
вхождений элементов в коллекции (например, в списке, строке или другом итерируемом объекте).

Каждый элемент хранится как ключ, а количество его вхождений — как значение.

`Counter` принимает итерированную последовательность и возвращает отсортированный по убыванию словарь,  
где 
- ключи - элементы итерированной последовательности
- значения - частота их повторений

```python
from collections import Counter

iterable_object = ['a','b','a','c','a','b']
c = Counter(iterable_object)
print(c)                 # Counter({'a': 3, 'b': 2, 'c': 1})
print(c.most_common(2))  # [('a', 3), ('b', 2)]
```

### Основные методы `Counter`

#### 1. `most_common([n])`

Возвращает список из `n` самых частых элементов и их счётчиков.
Если `n` не указано — возвращает все элементы, отсортированные по убыванию частоты.

```python
c = Counter('abracadabra')
print(c.most_common(3))
# [('a', 5), ('b', 2), ('r', 2)]
```
#### 2. `elements()`

Возвращает итератор по элементам, повторённым столько раз, сколько их количество.
Элементы возвращаются в порядке, не обязательно упорядоченном.

```python
c = Counter(a=3, b=2, c=1)
print(list(c.elements()))
# ['a', 'a', 'a', 'b', 'b', 'c']
```
…
Проверьте себя: Почему defaultdict(random_int) при каждом обращении к новому ключу может вернуть разное значение, а defaultdict(int) — всегда одно и то же (0)?
3
MarkdownРазбор концепции38 строк

Ручная реализация LRU-кэша на OrderedDict

less_21__time_collections_OrderedDict_Counter__lru_cache/theory_03__LRU.md

Показывает идею LRU (Least Recently Used) кэша «на пальцах»: функция put(key, value) при повторном использовании ключа переносит его в конец словаря (через pop и повторную вставку), а при превышении MAX_SIZE удаляет самый старый элемент через popitem(last=False). Прогон по пяти вызовам put() наглядно показывает, как обновление и переполнение меняют содержимое кэша.

  • cache = OrderedDict(), MAX_SIZE = 3 — ограничение размера кэша
  • put(key, value): если ключ уже есть — pop() и вставка заново (перенос в конец)
  • cache[key] = value — новая или обновлённая пара всегда попадает в конец
  • len(cache) > MAX_SIZE → cache.popitem(last=False) — удаление самого старого элемента
  • Повторный put('a', 1) переносит 'a' в конец, спасая его от вытеснения
Показать начало файла (38 строк всего)
## Что такое LRU cache?

LRU (Least Recently Used) — это стратегия, при которой из кэша удаляются  
"наименее недавно использованные" элементы, когда кэш заполняется.


### Пример, создания LRU кэша с помощью OrderedDict:

```python
from collections import OrderedDict

cache = OrderedDict()
MAX_SIZE = 3

def put(key, value):
    # если ключ уже есть — удаляем его, чтобы потом вставить как "новый"
    if key in cache:
        cache.pop(key)
    cache[key] = value  # вставляем в конец (новый элемент)
    # если переполнен — удаляем самый старый (первый)
    if len(cache) > MAX_SIZE:
        cache.popitem(last=False)

    print(cache)  # просто показываем текущее состояние

# наполняем кэш
put('a', 1)
put('b', 2)
put('c', 3)
put('a', 1)  # "a" снова используется → переносим в конец
put('d', 4)  # переполнение → удалится "b"

# OrderedDict({'a': 1})
# OrderedDict({'a': 1, 'b': 2})
# OrderedDict({'a': 1, 'b': 2, 'c': 3})
# OrderedDict({'b': 2, 'c': 3, 'a': 1})
# OrderedDict({'c': 3, 'a': 1, 'd': 4})
```
Проверьте себя: Почему при добавлении 'd' из кэша вытесняется именно 'b', а не 'a', ведь 'a' был добавлен раньше 'b'?
4
MarkdownРазбор концепции42 строк

functools.lru_cache: готовое кэширование результатов функции

less_21__time_collections_OrderedDict_Counter__lru_cache/theory_04__lru_cache.md

Декоратор @lru_cache автоматически запоминает результаты вызовов функции по её аргументам и возвращает закэшированное значение при повторном вызове с теми же аргументами вместо пересчёта. Разобраны параметры maxsize (лимит кэша, None — без лимита) и typed (различать ли 1 и 1.0 как разные ключи). Пример — рекурсивный fib(n), где print внутри функции наглядно показывает, что для уже посчитанных n тело функции больше не выполняется.

  • @lru_cache(maxsize=128) — декоратор над функцией
  • maxsize=None — кэш без ограничения по размеру
  • typed=True — 1 и 1.0 кэшируются как разные ключи
  • fib(n) с print(n) внутри — видно, для каких n тело функции реально выполнилось
  • Повторные вызовы fib(i) в цикле переиспользуют уже посчитанные значения
Показать начало файла (42 строк всего)
## `functools.lru_cache` 

Это декоратор из стандартной библиотеки Python, который автоматически кэширует результаты функции.  
Он хранит последние (или наиболее часто используемые) результаты вызовов,  
чтобы при повторном вызове с теми же аргументами не пересчитывать результат заново.


## Синтаксис

```python
from functools import lru_cache

@lru_cache(maxsize=128)
def func(args):
    ...
```

**Параметры:**

* `maxsize` — максимальное количество сохранённых вызовов (по умолчанию `maxsize=128`)
  * Если `maxsize=None`, кэш не ограничен по размеру.
* `typed` (по умолчанию `False`) — если `True`, то аргументы разных типов кэшируются отдельно 
  * например, (`1` и `1.0` считаются разными ключами).

---

## Пример с функцией, вычисляющей числа Фибоначчи

```python
from functools import lru_cache

@lru_cache
def fib(n):
    if n < 2:
        return n
    print(n, end=' ')
    return fib(n-1) + fib(n-2)

for i in range(1, 10):
    print(fib(i))
```
Проверьте себя: Если убрать @lru_cache у рекурсивного fib(n), print(n) внутри функции напечатает намного больше чисел — почему именно с декоратором вывод такой короткий?