Списки и модуль List

Знакомимся со списками — иммутабельной односвязной структурой, на которой держится функциональная обработка данных.

Список в OCaml — неизменяемая односвязная последовательность элементов одного типа, построенная из [] и операции добавления в голову ::.

Список — самая узнаваемая структура функциональных языков. В OCaml он односвязный и неизменяемый: вы не меняете список, а создаёте новый. Понимание устройства объясняет, почему одни операции дёшевы, а другие дороги.

Устройство: голова и хвост

let a = []                 (* пустой: 'a list *)
let b = 1 :: 2 :: 3 :: []  (* [1; 2; 3] *)
let c = [1; 2; 3]          (* то же, синтаксический сахар *)
let d = 0 :: c             (* [0; 1; 2; 3] — добавление в голову *)

Элементы разделяются точкой с запятой ;, а не запятой (запятая создала бы кортеж!). Все элементы одного типа: [1; "a"] — ошибка.

Рекурсивная обработка

let rec sum = function
  | [] -> 0
  | head :: tail -> head + sum tail

let total = sum [10; 20; 30]   (* 60 *)

Образец head :: tail разбирает непустой список: head — первый элемент, tail — остаток. Эта схема — основной приём работы со списками.

Ключевые функции модуля List

ФункцияЧто делает
List.length lчисло элементов
List.rev lразворот списка
l1 @ l2конкатенация (дорогая по длине l1)
List.nth l iэлемент по индексу (линейный)
List.map f lприменить f к каждому
List.filter p lоставить подходящие
let evens = List.filter (fun x -> x mod 2 = 0) [1;2;3;4;5;6]
(* [2; 4; 6] *)

Как работает под капотом

Каждый :: — это ячейка в куче из двух полей: значение и указатель на хвост. Поэтому добавление в голову занимает O(1) — создаётся одна ячейка, указывающая на старый список, который не копируется (он неизменяем). А доступ по индексу List.nth и конкатенация @ требуют пройти список — это O(n). Практическое правило: накапливайте добавлением в голову, а в конце делайте List.rev. Для быстрого доступа по индексу используйте массивы.

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

  • Разделять элементы запятой: [1, 2, 3] — это список из одного кортежа. Нужна точка с запятой.
  • Часто использовать @ в цикле. Накопление через @ даёт квадратичную сложность. Лучше :: + rev.
  • Обращаться по индексу как в массиве. List.nth линеен.

Итоги

  • Список — неизменяемая односвязная структура из [] и ::; элементы разделяются ;.
  • Обработка — рекурсия с разбором на голову и хвост через сопоставление с образцом.
  • Добавление в голову — O(1), индексация и конкатенация — O(n).
Проверьте себя
1. Чем разделяются элементы списка в OCaml?
AЗапятой
BТочкой с запятой
CПробелом
DДвоеточием
2. Какова сложность добавления элемента в голову списка через `::`?
AO(n)
BO(1)
CO(log n)
DO(n^2)
3. Что означает образец `head :: tail`?
AСписок из двух элементов
BРазбор непустого списка: head — первый элемент, tail — остаток
CКонкатенацию двух списков
DКортеж