Получение четных чисел в обратном итераторе

Я хочу создать итератор, который выдает четные числа из значения в обратном порядке.

Эта программа демонстрирует проблему. Он считывает два целых числа из командной строки и печатает четные числа между ними в порядке убывания. Достаточно ожидать, что минимальное значение будет четным.

use std::io::{self, BufRead};
fn main() {
    // Read numbers
    let mut input = io::BufReader::new(io::stdin());
    let mut buf = String::new();
    input.read_line(&mut buf).unwrap();

    let mut split = buf.split(" ");
    let min = split.next().unwrap().trim().parse::<i64>().unwrap();
    let max = split.next().unwrap().trim().parse::<i64>().unwrap();
    // Attempt to generate the iterator
    let payload: String = (min..max)
        .rev()
        .step_by(2)
        .map(|x| x.to_string() + " ")
        .collect();
    println!("{payload}");
}

Однако, например, при вводе 2 6 программа выдает 5 3, хотя я ожидал, что числа будут 4 2.

Как создать итератор, который будет выдавать 4 2, поскольку необратимая версия выдает 2 4?

Вы можете настроить максимум так, чтобы он был четным (max &= !1), и использовать min..=max в качестве диапазона.

Sven Marnach 13.03.2024 13:41

Что это &= !1 вообще делает? Злой маленький хак.

vahvero 13.03.2024 13:46

Но да, пожалуйста, ответьте, если считаете, что это будет оптимальный способ добиться этого.

vahvero 13.03.2024 13:46

Это маскирует младший бит и устанавливает его в ноль.

Sven Marnach 13.03.2024 13:46

или переместите .rev() после .step_by(2)

vallentin 13.03.2024 13:46

@valllentin это вызовет ошибку компилятора.

vahvero 13.03.2024 13:47

Вы указываете "от любого максимума". Если вы хотите, чтобы код работал с любого минимума, вам также необходимо настроить минимум на следующее четное число, используя min += min & 1.

Sven Marnach 13.03.2024 13:48

Какой «правильный итератор» вам нужен? Тот, который выводит 4 2, или тот, который выводит 6 4 2? Вам необходимо учитывать тот факт, что x.rev() — это не просто замена конечных точек и обратный отсчет.

chepner 13.03.2024 13:59

То есть с помощью x вы всегда включаете x.start, но никогда не включаете x.end. С помощью x.rev() вы можете включить x.end и включать x.start только тогда, когда вы включаете x.end.

chepner 13.03.2024 14:00

@chepner Я прояснил вопрос в этом аспекте.

vahvero 13.03.2024 14:07

@vahvero замена rev и step_by не приведет к ошибке компилятора вы, должно быть, напутали где-то еще.

cafce25 13.03.2024 14:11
step_by(2) — это хак, который выбирает четные числа только тогда, когда четные числа изначально находятся на четных позициях. Это верно для диапазонов (even..odd), но не для диапазонов в целом, и rev() не сохраняет свойство четных чисел в четных позициях. Проще всего было бы использовать filter для прямого выбора четных значений, независимо от того, где они находятся. (1/2)
chepner 13.03.2024 14:14

Я предполагаю, что вы ищете что-то более эффективное, которое не требует применения предиката к каждому значению, а использует тот факт, что независимо от того, начинается ли итератор с четного или нечетного числа, четные и нечетные числа чередуются. Для этого вам необходимо настроить логику в зависимости от того, четное или нечетное первое число. (2/2)

chepner 13.03.2024 14:15

@vahvero, вы получаете отрицательные голоса, потому что вопрос основан на ошибочной предпосылке; итератор и rev работают, как указано. Диапазон (min..max) оценивается как [min,max) и оценивается до вызова rev. Ваше ожидание того, что ваш код правильный, нереально.

Michael F 13.03.2024 14:19

@cafce25 выдает ошибку, если максимальное значение неизвестно во время компиляции. Я постарался сделать вопрос максимально простым, но если конечная точка является динамической, должна возникнуть ошибка «ошибка [E0277]: привязка признака std::ops::Range<i64>: ExactSizeIterator не удовлетворена».

vahvero 13.03.2024 14:19

@vahvero, похоже, ваш вопрос не включает минимально воспроизводимый пример тогда.

cafce25 13.03.2024 14:19

@cafce25 cafce25 Я изменил код, включенный в вопрос, чтобы лучше продемонстрировать вопрос.

vahvero 13.03.2024 14:34

Перестановка rev и step_by по-прежнему работает, если вы используете не i64 в качестве типа, а, например, i32 или isize.

cafce25 13.03.2024 15:37

@cafce25 Да, могу подтвердить. Я бы посчитал это лучшим ответом, чем принятый, без дополнительных звонков. Я даже не знал, что смена типа может повлиять на дальность.

vahvero 13.03.2024 17:43
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
2
19
63
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

step_by(2) работает в предположении, что итератор строго чередует четные и нечетные числа, но также начинается с четного числа. Это верно для Range, начинающегося с четного числа, но не верно для обращения каждого Range. В частности, первый элемент (2..6).rev() нечетный.

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

let a = (2..6).rev()
        .skip_while(|x| x % 2 == 1)
        .step_by(2)
        .map(|x| x.to_string() + " ")
        .collect();
println!("{a}");

(2..6).rev() начинается с 5, а (2..6).rev().skips_while(...) начинается с 4. Обратите внимание, что (2..5).rev() и (2..5).rev().skips_while(...) будут эквивалентны, поскольку ничего из исходного итератора не пропускается.

Что касается эффективности, предикат будет применен не более чем к 2 элементам, чтобы удовлетворить предварительному условию, необходимому для step_by(2), по сравнению с

let a = (2..6).rev()
        .filter(|x| x % 2 == 0)
        .map(|x| x.to_string() + " ")
        .collect();

который не может использовать какую-либо информацию о структуре (2..6).rev() для применения данного предиката менее чем O(n) раз.

Другие вопросы по теме