Шаг 6. Коллекции и итераторы
Зачем#
Три коллекции покрывают подавляющее большинство задач: List<T> (упорядоченный список), Map<K, V> (словарь) и Set<T>
(множество уникальных значений). Map и Set итерируются в порядке вставки, поэтому вывод детерминирован — это важно для
тестов и воспроизводимых журналов.
Обработка коллекций — это конвейеры «отфильтровать → преобразовать → собрать». Они ленивы: iter<T> ничего не вычисляет,
пока вы не попросите результат (toList, fold, for). Поэтому длинный конвейер не создаёт промежуточных списков.
Список, словарь, множество#
fn main() io {
var xs: List<i32> = [5, 3, 8]
xs.push(1)
xs.sort()
print("{xs} first={xs.first() ?? -1} last={xs.last() ?? -1}")
print("{xs.map(x => x * 10)} {xs.filter(x => x > 3)} sum={xs.fold(0, (a, x) => a + x)}")
print("{xs.contains(8)} {xs.any(x => x > 7)} {xs.all(x => x > 1)} {xs.count(x => x % 2 == 1)}")
var stock: Map<str, i32> = {"apple": 3, "pear": 0} // Map<K, V>: литерал {k: v}
stock["plum"] = 7 // присваивание по ключу вставляет или заменяет
print("{stock.get("apple") ?? 0} {stock.get("kiwi") ?? -1} {stock.len()}")
stock.remove("pear")
for (k, v) in stock.iter().sortBy(p => p.0) { print("{k}={v}") }
var seen: Set<str> = Set.new()
print("{seen.insert("a")} {seen.insert("a")} {seen.contains("a")} {seen.len()}")
}Вывод
[1, 3, 5, 8] first=1 last=8 [10, 30, 50, 80] [5, 8] sum=17 true true false 3 3 -1 3 apple=3 plum=7 true false true 1
Разбор#
xs.push(1),xs.sort()изменяют список на месте и поэтому требуютvar.map,filter,foldвозвращают новое значение.xs.first()возвращаетT?— у пустого списка первого элемента нет, и?? -1задаёт значение по умолчанию. То же сgetу списка и словаря: выход за границы черезgetне паника, аnone.{"apple": 3}— литерал словаря,{:}— пустой словарь (пустые{}— это блок).stock["plum"] = 7вставляет или заменяет.Set.insertвозвращаетtrue, если элемент новый.- Итерация по словарю даёт пары
(ключ, значение);sortByупорядочивает их.
Ленивые итераторы и запросы#
fn main() io {
let nums = 1..=10
let evens = nums.iter().filter(n => n % 2 == 0).map(n => n * n) // ленивая цепочка: пока ничего не вычислено
print("{evens.toList()}") // toList запускает вычисление
let words = ["pear", "fig", "banana", "kiwi", "apple"]
let q = from w in words where w.len() > 3 order by w.len() desc, w select "{w}:{w.len()}" // запрос — тот же конвейер в виде выражения
print("{q.toList()}")
let pairs = words.iter().enumerate().skip(1).take(2).toList()
print("{pairs}")
let total = (1..=100).iter().takeWhile(n => n * n < 200).fold(0, (a, n) => a + n)
print("{total}")
print("{words.iter().map(w => w.len()).max() ?? 0}")
let byLen = from w in words group by w.len() into g select (g.key, g.count())
print("{byLen.toList()}")
}Вывод
[4, 16, 36, 64, 100] ["banana:6", "apple:5", "kiwi:4", "pear:4"] [(1, "fig"), (2, "banana")] 105 6 [(4, 2), (3, 1), (6, 1), (5, 1)]
1..=10— диапазон;.iter()превращает коллекцию или диапазон в ленивыйiter<T>.filter,map,skip,take,enumerate,takeWhileвозвращают новый итератор; вычисление запускаютtoList(),fold,count,max,for.from w in words where … order by … select …— выражение запроса: то же самое, но читается как SQL. Для коллекций в памяти это ленивый итератор; для таблиц ORM (шаг в туре) тот же синтаксис компилируется в SQL.group by … into g select (g.key, g.count())группирует; группы идут в порядке первого появления ключа.
Типичные ошибки#
Вызов несуществующего метода — unknown-method (в OxL длина списка — len(), а не length()):
fn main() io {
let xs = [1, 2, 3]
print("{xs.length()}")
}error
unknown-method
Тип элемента проверяется строго: положить строку в List<i32> нельзя — type-mismatch:
fn main() io {
var xs: List<i32> = [1, 2]
xs.push("three")
}error
type-mismatch
Что запомнить#
List,Map,Set— по порядку вставки; поиск возвращаетT?.- Конвейеры
iterленивы; результат нужно потребовать. - Запрос
from … select— удобная запись того же конвейера.
Дальше: владение и заимствование.