Есть ли черта в Rust, которая позволяет сворачивать по группам без экспериментальных функций, таких как сопрограммы?

#rust #iterator #delimiter #yield

#Ржавчина #итератор #разделитель #выход

Вопрос:

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

У меня есть вектор v , который разделен на группы 0. Сумма этих групп с разделителями может быть накоплена с помощью цикла for или итератора:

 fn main() {
    let v = vec![55, 23, 75, 0, 12, 34, 0, 97, 71, 23, 0];

    // for loop
    let mut acc = 0;

    for amp;vi in amp;v {
        acc  = vi;

        if vi == 0 {
            println!("for yield {}", acc);
            acc = 0;
        }
    }

    // iter 'loop'
    v.iter()
        .scan(0, |acc, amp;vi| {
            *acc  = vi;
            Some(if vi == 0 {
                let total = *acc;
                *acc = 0;
                Some(total)
            } else {
                None
            })
        })
        .filter_map(|acc| acc)
        .for_each(|acc| println!("iter yield {:?}", acc));
}
  

scan используется как сопрограмма ad hoc, которая возвращает, Some(value) когда итератор выдал значение и None когда итератор все еще обрабатывает. None отфильтровывается и выводятся суммы.

Приведенный выше пример несколько тривиален, поскольку обе операции приводят к одному и тому же результату; однако мой проект требует переваривания потока данных (не могут быть собраны в вектор) и их условного сворачивания (например, условием здесь может быть, если acc делится на 10, а не разделяется 0).

Само условие сворачивания конвейерное, так что каждый конвейер может предоставлять итератор (подумайте о вложенных сопрограммах). Я хотел бы посмотреть, есть ли альтернатива Iterator::scan -> Iterator::filter_map , используют ли они итераторы или нет. Имейте в виду, что сбор всего потока данных невозможен, поскольку поток потенциально неограничен.

Комментарии:

1. разделение на группы по нулям должно быть максимально простым, v.split(|vi| vi == 0) и затем вы можете использовать .map(|g| g.sum()) или что-то подобное. Однако у меня нет лучшего решения для разделения на основе самой суммы.

2. @kmdreko Есть ли способ сделать это с помощью итераторов? Я сжимаю большой поток объектов, а само сжатие конвейерно разделено на 4 этапа, поэтому сбор в вектор приведет к нежелательной задержке. Я вижу, что черта разделения специализирована для строковых типов. Интересно, смогу ли я избежать ручной реализации этого для векторов.

3. Эта дополнительная информация о том, как вы ее используете, является ключом к тому, чтобы сделать вопрос несубъективным. В настоящее время у вас есть только два разных способа сделать одно и то же, оба из которых работают, и неопределенный запрос сделать его «более идиоматичным». Если код, приведенный в вопросе, не соответствует вашим потребностям, важно указать в вопросе, почему он не соответствует вашим потребностям, и объяснить, какой ответ был бы приемлемым.

4. @trentcl Я добавлю дополнительную информацию в вопрос. Спасибо вам за предложение.

5. Меня смущает ваше использование термина «сопрограмма» применительно к scan . Как scan используется здесь как сопрограмма? Сопрограмма должна либо иметь возможность приостановить выполнение, либо переключить его на кого-то другого, а ваш код не делает ни того, ни другого, это обычная подпрограмма. Название также относится к сопрограммам без объяснения связи.

Ответ №1:

Нет ничего плохого ни в for цикле, ни в версии с scan() . Если бы я искал более понятную альтернативу, я бы подумал об использовании group_by из itertools ящика. Эта версия элегантна и делает почти то, что вам нужно:

 use itertools::Itertools;

v.iter()
    .group_by(|amp;amp;vi| vi != 0)
    .into_iter()
    .map(|(_, group)| group.sum::<u32>())
    .for_each(|s| println!("sum {}", s));
  

Проблема в том, что он выводит числа 153, 0, 46, 0, 191, и 0. Значения 0 исходят из того факта, что он рассматривает элемент 0 как просто еще одну группу, для которой ключ (результат выражения vi != 0 ) оказывается ложным, а не истинным. Чтобы исправить это, вам нужно изменить map на filter_map аналогично тому, как вы сделали с scan() :

 v.iter()
    .group_by(|amp;amp;vi| vi != 0)
    .into_iter()
    .filter_map(|(filt, group)| if filt { Some(group.sum::<u32>()) } else { None })
    .for_each(|s| println!("sum {}", s));
  

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

Комментарии:

1. Мой вопрос был неправильно сформулирован. Примите мой голос, но я закрою этот вопрос и опубликую исправленную версию.