Теперь мы можем выполнять различные операции над элементами коллекции, например, отфильтровать значения по определенному критерию, сосчитать, просуммировать и много чего еще. Эти операции настолько распространены и часто используются, что воплотились в специальные функции, в Rust они называются адаптерами для итераторов (iterator adapters), потому что могут соединяться в цепочки из нескольких адаптеров (как в электрике), передавая результат операции от одного к другому (с помощью итераторов), добавляя свои изменения к полученным на входе элементам коллекции. Некоторые из адаптеров принимают в качестве параметров замыкания, которые принято называть предикатами (predicates). Это очень удобно, давайте рассмотрим основные из них.
Адаптер фильтр (filter) делает выборку по условию заданному предикатом, например, .iter().filter(|x| **x < 0) отбирает только те элементы, которые меньше нуля, т.е. имеют отрицательное значение и возвращает итератор, который мы можем использовать в цикле. Каждый элемент коллекции присваивается локальной переменной x, которая сравнивается с нулем. Замыкание возвращает тру если элемент соответствует условию и адаптер-фильтр исключает элементы, которые ему не соответствуют. Мы используем два оператора разыменовывания (*) потому что “х” это ссылка(адаптер) на ссылку(итератор) на элемент в коллекции.
Адаптер преобразовать (map) применяет выражение, заданное предикатом к каждому элементу коллекции. Например, .iter().map(|x| *x * 2) умножает каждый элемент на 2, т.е. изменяет (удваивает) элемент в коллекции и возвращает итератор на новые значения. В данном случае у нас одно разыменование (*), т.к. ссылка(итератор) на элемент только одна.
Адаптер: перечислить (enumerate) создает кортеж, состоящий из счетчика (позиции) и ссылки на значение элемента в этой позиции. Адаптер получает и возвращает итератор, .iter().enumerate().
Потребитель итератора (iterator consumer) отличается от адаптера тем, что принимает, но не возвращает итератор. Их удобно использовать для различных проверок, например с помощью if-else. Обычно потребитель завершает цепочку из адаптеров.
Iterator generator -> Iterator Adapter -> … -> Iterator Adapter -> Iterator Consumer
Потребитель: любой (any) возвращает true (bool), если любой (какой-нибудь) из элементов коллекции, соответствует условию, заданному предикатом (замыканием). Например, .iter().any(|e| *e == 0) проверяет элементы на равенство нулю. Полная версия выглядит так: .iter().any(|e: &i32| -> bool { *e == 0 }). К элементам применяется логическое ИЛИ.
Потребитель: все (all) возвращает true (bool), если все элементы коллекции, соответствует условию, заданному предикатом (замыканием). Например, .iter().all(|e| *e == 0) проверяет равны ли нулю все элементы коллекции. К элементам применяется логическое И.
Потребитель: подсчет (count) возвращает количество элементов в итераторе. Удобно использовать в связке с адаптером. Например, .iter().filter(|e| **e == 1).count(), возвращает количество элементов, которые равны нулю.
Потребитель: сумма (sum) возвращает общую сумму значений элементов в итераторе. Например, .iter().sum::<i32>(). Как мы видим, требуется указать тип возвращаемого значения, если Rust не может его вывести. Если итератор пустой, возвращается ноль. Элементы коллекции должны поддерживать операцию сложения (+).
Потребители: минимальное (min) и максимальное (max) возвращают минимальное и максимальное значение соответственно. Так как итератор может быть пустой, возвращается тип Option, с вариантами Some(n) и None. Элементы должны поддерживать операции сравнения (=, <) и могут быть строками.
Потребитель: собрать (collect) создает вектор из элементов, полученных от итератора. Например, .iter().collect::<Vec<&i32>>(), так как Rust может вывести тип, можно использовать “_” .iter().collect::<Vec<_>>() или .iter().collect(). Таким образом удобно сохранять результаты обработки коллекции. При обработке строк, от выбранного типа (String или Vec<char>), зависит в каком виде будут сохранены элементы вектора, как строки или символы. Смотрим пример.
Итераторы ленивы (lazy) они ничего не делают пока их не просит адаптер, потребитель или цикл, который выступает в роли потребителя. Если данные кем-то не запрошены, к ним нет доступа.
fn main() {
//Iterator
for element in vec![0; 3].iter_mut() {
*element += 1;
//println!("{:?}", element);
}
//Adapters
//Filter
for n in [-1, 3, 0].iter().filter(|x| **x < 0) {
println!("{} ", n);
}
//Map
for n in [2, 4, 6].iter().map(|x| *x * 2) {
println!("{} ", n);
}
//Enumerate
for (i, n) in [2, 4, 6].iter().enumerate() {
println!("{} {}", i, n);
}
//Consumers
//Any
if [2, 0, 6].iter().any(|e| *e == 0) {
println!("arr has 0");
}
//All
if [1, 1, 1].iter().all(|e| *e == 1) {
println!("all are 1");
}
//Count
println!("count of 1: {}", [1, 0, 1].iter().filter(|e| **e == 1).count());
//Sum
println!("sum: {}", [2, 2].iter().sum::<i32>());
let sum: i32 = [2, 2].iter().sum();
println!("sum: {}", sum);
//Min
match [1, 2].iter().min() {
Some(n) => println!("min: {}", n),
None => println!("empty")
}
//Max
match [0; 0].iter().max() {
Some(n) => println!("max: {}", n),
None => println!("empty")
}
//Collect
let vec = [1, 2, 3].iter().collect::<Vec<&i32>>();
println!("vec: {:?}", vec); //vec: [1, 2, 3]
let vec1 = "hello world".to_string().chars().collect::<String>();
println!("vec: {:?}", vec1); //vec: "hello world"
let vec2 = "hello".to_string().chars().collect::<Vec<char>>();
println!("vec: {:?}", vec2); //vec: ['h', 'e', 'l', 'l', 'o']
//Chains
let arr = [66, -8, 43, 19, 0, -31];
let v: Vec<i32> = arr
.iter()
.filter(|x| **x > 0)
.map(|x| *x * 2)
.collect();
print!("{:?}", v); //[132, 86, 38]
}
Перегуд В.