use crate::params::{AbstractCombination, MatrixCellValue};
use itertools::structs::MultiProduct;
use itertools::Itertools;
use std::iter::Cloned;
use std::slice::Iter;
#[derive(Debug, Clone)]
pub struct CombinationIterator<'a> {
inner_iterator: MultiProduct<Cloned<Iter<'a, MatrixCellValue>>>,
len: usize,
}
impl<'a> Iterator for CombinationIterator<'a> {
type Item = AbstractCombination;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self
.inner_iterator
.next()
.map(|combination_vec| AbstractCombination { cells: combination_vec })
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len, Some(self.len))
}
}
impl<'a> ExactSizeIterator for CombinationIterator<'a> {
#[inline]
fn len(&self) -> usize {
self.len
}
}
pub fn generate_combinations(axes: &[Vec<MatrixCellValue>]) -> CombinationIterator<'_> {
let len = if axes.iter().any(Vec::is_empty) {
0
} else {
axes.iter().map(Vec::len).product()
};
let inner_iterator = axes
.iter()
.map(|axis_values| axis_values.iter().cloned())
.multi_cartesian_product();
CombinationIterator { inner_iterator, len }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::params::MatrixCellValue;
fn get_all_combos(axes: &[Vec<MatrixCellValue>]) -> Vec<AbstractCombination> {
generate_combinations(axes).collect()
}
#[test]
fn test_len_calculation_and_iteration_empty_axes() {
let axes: Vec<Vec<MatrixCellValue>> = vec![];
let iter = generate_combinations(&axes);
assert_eq!(iter.len(), 0, "Length should be 0 for empty axes slice");
assert_eq!(iter.count(), 0, "Iterator should yield 0 items");
}
#[test]
fn test_len_calculation_and_iteration_one_axis_empty() {
let axis1 = vec![MatrixCellValue::Tag("A".to_string())];
let axis2: Vec<MatrixCellValue> = vec![]; let axis3 = vec![MatrixCellValue::Int(1)];
let axes = vec![axis1, axis2, axis3];
let iter = generate_combinations(&axes);
assert_eq!(iter.len(), 0, "Length should be 0 if any axis is empty");
assert_eq!(iter.count(), 0, "Iterator should yield 0 items");
}
#[test]
fn test_len_and_content_single_axis() {
let axis1 = vec![
MatrixCellValue::Tag("A".to_string()),
MatrixCellValue::Tag("B".to_string()),
];
let axes = vec![axis1.clone()];
let iter = generate_combinations(&axes);
assert_eq!(iter.len(), 2, "Length should be 2 for a single axis with 2 items");
let combinations = iter.collect::<Vec<_>>();
assert_eq!(combinations.len(), 2);
assert_eq!(combinations[0].cells, vec![axis1[0].clone()]);
assert_eq!(combinations[1].cells, vec![axis1[1].clone()]);
}
#[test]
fn test_len_and_content_two_axes() {
let axis1 = vec![
MatrixCellValue::Tag("A".to_string()),
MatrixCellValue::Tag("B".to_string()),
];
let axis2 = vec![MatrixCellValue::Int(1), MatrixCellValue::Int(2)];
let axes = vec![axis1.clone(), axis2.clone()];
let iter = generate_combinations(&axes);
assert_eq!(iter.len(), 4, "Length should be 2 * 2 = 4");
let combinations = get_all_combos(&axes);
assert_eq!(combinations.len(), 4);
assert_eq!(combinations[0].cells, vec![axis1[0].clone(), axis2[0].clone()]);
assert_eq!(combinations[1].cells, vec![axis1[0].clone(), axis2[1].clone()]);
assert_eq!(combinations[2].cells, vec![axis1[1].clone(), axis2[0].clone()]);
assert_eq!(combinations[3].cells, vec![axis1[1].clone(), axis2[1].clone()]);
}
#[test]
fn test_len_and_content_three_axes_mixed_types() {
let axis1 = vec![MatrixCellValue::Tag("X".to_string())];
let axis2 = vec![MatrixCellValue::Bool(true), MatrixCellValue::Bool(false)];
let axis3 = vec![MatrixCellValue::Unsigned(100), MatrixCellValue::Unsigned(200)];
let axes = vec![axis1.clone(), axis2.clone(), axis3.clone()];
let iter = generate_combinations(&axes);
assert_eq!(iter.len(), 4, "Length should be 1 * 2 * 2 = 4");
let combinations = get_all_combos(&axes);
assert_eq!(combinations.len(), 4);
assert_eq!(
combinations[0].cells,
vec![axis1[0].clone(), axis2[0].clone(), axis3[0].clone()]
);
assert_eq!(
combinations[1].cells,
vec![axis1[0].clone(), axis2[0].clone(), axis3[1].clone()]
);
assert_eq!(
combinations[2].cells,
vec![axis1[0].clone(), axis2[1].clone(), axis3[0].clone()]
);
assert_eq!(
combinations[3].cells,
vec![axis1[0].clone(), axis2[1].clone(), axis3[1].clone()]
);
}
#[test]
fn test_abstract_combination_id_suffix() {
let combo = AbstractCombination {
cells: vec![
MatrixCellValue::Tag("StdTokio".to_string()),
MatrixCellValue::Tag("HWM-Low".to_string()), MatrixCellValue::Int(1024),
MatrixCellValue::Unsigned(4096),
MatrixCellValue::Bool(true),
],
};
assert_eq!(combo.id_suffix(), "_StdTokio_HWM-Low_Int1024_Uint4096_Booltrue");
let combo2 = AbstractCombination {
cells: vec![
MatrixCellValue::String("My Param With Spaces".to_string()), ],
};
assert_eq!(combo2.id_suffix(), "_My_Param_With_Spaces");
let combo3 = AbstractCombination { cells: vec![] };
assert_eq!(
combo3.id_suffix(),
"_",
"Should produce a minimal suffix for empty combo"
);
}
#[test]
fn test_abstract_combination_id_suffix_with_names() {
let combo = AbstractCombination {
cells: vec![
MatrixCellValue::Tag("Uring".to_string()),
MatrixCellValue::Unsigned(512),
],
};
let names = vec!["Backend".to_string(), "BlockSize".to_string()];
assert_eq!(combo.id_suffix_with_names(&names), "_Backend-Uring_BlockSize-512");
let names_mismatch = vec!["Backend".to_string()];
assert_eq!(combo.id_suffix_with_names(&names_mismatch), "_Uring_Uint512");
}
}