Skip to content

Commit

Permalink
Replace itertools dependency with copied module
Browse files Browse the repository at this point in the history
Copied multi_product module from itertools 0.11.0
under MIT license. Eliminates dependency on building
all of itertools.
  • Loading branch information
overhacked committed Jul 29, 2023
1 parent bc100d1 commit 19ba7c4
Show file tree
Hide file tree
Showing 3 changed files with 290 additions and 11 deletions.
1 change: 0 additions & 1 deletion crates/test-case-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,3 @@ proc-macro2 = { version = "1.0", features = [] }
proc-macro-error = "1.0"
quote = "1.0"
syn = { version = "1.0", features = ["full", "extra-traits"] }
itertools = "0.11.0"
283 changes: 283 additions & 0 deletions crates/test-case-core/src/test_matrix/matrix_product.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
//! Copied with minor modifications from itertools v0.11.0
//! under MIT License
//!
//! Modifications called out in "(MOD)" comments, below
//!
//! Source file and commit hash:
//! https://github.com/rust-itertools/itertools/blob/v0.11.0/src/adaptors/multi_product.rs
//! ed6fbda086a913a787450a642acfd4d36dc07c3b
#[derive(Clone)]
/// An iterator adaptor that iterates over the cartesian product of
/// multiple iterators of type `I`.
///
/// An iterator element type is `Vec<I>`.
///
/// See [`.multi_cartesian_product()`](crate::Itertools::multi_cartesian_product)
/// for more information.
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
pub struct MultiProduct<I>(Vec<MultiProductIter<I>>)
where
I: Iterator + Clone,
I::Item: Clone;

// (MOD) Omit `impl Debug for MultiProduct`
// Not needed here and relies on a macro from itertools

/// Create a new cartesian product iterator over an arbitrary number
/// of iterators of the same type.
///
/// Iterator element is of type `Vec<H::Item::Item>`.
pub fn multi_cartesian_product<H>(iters: H) -> MultiProduct<<H::Item as IntoIterator>::IntoIter>
where
H: Iterator,
H::Item: IntoIterator,
<H::Item as IntoIterator>::IntoIter: Clone,
<H::Item as IntoIterator>::Item: Clone,
{
MultiProduct(
iters
.map(|i| MultiProductIter::new(i.into_iter()))
.collect(),
)
}

#[derive(Clone, Debug)]
/// Holds the state of a single iterator within a `MultiProduct`.
struct MultiProductIter<I>
where
I: Iterator + Clone,
I::Item: Clone,
{
cur: Option<I::Item>,
iter: I,
iter_orig: I,
}

/// Holds the current state during an iteration of a `MultiProduct`.
#[derive(Debug)]
enum MultiProductIterState {
StartOfIter,
MidIter { on_first_iter: bool },
}

impl<I> MultiProduct<I>
where
I: Iterator + Clone,
I::Item: Clone,
{
/// Iterates the rightmost iterator, then recursively iterates iterators
/// to the left if necessary.
///
/// Returns true if the iteration succeeded, else false.
fn iterate_last(
multi_iters: &mut [MultiProductIter<I>],
mut state: MultiProductIterState,
) -> bool {
use self::MultiProductIterState::*;

if let Some((last, rest)) = multi_iters.split_last_mut() {
let on_first_iter = match state {
StartOfIter => {
let on_first_iter = !last.in_progress();
state = MidIter { on_first_iter };
on_first_iter
}
MidIter { on_first_iter } => on_first_iter,
};

if !on_first_iter {
last.iterate();
}

if last.in_progress() {
true
} else if MultiProduct::iterate_last(rest, state) {
last.reset();
last.iterate();
// If iterator is None twice consecutively, then iterator is
// empty; whole product is empty.
last.in_progress()
} else {
false
}
} else {
// Reached end of iterator list. On initialisation, return true.
// At end of iteration (final iterator finishes), finish.
match state {
StartOfIter => false,
MidIter { on_first_iter } => on_first_iter,
}
}
}

/// Returns the unwrapped value of the next iteration.
fn curr_iterator(&self) -> Vec<I::Item> {
self.0
.iter()
.map(|multi_iter| multi_iter.cur.clone().unwrap())
.collect()
}

/// Returns true if iteration has started and has not yet finished; false
/// otherwise.
fn in_progress(&self) -> bool {
if let Some(last) = self.0.last() {
last.in_progress()
} else {
false
}
}
}

impl<I> MultiProductIter<I>
where
I: Iterator + Clone,
I::Item: Clone,
{
fn new(iter: I) -> Self {
MultiProductIter {
cur: None,
iter: iter.clone(),
iter_orig: iter,
}
}

/// Iterate the managed iterator.
fn iterate(&mut self) {
self.cur = self.iter.next();
}

/// Reset the managed iterator.
fn reset(&mut self) {
self.iter = self.iter_orig.clone();
}

/// Returns true if the current iterator has been started and has not yet
/// finished; false otherwise.
fn in_progress(&self) -> bool {
self.cur.is_some()
}
}

impl<I> Iterator for MultiProduct<I>
where
I: Iterator + Clone,
I::Item: Clone,
{
type Item = Vec<I::Item>;

fn next(&mut self) -> Option<Self::Item> {
if MultiProduct::iterate_last(&mut self.0, MultiProductIterState::StartOfIter) {
Some(self.curr_iterator())
} else {
None
}
}

fn count(self) -> usize {
if self.0.is_empty() {
return 0;
}

if !self.in_progress() {
return self
.0
.into_iter()
.fold(1, |acc, multi_iter| acc * multi_iter.iter.count());
}

self.0.into_iter().fold(
0,
|acc,
MultiProductIter {
iter,
iter_orig,
cur: _,
}| {
let total_count = iter_orig.count();
let cur_count = iter.count();
acc * total_count + cur_count
},
)
}

fn size_hint(&self) -> (usize, Option<usize>) {
// Not ExactSizeIterator because size may be larger than usize
if self.0.is_empty() {
return (0, Some(0));
}

if !self.in_progress() {
return self.0.iter().fold((1, Some(1)), |acc, multi_iter| {
size_hint::mul(acc, multi_iter.iter.size_hint())
});
}

// (MOD) Clippy warning about unnecessary `ref` destructuring of `MultiProductIter`
// Removed redundant `&` and `ref`
self.0.iter().fold(
(0, Some(0)),
|acc,
MultiProductIter {
iter,
iter_orig,
cur: _,
}| {
let cur_size = iter.size_hint();
let total_size = iter_orig.size_hint();
size_hint::add(size_hint::mul(acc, total_size), cur_size)
},
)
}

fn last(self) -> Option<Self::Item> {
let iter_count = self.0.len();

// (MOD) Replaced `itertools::Itertools::while_some()`
// `std::iter::Iterator::filter_map()` does the same
// thing in this case, and doesn't require the `Itertools` trait
let lasts: Self::Item = self
.0
.into_iter()
.filter_map(|multi_iter| multi_iter.iter.last())
.collect();

if lasts.len() == iter_count {
Some(lasts)
} else {
None
}
}
}

// (MOD) Copied two required functions and type alias
// From itertools::size_hint module
mod size_hint {
/// `SizeHint` is the return type of `Iterator::size_hint()`.
pub type SizeHint = (usize, Option<usize>);

/// Add `SizeHint` correctly.
#[inline]
pub fn add(a: SizeHint, b: SizeHint) -> SizeHint {
let min = a.0.saturating_add(b.0);
let max = match (a.1, b.1) {
(Some(x), Some(y)) => x.checked_add(y),
_ => None,
};

(min, max)
}

/// Multiply `SizeHint` correctly
#[inline]
pub fn mul(a: SizeHint, b: SizeHint) -> SizeHint {
let low = a.0.saturating_mul(b.0);
let hi = match (a.1, b.1) {
(Some(x), Some(y)) => x.checked_mul(y),
(Some(0), None) | (None, Some(0)) => Some(0),
_ => None,
};
(low, hi)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::{iter, mem};

use itertools::Itertools;
use proc_macro2::{Literal, Span};
use syn::{
parse::{Parse, ParseStream},
Expand All @@ -11,6 +10,8 @@ use syn::{

use crate::{comment::TestCaseComment, expr::TestCaseExpression, TestCase};

mod matrix_product;

#[derive(Debug, Default)]
pub struct TestMatrix {
variables: Vec<Vec<Expr>>,
Expand All @@ -25,15 +26,11 @@ impl TestMatrix {
pub fn cases(&self) -> impl Iterator<Item = TestCase> {
let expression = self.expression.clone();

self.variables
.iter()
.cloned()
.multi_cartesian_product()
.map(move |v| {
let mut case = TestCase::from(v);
case.expression = expression.clone();
case
})
matrix_product::multi_cartesian_product(self.variables.iter().cloned()).map(move |v| {
let mut case = TestCase::from(v);
case.expression = expression.clone();
case
})
}
}

Expand Down

0 comments on commit 19ba7c4

Please sign in to comment.