Перейти к основному содержимому

Коллекции

Разработчику Аналитику Тестировщику
Архитектору Инженеру

Ранее: Перечисления · Дальше: Итоги раздела

Связанные материалы — Структуры данных, реализация на псевдокоде, индексация, итератор, коллекция (программирование).


Коллекции

Коллекция — объект в программе, который хранит много элементов и даёт готовые способы их добавлять, искать, удалять и перебирать.

Элементом может быть:

  • примитивint, double, bool (типы данных);
  • строка — имя, заголовок, ключ;
  • объект — экземпляр класса Book, User, Order (класс и объект);
  • другая коллекция — список списков, словарь списков.

Коллекция меняет размер во время работы. После добавить элементов больше, после удалить — меньше. Перераспределение памяти выполняет библиотека языка.

С точки зрения абстракции данных коллекция — контейнер с операциями добавить, удалить, найти, перебрать. Внутри может быть динамический массив, связный список или хеш-таблица. Код обращается к интерфейсу List, Map, Set.

image-11.png


Массив и динамический список

Массив — упорядоченный набор ячеек фиксированной или заданной при создании длины.

Список (List) — коллекция с доступом по индексу и изменяемым размером.

СвойствоМассивСписок
Размерчасто фиксированменяется в рантайме
Вставка в серединусдвиг вручнуюadd, insert
Памятьодин блокбуфер с перевыделением
Типичные задачиматрица, буфер, сокетыкаталог, корзина, лента

По языкам:

  • JavaScriptArray с push, pop, splice;
  • Javaint[] и ArrayList;
  • C#T[] и List<T>;
  • Pythonlist;
  • Go — массив [n]T и срез []T.
Компонент загружается. Это пример интеграции — сейчас мы запрашиваем данные из другой системы.

Индексация

Индексация — доступ к элементу упорядоченной коллекции по номеру позиции. Подробная практика — ниже; теория адреса в памяти — линейные структуры.

элемент := список[индекс]
список[индекс] := значение

Индекс и ключ

СписокСловарь
Обращениеlist[2]map["id"]
Позиция задаётсяпорядком вставкиуникальным ключом
Когда уместенобход по порядку, UI-спискипоиск по id, кэш

Нумерация с нуля

СхемаПервый элементЯзыки
С нуля0C, Java, C#, JS, Python, Go, Rust
С единицы1Pascal, MATLAB
Произвольный базисnFortran

Третья книга на полке — books[2]. Ошибка на единицу (off-by-one) — цикл до <= size вместо < size.

Отрицательные индексы

items = ["a", "b", "c", "d"]
items[-1] # "d"
items[-2] # "c"

В Java и C# отрицательный индекс — исключение.

Выход за границы

Допустимые индексы при нумерации с нуля: 0размер − 1.

ЯзыкИсключение
JavaIndexOutOfBoundsException
PythonIndexError
C#ArgumentOutOfRangeException

Срезы

nums = [10, 20, 30, 40, 50]
nums[1:4] # [20, 30, 40]
nums[:2] # [10, 20]
nums[2:] # [30, 40, 50]

Срез создаёт новую коллекцию, исходная не меняется.

Многомерные массивы

grid[строка][столбец]

См. Матрицы.

Слово "индекс" в SQL

В СУБД индекс ускоряет поиск по столбцу — индексы СУБД. Это не номер строки в результате SELECT.


Список (List)

Список — упорядоченная коллекция с индексами 0n−1, дубликаты разрешены.

Основные операции:

ОперацияПсевдокодЗаметка
Добавить в конецсписок.добавить(x)амортизированно O(1)
Вставить по индексусписок.вставить(i, x)O(n) из-за сдвига
Получитьсписок[i]O(1) для динамического массива
Удалить по индексусписок.удалить(i)O(n)
Найти индекссписок.индекс(x)O(n)
Размерсписок.размер()O(1)
Подсписоксписок.срез(a, b)новая коллекция

Пример — корзина покупок:

корзина = новый Список()
корзина.добавить("Хлеб")
корзина.добавить("Молоко")
корзина.добавить("Яйца")

суммаПозиций = корзина.размер()
первыйТовар = корзина[0]

Список подходит, когда важен порядок отображения и обход от начала к концу. Плохой выбор для поиска по id среди десятков тысяч записей — лучше словарь.

Реализации — ArrayList (Java), List<T> (C#), list (Python), Array (JS). Сложность — таблицы O(·).


Словарь (Map)

Словарь — коллекция пар ключ → значение. Ключи уникальны.

ОперацияПсевдокод
Записатьсловарь[ключ] := значение
Прочитатьзначение := словарь[ключ]
Удалитьсловарь.удалить(ключ)
Есть ключ?словарь.содержитКлюч(ключ)
Все ключисловарь.ключи()

Пример — каталог товаров по артикулу:

товары = новый Словарь()
товары["SKU-001"] = новый Product("Хлеб", 45)
товары["SKU-002"] = новый Product("Молоко", 89)

хлеб = товары["SKU-001"]

Поиск по ключу в хеш-таблице в среднем за O(1). Порядок ключей в HashMap не гарантирован; для порядка вставки — LinkedHashMap (Java), для сортировки по ключу — TreeMap.

Компонент загружается. Это пример интеграции — сейчас мы запрашиваем данные из другой системы.

Безопасное чтение:

если словарь.содержитКлюч(id):
вернуть словарь[id]
иначе
вернуть значениеПоУмолчанию

В C# — TryGetValue. В Java — getOrDefault. В Python — dict.get(key, default).

Подробнее — ключ-значение, Коллекции в Java, Коллекции в C#.


Множество (Set)

Множество хранит уникальные элементы. Повторный add того же значения игнорируется.

ОперацияНазначение
добавить(x)вставить, если ещё нет
содержит(x)проверка за O(1) в среднем
удалить(x)убрать элемент
объединение(a, b)все из обоих
пересечение(a, b)только общие
разность(a, b)из a без b

Пример — уникальные теги статьи:

теги = новый Множество()
теги.добавить("java")
теги.добавить("oop")
теги.добавить("java") // второй раз не добавится

если теги.содержит("oop"):
вывести "есть тег oop"

Множество удобно для:

  • удаления дубликатов из списка;
  • быстрой проверки "уже видели этот id?";
  • операций теории множеств.

Порядок обхода в HashSet не фиксирован. Нужна сортировка — TreeSet (Java), sorted(set) (Python).


Очередь (Queue)

ОчередьFIFO (First In, First Out). Первым пришёл — первым вышел.

ОперацияСмысл
enqueue(x)встать в хвост
dequeue()взять из головы
peek()посмотреть голову без извлечения

Пример — очередь печати:

очередь = новый Queue()
очередь.enqueue("doc1.pdf")
очередь.enqueue("doc2.pdf")

следующий = очередь.dequeue() // "doc1.pdf"

Очереди задач в фоне, обработка сообщений, BFS в графах — типичные применения.

Компонент загружается. Это пример интеграции — сейчас мы запрашиваем данные из другой системы.

Приоритетная очередь (PriorityQueue) отдаёт элемент с наивысшим приоритетом, а не строго первый по времени.


Стек (Stack)

СтекLIFO (Last In, First Out). Последним пришёл — первым вышел.

ОперацияСмысл
push(x)положить сверху
pop()снять сверху
peek()посмотреть верхний

Примеры:

  • стек вызовов функций (выполнение кода);
  • undo/redo в редакторе;
  • разбор вложенных скобок (( )).
стек = новый Stack()
стек.push("шаг1")
стек.push("шаг2")
отмена = стек.pop() // "шаг2"

Стек и очередь — структуры данных, очередь.


Кортеж и неизменяемые коллекции

Кортеж (tuple) — упорядоченный набор фиксированной длины, который после создания не меняют.

  • Python — point = (10, 20);
  • C# — ValueTuple<int, int>;
  • Kotlin — Pair, Triple.

Неизменяемый списокList.of(...) (Java), кортеж из элементов. Изменение создаёт новую коллекцию (+ в Kotlin, spread в Python). Это снижает риск случайной порчи данных из другого места кода.

Подробнее — кортежи.


Сводная таблица видов

ТипПорядокДубликатыДоступ
Списокпо индексудаlist[i]
Множествообычно нетнетcontains
Словарьпо ключамключи уникальныmap[key]
ОчередьFIFOзависит от типаdequeue
СтекLIFOpop
Dequeс обоих концовaddFirst

Как выбрать тип

  • порядок и позиция — список;
  • уникальность — множество;
  • поиск по id — словарь;
  • задачи по очереди — очередь;
  • откат действий — стек.

Сценарий — интернет-магазин

// Список — порядок отображения в каталоге
каталог = новый Список()
каталог.добавить(товар1)
каталог.добавить(товар2)

// Словарь — быстрый поиск по артикулу
поАртикулу = новый Словарь()
поАртикулу[товар1.sku] = товар1

// Множество — уникальные категории для фильтра
категории = новый Множество()
для каждого т в каталог:
категории.добавить(т.category)

// Очередь — заказы на обработку
очередьЗаказов = новый Queue()
очередьЗаказов.enqueue(заказ42)

Три разных коллекции решают три разные задачи в одном модуле.


Сценарий — библиотека

Класс Library:
свойство books: List<Book>
свойство byIsbn: Map<String, Book>

метод addBook(book):
books.Add(book)
byIsbn[book.isbn] = book

метод findByIsbn(isbn):
вернуть byIsbn[isbn]

метод listByAuthor(author):
результат = новый Список()
для каждого b в books:
если b.author == author:
результат.добавить(b)
вернуть результат

List хранит порядок поступления. Map даёт O(1) по ISBN. Фильтр по автору — линейный проход по списку или Stream / LINQ.


Сценарий — фоновые задачи

очередь = новый Queue()
очередь.enqueue(задачаОтправитьEmail)
очередь.enqueue(задачаСжатьФайл)

пока не очередь.пуста():
задача = очередь.dequeue()
выполнить(задача)

Воркер забирает задачи строго по порядку постановки. Для параллельных воркеров смотрят асинхронность.


Создание и наполнение

products = новый Список()
products.добавить("Яблоко")
products.добавить("Банан")

colors = новый Список("красный", "зелёный", "синий")
usersById = новый Словарь()

Пустая коллекция — старт для данных из API, файла, формы. Размер заранее неизвестен.

Чтение файла построчно:

строки = новый Список()
пока естьСледующаяСтрока(файл):
строки.добавить(прочитатьСтроку(файл))

Где хранят коллекции

МестоПример
Локальная переменнаяitems = новый Список()
Поле классаbooks: Список<Book>
Параметр функцииобработать(orders: Список<Order>)
Возврат функцииgetAllUsers(): Список<User>
ВложенностьMap<String, List<Integer>>

Ссылка и копия

Переменная коллекции часто хранит ссылку на один и тот же объект:

a = новый Список(1, 2, 3)
b = a
b.добавить(4)
// a тоже содержит 1, 2, 3, 4

Чтобы изменить b и не трогать a, нужна копия:

b = a.копия() // поверхностная
b = глубокаяКопия(a) // если элементы — изменяемые объекты

В Python — list(a) или a.copy(). В Java — new ArrayList<>(a). Путаница ссылок — частая причина "неожиданных" изменений в другом модуле.


Перебор элементов

Итерация — обработка каждого элемента по очереди.

для каждого product в products:
вывести product

С индексом:

для i от 0 до products.размер() - 1:
вывести i, products[i]

Через итератор:

итератор = products.получитьИтератор()
пока итератор.естьСледующий():
обработать(итератор.следующий())

foreach (C#, Java), for…of (JS) скрывают итератор. Циклы — Управляющие конструкции.

:::caution Изменение коллекции во время обхода Удаление внутри foreach ломает перебор. Используйте removeIf, отдельный список на удаление или iterator.remove(). :::


Основные операции обработки

Фильтрация — оставить элементы по условию.

Преобразование (map) — применить функцию к каждому элементу.

Свёртка (reduce) — свести коллекцию к одному значению.

чётные = numbers.фильтровать(n -> n % 2 == 0)
квадраты = numbers.преобразовать(n -> n * n)
сумма = numbers.свернуть(0, (acc, n) -> acc + n)
ЯзыкИнструмент
Pythonfilter, list comprehension — 22
JavaStream API — 24
C#LINQ — 29
JavaScriptfilter, map, reduce211
Компонент загружается. Это пример интеграции — сейчас мы запрашиваем данные из другой системы.

Подсчёт частот

Сколько раз встретилось каждое слово:

частоты = новый Словарь()
для каждого слова в слова:
если частоты.содержитКлюч(слово):
частоты[слово] = частоты[слово] + 1
иначе
частоты[слово] = 1

В Python — collections.Counter. В Java — Collectors.groupingBy. Задачи — Lab / частоты.


Группировка

Разбить заказы по статусу:

поСтатусу = новый Словарь()
для каждого заказа в заказы:
статус = заказ.status
если не поСтатусу.содержитКлюч(статус):
поСтатусу[статус] = новый Список()
поСтатусу[статус].добавить(заказ)

В C# — GroupBy, в Java — Collectors.groupingBy, в Python — itertools.groupby (после сортировки) или цикл.


Сортировка и поиск

Сортировка — упорядочить элементы (sort, sorted). Для объектов задают правило сравнения — компаратор, Comparator (Java), IComparer (C#).

список.сортировать((a, b) -> a.price - b.price)

Линейный поиск — перебор до нахождения, O(n).

Бинарный поиск — в отсортированной коллекции, O(log n). В Java — Collections.binarySearch.

Поиск в словаре — O(1) в среднем по ключу.

Алгоритмы — поиск и сортировка. Big-O — Lab / 1128.


Чтение JSON

JSON-массив → список:

["apple", "banana", "pear"]
fruits = json.loads(text) # list в Python

JSON-объект → словарь:

{"id": 1, "name": "Alice"}

Согласуйте типы полей с моделью класса. Парсинг — JSON, Продвинутые операции с данными.


Чтение CSV и SQL

CSV — строка разбивается на поля, строки складывают в список словарей или список списков.

SQL — каждая строка результата SELECT становится объектом или словарём, все строки — List<Row>.

Дальше — SQL, ORM.


Коллекции и полиморфизм

Полиморфизм подтипов — список базового типа хранит объекты разных подклассов:

figures = новый List<Фигура>()
figures.Add(новый Круг())
figures.Add(новый Прямоугольник())

для каждой f в figures:
f.Нарисовать()

Обобщения (generics)List<Order>, не List «чего угодно». Теория — Обобщения. Синтаксис — Java, C#.


Коллекции и перечисления

Список статусов для фильтра:

активные = новый Множество()
активные.добавить(OrderStatus.New)
активные.добавить(OrderStatus.Processing)

для каждого заказа в заказы:
если активные.содержит(заказ.status):
обработать(заказ)

В Java — EnumSet. Словарь подписей для UI: Map<OrderStatus, String>.


Ленивая итерация

Генератор выдаёт элементы по одному без полной материализации:

  • C# — yield return30;
  • Kotlin — Sequence225;
  • Python — yield в функции-генераторе — 22.

Полезно при чтении больших файлов построчно.


Пустая коллекция и null

Возвращать пустой список [] предпочтительнее, чем null:

Функция findByTag(tag):
результат = новый Список()
...
вернуть результат // даже если ничего не нашли

Вызывающий код пишет for (x : result) без проверки на null. Идиома — Effective Java / стиль проекта.


Библиотеки по языкам

ЯзыкТипы и пакетыСтатья
Javajava.util24
Pythonlist, dict, set, collections22
C#System.Collections.Generic28
JavaScriptArray, Map, Set211
TypeScriptтипизированные коллекции19
KotlinlistOf, mutableListOf225
Goslice, map16
C++STL24
RustVec, HashMap13
SwiftArray, Dictionary16
PHParray15
RubyArray, Hash13

Полный список — коллекции в языках.


Мини-практикум

  1. Создайте List<String> из пяти городов. Выведите второй и последний (с учётом нумерации с нуля).
  2. Постройте Map<id, User> из списка пользователей. Найдите пользователя по id за одно обращение.
  3. Удалите дубликаты из списка чисел через Set.
  4. Смоделируйте undo — три push и два pop на стеке.
  5. Посчитайте частоту букв в строке через словарь.
  6. Отсортируйте список товаров по цене.
  7. Сгруппируйте заказы по статусу enum.
  8. Распарсьте JSON-массив имён в список.
  9. Объясните, почему поиск по id в List из 100 000 элементов медленнее, чем в Map.
  10. Верните пустой список вместо null из функции поиска.

Частые вопросы

Чем список отличается от массива?

Список в Java/C# динамически растёт. Классический массив C фиксирован. В JS и Python граница размыта.

Когда Set, когда List?

List — порядок и дубликаты. Set — только уникальность и быстрая проверка вхождения.

Почему map[key] падает с ошибкой?

Ключа нет. Используйте get с значением по умолчанию или containsKey.

Можно ли сортировать словарь?

Сортируют список ключей или пар (key, value), либо используют TreeMap.

Что такое Iterable?

Интерфейс «по мне можно итерироваться». List, Set в Java его реализуют.

Зачем LinkedList, если есть ArrayList?

LinkedList лучше при частых вставках в середину (на практике реже, чем кажется). Чаще берут ArrayList за кэш-локальность.

Как хранить список списков?

List<List<Integer>> — матрица смежности, таблица ячеек.

Коллекции потокобезопасны?

Обычные ArrayList, HashMap — нет. Для многопоточности — ConcurrentHashMap, CopyOnWriteArrayList (Java) или синхронизация снаружи.


Частые ошибки

  • линейный поиск по id в длинном списке;
  • удаление в foreach;
  • путаница ссылки и копии;
  • null вместо пустой коллекции;
  • изменение коллекции, переданной в другой модуль, без явного контракта;
  • хранение всего в одном List<Object> без generics.

Справочник методов списка

МетодДействиеJavaPythonC#JavaScript
Добавить в конец+1 элементaddappendAddpush
Вставитьпо индексуadd(i, x)insertInsertsplice
Удалитьпо индексу/значениюremovepop/removeRemovesplice
Размерколичествоsize()lenCountlength
ПодсписоксрезsubList[a:b]GetRangeslice
Содержит?поискcontainsinContainsincludes
Очиститьудалить всеclearclearClearlength=0

Справочник методов словаря

МетодJavaPythonC#JavaScript
Записьput[k]=[key]=set
Чтениеget[k] / get[key]get
Удалениеremovedel / popRemovedelete
КлючиkeySet.keys()Keyskeys()
Значенияvalues.values()Valuesvalues()
ПарыentrySet.items()переборentries()

Справочник методов множества

МетодJavaPythonC#
ДобавитьaddaddAdd
УдалитьremoveremoveRemove
СодержитcontainsinContains
ОбъединениеaddAll|UnionWith
ПересечениеretainAll&IntersectWith

Вложенные коллекции

Список списков — матрица, таблица:

матрица = новый Список()
строка0 = новый Список(1, 2, 3)
строка1 = новый Список(4, 5, 6)
матрица.добавить(строка0)
матрица.добавить(строка1)
ячейка = матрица[1][2] // 6

Словарь списков — группировка:

поАвтору = новый Словарь()
поАвтору["Толстой"] = новый Список("Война и мир", "Анна Каренина")

Словарь словарей — двухуровневый кэш region → userId → User.


Сценарий — подсчёт слов в тексте

текст = прочитатьФайл("article.txt")
слова = разбить(текст, поПробелам)
частоты = новый Словарь()

для каждого слова в слова:
норма = слово.вНижнийРегистр()
если частоты.содержитКлюч(норма):
частоты[норма] = частоты[норма] + 1
иначе
частоты[норма] = 1

топ10 = отсортировать(частоты.пары(), поЗначению).взять(10)

Словарь даёт O(1) обновление счётчика на слово. Список пар сортируют для вывода топа.


Сценарий — история посещений (deque)

история = новый Deque()
история.addLast("/home")
история.addLast("/catalog")
история.addLast("/product/5")

назад = история.removeLast() // /product/5
текущая = история.peekLast() // /catalog

Двусторонняя очередь — навигация "назад" и "вперёд" в браузере.


Сценарий — проверка скобок (стек)

Функция скобкиСбалансированы(текст):
стек = новый Stack()
для каждого символ c в текст:
если c == "(" или c == "[":
стек.push(c)
если c == ")":
если стек пуст или стек.pop() != "(":
вернуть ложь
если c == "]":
если стек пуст или стек.pop() != "[":
вернуть ложь
вернуть стек.пуст()

Классическая задача на стек — алгоритмы.


Сравнение List и Map для поиска

ОперацияList из n элементовMap по ключу
Найти по idO(n) переборO(1) в среднем
Добавить в конецO(1) амортиз.O(1)
Обход по порядкуестественныйключи не по порядку в HashMap
Памятькомпактнеенакладные расходы на хеш

На 10 элементах разницы нет. На 100 000 — словарь критичен.


List comprehension (Python-стиль)

Идея, переносимая на другие языки через map/filter:

squares = [x * x for x in range(10) if x % 2 == 0]

Читается как "список квадратов x для чётных x". Аналог — LINQ where + select, Java Stream filter + map.


Сортировка объектов

Класс Product:
свойство name: String
свойство price: int

продукты.сортировать((a, b) -> a.price - b.price)

Для строк — лексикографическое сравнение. Для дат — по timestamp. Нестабильная сортировка может поменять порядок равных элементов — алгоритмы сортировки.


Обход словаря

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

для каждого ключа в словарь.ключи():
обработать(ключ, словарь[ключ])

В Java — entrySet(). В Python — .items(). В JS — for (const [k,v] of map).


Иммутабельность и защитная копия

Публичный геттер не должен отдавать внутренний изменяемый список:

Класс Team:
свойство _members: List<Player>

метод getMembers():
вернуть неизменяемыйСписок(_members)
// или return new ArrayList<>(_members)

Иначе вызывающий код вызовет _members.clear() в обход API класса — нарушение инкапсуляции.


Коллекции в параметрах функций

Функция printAll(items: List<String>):
для каждого item в items:
вывести item

Принимают любой размер. Не перегружайте сигнатуру десятком параметров a, b, c, d — передайте список.

Возврат нескольких значений без кортежа — через List или Map (осторожно с типизацией).


Потокобезопасность (кратко)

Обычные коллекции не рассчитаны на одновременную запись из нескольких потоков. Варианты:

  • обёртка Collections.synchronizedList (Java);
  • ConcurrentHashMap для общих кэшей;
  • передача копии коллекции в другой поток;
  • однопоточная модель без разделяемого состояния.

Подробнее — многопоточность, Java Collections.


Связь со структурами данных

Коллекция в языкеСтруктура под капотом (часто)
ArrayListдинамический массив
LinkedListсвязный список
HashMapхеш-таблица
TreeMapкрасно-чёрное дерево
PriorityQueueкуча
ArrayDequeкольцевой буфер

Расширенный практикум

  1. Реализуйте invert(map) — словарь {a:1, b:2}{1:a, 2:b} (значения уникальны).
  2. Найдите пересечение двух Set<String> тегов.
  3. Реализуйте очередь из двух стеков.
  4. Постройте Map<Character, Integer> частот букв и выведите три самых частых.
  5. Отфильтруйте список пользователей старше 18 через filter.
  6. Преобразуйте List<Order> в Map<orderId, Order>.
  7. Объясните, зачем LinkedHashMap для LRU-кэша.
  8. Прочитайте JSON-массив объектов и постройте словарь по полю id.

Расширенные вопросы

Чем Vector отличается от ArrayList?

Vector синхронизирован, legacy. Новый код — ArrayList или concurrent-коллекции.

Зачем Collections.unmodifiableList?

Вернуть read-only вид коллекции вызывающему коду.

Можно ли использовать список как ключ словаря?

В Python — да, если список hashable нет; списки не hashable. Ключ должен быть неизменяемым (tuple).

Что такое WeakHashMap?

Карта со слабыми ссылками на ключи — для кэшей, GC может собрать ключ. Java-специфика.

Как отсортировать Map по значению?

Собрать entrySet, отсортировать список записей по getValue(), собрать LinkedHashMap.


Путеводитель — Java

List<String> fruits = new ArrayList<>();
fruits.add("apple");
fruits.add("banana");

Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 30);

Set<String> tags = new HashSet<>();
tags.add("java");

for (String f : fruits) {
System.out.println(f);
}

fruits.stream()
.filter(s -> s.startsWith("a"))
.map(String::toUpperCase)
.forEach(System.out::println);
  • ArrayList — список по умолчанию.
  • HashMap — словарь по умолчанию.
  • HashSet — множество по умолчанию.
  • Stream API — декларативная фильтрация и преобразование.

Статья — Коллекции в Java.


Путеводитель — Python

fruits = ["apple", "banana"]
fruits.append("pear")

ages = {"Alice": 30, "Bob": 25}
ages["Charlie"] = 35

tags = {"java", "oop"}

for f in fruits:
print(f)

squares = [x * x for x in range(10) if x % 2 == 0]

from collections import Counter
counts = Counter(words)
  • list — динамический массив.
  • dict — хеш-словарь.
  • set — множество.
  • tuple — неизменяемая последовательность.
  • Counter — частоты из коробки.

Статья — Коллекции в Python.


Путеводитель — C#

var fruits = new List<string> { "apple", "banana" };
fruits.Add("pear");

var ages = new Dictionary<string, int> {
["Alice"] = 30
};

var tags = new HashSet<string> { "csharp" };

foreach (var f in fruits)
Console.WriteLine(f);

var query = fruits
.Where(s => s.Length > 5)
.Select(s => s.ToUpper())
.ToList();
  • List<T> — основной список.
  • Dictionary<TKey, TValue> — словарь.
  • HashSet<T> — множество.
  • LINQ — Where, Select, OrderBy, GroupBy.

Статьи — Коллекции в C#, LINQ.


Путеводитель — JavaScript

const fruits = ["apple", "banana"];
fruits.push("pear");

const ages = new Map();
ages.set("Alice", 30);

const tags = new Set(["js", "web"]);

for (const f of fruits)
console.log(f);

const long = fruits
.filter(s => s.length > 5)
.map(s => s.toUpperCase());
  • Array — список и методы map/filter/reduce.
  • Map — словарь с любым типом ключа.
  • Set — уникальные значения.
  • Объект { key: value } — часто как словарь, но ключи только строки/Symbol.

Статья — Массивы в JavaScript.


Путеводитель — Kotlin

val fruits = mutableListOf("apple", "banana")
fruits.add("pear")

val ages = mutableMapOf("Alice" to 30)
val tags = setOf("kotlin", "jvm")

for (f in fruits)
println(f)

val upper = fruits
.filter { it.length > 5 }
.map { it.uppercase() }
  • listOf / mutableListOf — неизменяемый и изменяемый список.
  • mapOf / mutableMapOf — словарь.
  • Sequence — ленивая цепочка операций.

Статья — Коллекции в Kotlin.


Путеводитель — Go

fruits := []string{"apple", "banana"}
fruits = append(fruits, "pear")

ages := map[string]int{"Alice": 30}

for _, f := range fruits {
fmt.Println(f)
}
  • slice []T — динамический список через append.
  • map map[K]V — словарь.
  • Отдельного Set в стандартной библиотеке нет — map[T]struct{} как идиома.

Статья — Типы, slice и map.


Сценарий — школьный журнал

ученики = новый List<Student>()
ученики.добавить(новый Student("Анна", 5))
ученики.добавить(новый Student("Борис", 4))

оценкиПоПредмету = новый Map<String, List<Integer>>()
оценкиПоПредмету["Математика"] = новый List(5, 4, 5)
оценкиПоПредмету["Русский"] = новый List(4, 4)

среднее(оценкиПоПредмету["Математика"])

Список учеников — порядок в классе. Словарь предмет → список оценок — вложенная структура.


Сценарий — кэш с ограничением размера

кэш = новый LinkedHashMap() // порядок доступа
лимит = 100

Функция get(key):
если кэш.содержитКлюч(key):
значение = кэш[key]
кэш.удалить(key)
кэш[key] = значение // обновить порядок
вернуть значение
значение = загрузитьИзБД(key)
если кэш.размер() >= лимит:
удалитьСтарейший(кэш)
кэш[key] = значение
вернуть значение

Идея LRU — LinkedHashMap в Java, OrderedDict в Python 3.7+.


Итератор — внутренний протокол

интерфейс Iterator<T>:
метод hasNext(): boolean
метод next(): T
метод remove(): void // опционально

foreach в Java разворачивается примерно так:

Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
...
}

В Python объект итерируем, если есть __iter__() и __next__().


Comparable и Comparator

Comparable — объект сравнивает себя с другим (a.compareTo(b)).

Comparator — внешнее правило сравнения (Comparator.comparing(Product::getPrice)).

Сортировка List<Product> требует одного из них. Без правила сравнения компилятор не знает порядок.


Таблица сложности (кратко)

Структураget по индексу/ключуaddcontainsremove
ArrayListO(1)O(1)*O(n)O(n)
LinkedListO(n)O(1)O(n)O(1)**
HashMapO(1)*O(1)*O(1)*O(1)*
HashSetO(1)*O(1)*O(1)*
TreeMapO(log n)O(log n)O(log n)O(log n)

* в среднем, амортизированно. ** при известном узле.

Полные таблицы — O(·), Lab / 1128.


Чек-лист выбора коллекции

  • Нужен ли порядок элементов?
  • Нужна ли уникальность?
  • Как часто ищем по ключу/id?
  • Сколько элементов (десятки vs миллионы)?
  • Меняется ли коллекция после создания?
  • Доступ из нескольких потоков?
  • Нужна ли сортировка по ключу автоматически?

Путеводитель — TypeScript

const fruits: string[] = ["apple", "banana"];
fruits.push("pear");

const ages = new Map<string, number>([["Alice", 30]]);

const unique = new Set(fruits);

const adults = users.filter(u => u.age >= 18);
const names = users.map(u => u.name);

Типы string[], Map<K,V>, Set<T>, дженерики — Коллекции в TypeScript.


Путеводитель — Rust

let mut fruits = vec!["apple", "banana"];
fruits.push("pear");

let mut ages = HashMap::new();
ages.insert("Alice", 30);

for f in &fruits {
println!("{}", f);
}

Vec<T> — список. HashMap<K,V> — словарь. HashSet<T> — множество. Владение и заимствование влияют на API — Rust.


Путеводитель — C++ (STL)

std::vector<std::string> fruits = {"apple", "banana"};
fruits.push_back("pear");

std::map<std::string, int> ages;
ages["Alice"] = 30;

std::unordered_set<std::string> tags = {"cpp", "stl"};

vector — динамический массив. map — дерево. unordered_map — хеш. Работа с данными.


Путеводитель — PHP

$fruits = ["apple", "banana"];
$fruits[] = "pear";

$ages = ["Alice" => 30, "Bob" => 25];

$tags = array_unique(["php", "web", "php"]);

foreach ($fruits as $f) {
echo $f;
}

Массив PHP — и список, и ассоциативный словарь. Типы данных.


Путеводитель — Ruby

fruits = ["apple", "banana"]
fruits << "pear"

ages = { "Alice" => 30 }

tags = Set.new(%w[ruby oop])

fruits.each { |f| puts f }
fruits.select { |f| f.length > 5 }

Array, Hash, Set из stdlib. Типы.


Путеводитель — Swift

var fruits = ["apple", "banana"]
fruits.append("pear")

var ages = ["Alice": 30]

let tags: Set = ["swift", "ios"]

for f in fruits { print(f) }
let upper = fruits.map { $0.uppercased() }

Array, Dictionary, SetДанные и коллекции.


Полный цикл — от API до UI

1. HTTP GET /products → JSON-массив
2. Парсинг → List<Product>
3. Map<sku, Product> для быстрого поиска
4. Set<String> категорий для фильтра
5. Сортировка List по цене для отображения
6. List<Product> в состоянии UI-компонента

Каждый шаг — своя коллекция под задачу.


Денормализация для скорости

Иногда одни и те же данные дублируют в list и map:

каталог = новый List<Product>() // порядок на витрине
индекс = новый Map<sku, Product>() // поиск в корзине

метод addProduct(p):
каталог.добавить(p)
индекс[p.sku] = p

Память тратится дважды, зато оба доступа быстрые. Компромисс в высоконагруженных UI.


Удаление дубликатов — три способа

// 1. Через множество
уникальные = новый List(новый Set(исходный))

// 2. Через словарь (сохранить последний)
seen = новый Set()
результат = новый List()
для каждого x в исходный с конца:
если не seen.содержит(x):
seen.добавить(x)
результат.добавить(0, x)

// 3. Сортировка + проход
отсортировать(исходный)
результат = убратьСоседниеДубликаты(исходный)

Объединение коллекций

все = новый List()
все.добавитьВсе(списокA)
все.добавитьВсе(списокB)

// Словари: B перезаписывает ключи A
объединённый = копия(A)
для каждой пары (k, v) в B:
объединённый[k] = v

В Python — a | b для dict (3.9+). В Java — putAll.


Плоский список из вложенного

вложенный = [[1,2], [3,4], [5]]
плоский = новый List()
для каждого внутренний во вложенный:
для каждого x во внутренний:
плоский.добавить(x)
// [1,2,3,4,5]

В JS — flat(). В Python — list comprehension с двойным циклом.


zip — параллельный обход

имена = ["Анна", "Борис"]
возрасты = [20, 22]

для каждой пары (имя, возраст) в zip(имена, возрасты):
вывести имя, возраст

Python zip, C# Zip, Kotlin zip — синхронный обход двух списков.


partition — разделить на два списка

прошли = новый List()
неПрошли = новый List()
для каждого студента в группа:
если студент.score >= 60:
прошли.добавить(студент)
иначе
неПрошли.добавить(студент)

Аналог filter дважды с разными условиями.


min, max, sum

мин = numbers[0]
макс = numbers[0]
сумма = 0
для каждого n в numbers:
если n < мин: мин = n
если n > макс: макс = n
сумма = сумма + n

Библиотеки предоставляют Collections.min, Math.min в stream, min() в Python.


any и all

естьОтрицательные = любой(numbers, n -> n < 0)
всеПоложительные = все(numbers, n -> n > 0)

Короткое замыкание — остановка при первом true для any.


Итоговая шпаргалка коллекций

ЗадачаКоллекция
Список на экранеList
Поиск по idMap
Уникальные тегиSet
Очередь задачQueue
UndoStack
Частоты словMap
ГруппировкаMap → List
СортировкаList + sort
JSON-массивList
JSON-объектMap

Финальный практикум

  1. Постройте инвертированный индекс: слово → список id документов.
  2. Реализуйте стек команд для undo в текстовом редакторе (3 команды).
  3. Сравните время поиска в List и Map на 10 000 элементов (Lab / 1128).
  4. Сериализуйте Map<String, List<Order>> в JSON вручную.
  5. Объясните разницу Arrays.asList и new ArrayList в Java.

Книга рецептов — список

Добавить в конецlist.add(x) / append(x) / push(x).

Вставить в серединуlist.add(i, x) / insert(i, x).

Удалить по индексуlist.remove(i) / pop(i).

Удалить по значениюlist.remove(x) (первое вхождение).

Первый/последнийlist.get(0), list.get(list.size()-1).

Пуст лиlist.isEmpty() / len(list)==0.

Копияnew ArrayList<>(list) / list.copy() / [...list].

Обратный порядокCollections.reverse / list.reverse() / reversed().

ПеремешатьCollections.shuffle.

Содержит элементlist.contains(x) / x in list.


Книга рецептов — словарь

Записатьmap.put(k,v) / map[k]=v / map.set(k,v).

Прочитать с defaultmap.getOrDefault(k, d).

Проверить ключmap.containsKey(k) / k in map.

Удалить ключmap.remove(k) / del map[k].

Размерmap.size() / len(map).

Только ключиmap.keySet() / map.keys().

Только значенияmap.values().

Обновить если естьmap.merge(k, v, merger) / map[k] = map.get(k,0)+1.

Инвертировать{v:k for k,v in map.items()} при уникальных values.


Книга рецептов — множество

Добавитьset.add(x).

Объединениеset.addAll(other) / a | b / a.union(b).

ПересечениеretainAll / a & b.

РазностьremoveAll / a - b.

Подмножество?a.containsAll(b).

Из списка убрать дублиnew HashSet<>(list).


Книга рецептов — очередь и стек

Очередь: встать в хвостqueue.offer(x) / enqueue(x).

Очередь: взять из головыqueue.poll() / dequeue().

Стек: положитьstack.push(x).

Стек: снятьstack.pop().

Посмотреть верхstack.peek() / queue.peek().


Книга рецептов — обработка

Фильтр — оставить по условию.

Map — преобразовать каждый элемент.

Reduce — свернуть в одно значение.

Сортировкаsort с компаратором.

ГруппировкаgroupingBy / цикл в Map<K,List>.

Подсчёт частотCounter / merge в цикле.

Первый подходящийfind / filter().findFirst().

Все подходящиеfilter в новый список.

Любой/всеanyMatch / allMatch.


Сценарий — социальная сеть

лента = новый List<Post>() // порядок в ленте
поId = новый Map<long, Post>() // открыть пост по id
подписчики = новый Map<long, Set<long>>() // user → set of follower ids
хештеги = новый Map<String, Set<long>>() // tag → post ids

метод publish(post):
лента.добавить(0, post) // в начало
поId[post.id] = post
для каждого tag в post.tags:
если не хештеги.содержитКлюч(tag):
хештеги[tag] = новый Set()
хештеги[tag].добавить(post.id)

Четыре структуры под четыре типа запросов.


Сценарий — склад

остатки = новый Map<sku, int>() // сколько на складе
зарезервировано = новый Map<sku, int>()
очередьОтгрузки = новый Queue<Order>()

метод reserve(sku, qty):
если остатки[sku] < qty:
выбросить НетНаСкладе()
остатки[sku] -= qty
зарезервировано[sku] = зарезервировано.get(sku,0) + qty

Словари для O(1) обновления остатков. Очередь для FIFO-отгрузки.


Сценарий — учебный курс

студенты = новый List<Student>()
оценки = новый Map<Student, List<int>>() // студент → список оценок

метод addGrade(student, grade):
если не оценки.содержитКлюч(student):
оценки[student] = новый List()
оценки[student].добавить(grade)

метод average(student):
список = оценки[student]
вернуть sum(список) / список.размер()

Ключом словаря выступает объект. В Java нужны корректные equals/hashCode у Student.


Nullable элементы в коллекции

List<String?> в Kotlin/C# — список, где элементы могут быть null. Отличие от пустого списка:

  • null — ссылки на коллекцию нет;
  • [] — коллекция есть, элементов ноль;
  • [null, "a"] — два слота, первый пустой.

Сериализация коллекций

JSON.stringify({ items: [1,2,3], meta: { count: 3 } })

Список → JSON-массив. Словарь → JSON-объект. Вложенность сохраняется. JSON.


Построчный разбор — загрузка каталога

// 1. Пустой список для порядка на витрине
каталог = новый ArrayList<Product>()

// 2. Пустой словарь для поиска по артикулу за O(1)
индекс = новый HashMap<String, Product>()

// 3. Читаем JSON-массив из API
json = http.get("/api/products")
массив = parseJsonArray(json)

// 4. Для каждого объекта JSON создаём Product
для каждого объекта obj в массив:
товар = новый Product(
obj["sku"],
obj["name"],
obj["price"]
)
каталог.добавить(товар) // сохраняем порядок с сервера
индекс[товар.sku] = товар // индексируем для корзины

// 5. Множество категорий для фильтра слева
категории = новый HashSet<String>()
для каждого товар в каталог:
категории.добавить(товар.category)

// 6. Пользователь кликнул товар в корзине — поиск по sku
выбранный = индекс[skuИзКорзины] // без перебора всего каталога

// 7. Сортировка по цене для режима "дешевле"
отсортировать(каталог, (a,b) -> a.price - b.price)

// 8. Отображение — обход списка
для каждого товар в каталог:
ui.renderCard(товар)

Один сценарий — четыре коллекции, у каждой своя роль.


Построчный разбор — обработка очереди задач

очередь = новый LinkedList<Task>()
стекОтмены = новый Stack<Command>()

метод submit(task):
очередь.addLast(task)

метод workerLoop():
пока не очередь.пуста():
task = очередь.removeFirst()
cmd = выполнить(task)
стекОтмены.push(cmd)

метод undo():
если стекОтмены.пуст():
вернуть
cmd = стекОтмены.pop()
cmd.отменить()

Очередь — FIFO для входящих задач. Стек — LIFO для отмены последней команды.


Построчный разбор — частотный анализ логов

счётчик = новый HashMap<String, Integer>()

для каждой строки line в файлЛогов:
уровень = извлечьУровень(line) // "ERROR", "WARN", ...
счётчик[уровень] = счётчик.getOrDefault(уровень, 0) + 1

для каждой пары (уровень, count) в счётчик.entrySet():
если count > порог:
alert(уровень, count)

Один проход по файлу, O(1) обновление счётчика на строку.


Сравнение коллекций Java (шпаргалка)

КлассИнтерфейсКогда
ArrayListListсписок по умолчанию
LinkedListList, Dequeочередь/deque
HashMapMapсловарь по умолчанию
LinkedHashMapMapпорядок вставки, LRU
TreeMapMapсортировка по ключу
HashSetSetмножество
TreeSetSetотсортированное множество
PriorityQueueQueueкуча по приоритету
ArrayDequeDequeстек и очередь
EnumSetSetмножество enum

Сравнение коллекций Python (шпаргалка)

ТипИзменяемыйКогда
listдаосновной список
tupleнетфиксированная запись
dictдасловарь
setдауникальность
frozensetнетнеизменяемое множество
dequeдаочередь с двух концов
Counterдачастоты
defaultdictдасловарь с default factory
OrderedDictдапорядок (3.7+ dict тоже)

Модуль collectionsКоллекции в Python.


Сравнение коллекций C# (шпаргалка)

ТипКогда
List<T>список
Dictionary<TKey,TValue>словарь
HashSet<T>множество
SortedDictionaryсортировка по ключу
SortedSetотсортированное множество
Queue<T>FIFO
Stack<T>LIFO
LinkedList<T>вставки в середину
ObservableCollectionUI с уведомлениями (WPF)

Матрица «задача → операции»

ЗадачаListMapSetQueueStack
Показать список UI
Найти по id
Уникальные теги
Фоновые задачи
Undo
Частоты
Сортировка
Группировка

Дополнительные вопросы FAQ

Можно ли сортировать Set?

HashSet — нет. TreeSet / sorted(set) — да.

List или массив в Java?

API и бизнес-логика — List. int[] — численные расчёты, interop.

Как объединить два списка?

addAll, + (Python/Kotlin), spread [...a, ...b] (JS).

Как глубоко копировать List объектов?

Поверхностная копия списка не клонирует объекты внутри. Нужен цикл new ArrayList<>(old) + clone каждого элемента при необходимости.

Коллекция как поле static?

Осторожно с mutable static — общее состояние для всех экземпляров, проблемы в многопоточности.


Глоссарий коллекций

ТерминКратко
Элементодна ячейка данных внутри коллекции
Индексномер позиции в списке (с 0)
Ключидентификатор записи в словаре
Значениеданные по ключу в словаре
Итераторобъект для пошагового обхода
Итерацияцикл по всем элементам
FIFOпервая вошла — первая вышла
LIFOпоследняя вошла — первая вышла
Хешфункция для быстрого поиска в Map/Set
Коллизиядва ключа с одним бакетом хеша
Срезподколлекция по диапазону индексов
GenericsList<T> — тип элементов
Mutableколлекцию можно менять после создания
Immutableсостав фиксирован (tuple, List.of)
Поверхностная копияновый контейнер, те же объекты внутри
Глубокая копиякопия контейнера и копии элементов

Приложение — типичные импорты

Java

import java.util.*;
import java.util.stream.*;

C#

using System.Collections.Generic;
using System.Linq;

Python

from collections import Counter, defaultdict, deque

Приложение — чтение документации JDK

Иерархия Java Collections:

Iterable
Collection
List → ArrayList, LinkedList
Set → HashSet, TreeSet, EnumSet
Map → HashMap, TreeMap, LinkedHashMap
Queue → PriorityQueue, ArrayDeque

Диаграмма в Коллекции в Java.


Куда дальше

ТемаСтатья
ПеречисленияПеречисления
Итоги ООПИтоги
Реализация и O(·)Структуры данных
АлгоритмыАлгоритмы
Big-OLab / 1128
Частоты и группировкаLab / 1131

Содержание