From 7230f8b65e79c211621ee6d53fd019baf65add40 Mon Sep 17 00:00:00 2001 From: Bergmann89 Date: Sat, 7 Nov 2020 20:28:53 +0100 Subject: [PATCH] Implemented 'reduce_with' and 'try_reduce_with' operation --- asparit/src/core/iterator.rs | 80 ++++++++++++++++++++++++++++++++- asparit/src/inner/reduce.rs | 45 +++++++++++++++++++ asparit/src/inner/try_reduce.rs | 57 +++++++++++++++++++++++ 3 files changed, 180 insertions(+), 2 deletions(-) diff --git a/asparit/src/core/iterator.rs b/asparit/src/core/iterator.rs index d0309ad..527908f 100644 --- a/asparit/src/core/iterator.rs +++ b/asparit/src/core/iterator.rs @@ -20,11 +20,11 @@ use crate::{ map_init::MapInit, map_with::MapWith, product::Product, - reduce::Reduce, + reduce::{Reduce, ReduceWith}, sum::Sum, try_fold::{TryFold, TryFoldWith}, try_for_each::{TryForEach, TryForEachInit, TryForEachWith}, - try_reduce::TryReduce, + try_reduce::{TryReduce, TryReduceWith}, update::Update, }, misc::Try, @@ -673,6 +673,39 @@ pub trait ParallelIterator<'a>: Sized + Send { Reduce::new(self, identity, operation) } + /// Reduces the items in the iterator into one item using `operation`. + /// If the iterator is empty, `None` is returned; otherwise, + /// `Some` is returned. + /// + /// This version of `reduce` is simple but somewhat less + /// efficient. If possible, it is better to call `reduce()`, which + /// requires an identity element. + /// + /// # Examples + /// + /// ``` + /// use rayon::prelude::*; + /// let sums = [(0, 1), (5, 6), (16, 2), (8, 9)] + /// .par_iter() // iterating over &(i32, i32) + /// .cloned() // iterating over (i32, i32) + /// .reduce_with(|a, b| (a.0 + b.0, a.1 + b.1)) + /// .unwrap(); + /// assert_eq!(sums, (0 + 5 + 16 + 8, 1 + 6 + 2 + 9)); + /// ``` + /// + /// **Note:** unlike a sequential `fold` operation, the order in + /// which `operation` will be applied to reduce the result is not fully + /// specified. So `operation` should be [associative] or else the results + /// will be non-deterministic. + /// + /// [associative]: https://en.wikipedia.org/wiki/Associative_property + fn reduce_with(self, operation: O) -> ReduceWith + where + O: Fn(Self::Item, Self::Item) -> Self::Item + Sync + Send + 'a, + { + ReduceWith::new(self, operation) + } + /// Reduces the items in the iterator into one item using a fallible `operation`. /// The `identity` argument is used the same way as in [`reduce()`]. /// @@ -713,6 +746,49 @@ pub trait ParallelIterator<'a>: Sized + Send { TryReduce::new(self, identity, operation) } + /// Reduces the items in the iterator into one item using a fallible `operation`. + /// + /// Like [`reduce_with()`], if the iterator is empty, `None` is returned; + /// otherwise, `Some` is returned. Beyond that, it behaves like + /// [`try_reduce()`] for handling `Err`/`None`. + /// + /// [`reduce_with()`]: #method.reduce_with + /// [`try_reduce()`]: #method.try_reduce + /// + /// For instance, with `Option` items, the return value may be: + /// - `None`, the iterator was empty + /// - `Some(None)`, we stopped after encountering `None`. + /// - `Some(Some(x))`, the entire iterator reduced to `x`. + /// + /// With `Result` items, the nesting is more obvious: + /// - `None`, the iterator was empty + /// - `Some(Err(e))`, we stopped after encountering an error `e`. + /// - `Some(Ok(x))`, the entire iterator reduced to `x`. + /// + /// # Examples + /// + /// ``` + /// use rayon::prelude::*; + /// + /// let files = ["/dev/null", "/does/not/exist"]; + /// + /// // Find the biggest file + /// files.into_par_iter() + /// .map(|path| std::fs::metadata(path).map(|m| (path, m.len()))) + /// .try_reduce_with(|a, b| { + /// Ok(if a.1 >= b.1 { a } else { b }) + /// }) + /// .expect("Some value, since the iterator is not empty") + /// .expect_err("not found"); + /// ``` + fn try_reduce_with(self, operation: O) -> TryReduceWith + where + Self::Item: Try, + O: Fn(T, T) -> Self::Item + Sync + Send + 'a, + { + TryReduceWith::new(self, operation) + } + /// Parallel fold is similar to sequential fold except that the /// sequence of items may be subdivided before it is /// folded. Consider a list of numbers like `22 3 77 89 46`. If diff --git a/asparit/src/inner/reduce.rs b/asparit/src/inner/reduce.rs index 56887ca..cac948a 100644 --- a/asparit/src/inner/reduce.rs +++ b/asparit/src/inner/reduce.rs @@ -1,5 +1,7 @@ use crate::{core::Driver, Consumer, Executor, Folder, ParallelIterator, Reducer}; +/* Reduce */ + pub struct Reduce { iterator: X, identity: S, @@ -39,6 +41,49 @@ where } } +/* ReduceWith */ + +pub struct ReduceWith { + iterator: X, + operation: O, +} + +impl ReduceWith { + pub fn new(iterator: X, operation: O) -> Self { + Self { + iterator, + operation, + } + } +} + +impl<'a, X, O, T> Driver<'a, Option> for ReduceWith +where + X: ParallelIterator<'a, Item = T>, + O: Fn(T, T) -> T + Clone + Send + 'a, + T: Send + 'a, +{ + fn exec_with(self, executor: E) -> E::Result + where + E: Executor<'a, Option>, + { + let fold_op = self.operation.clone(); + let reduce_op = self.operation; + + self.iterator + .fold(<_>::default, move |a, b| match a { + Some(a) => Some(fold_op(a, b)), + None => Some(b), + }) + .reduce(<_>::default, move |a, b| match (a, b) { + (Some(a), Some(b)) => Some(reduce_op(a, b)), + (Some(v), None) | (None, Some(v)) => Some(v), + (None, None) => None, + }) + .exec_with(executor) + } +} + /* ReduceConsumer */ struct ReduceConsumer { diff --git a/asparit/src/inner/try_reduce.rs b/asparit/src/inner/try_reduce.rs index d067859..d1b00f1 100644 --- a/asparit/src/inner/try_reduce.rs +++ b/asparit/src/inner/try_reduce.rs @@ -5,6 +5,8 @@ use std::sync::{ use crate::{core::Driver, misc::Try, Consumer, Executor, Folder, ParallelIterator, Reducer}; +/* TryReduce */ + pub struct TryReduce { iterator: X, identity: S, @@ -46,6 +48,61 @@ where } } +/* TryReduceWith */ + +pub struct TryReduceWith { + iterator: X, + operation: O, +} + +impl TryReduceWith { + pub fn new(iterator: X, operation: O) -> Self { + Self { + iterator, + operation, + } + } +} + +impl<'a, X, O, T> Driver<'a, Option> for TryReduceWith +where + X: ParallelIterator<'a, Item = T>, + O: Fn(T::Ok, T::Ok) -> T + Clone + Send + 'a, + T: Try + Send, +{ + fn exec_with(self, executor: E) -> E::Result + where + E: Executor<'a, Option>, + { + let fold_op = self.operation.clone(); + let reduce_op = self.operation; + + self.iterator + .fold( + || None, + move |a: Option, b: T| match a { + Some(a) => match (a.into_result(), b.into_result()) { + (Ok(a), Ok(b)) => Some(fold_op(a, b)), + (Err(e), _) | (_, Err(e)) => Some(T::from_error(e)), + }, + None => Some(b), + }, + ) + .reduce( + || None, + move |a: Option, b: Option| match (a, b) { + (Some(a), Some(b)) => match (a.into_result(), b.into_result()) { + (Ok(a), Ok(b)) => Some(reduce_op(a, b)), + (Err(e), _) | (_, Err(e)) => Some(T::from_error(e)), + }, + (Some(v), None) | (None, Some(v)) => Some(v), + (None, None) => None, + }, + ) + .exec_with(executor) + } +} + /* TryReduceConsumer */ struct TryReduceConsumer {