use std::cmp::Ordering;
use crate::common::*;
pub struct ManhattanPermutationIter<'a, T> {
sorted_dists: Vec<Vec<(usize, T)>>,
combination_fn: &'a dyn Fn(&[T]) -> Option<T>,
orderings: Vec<Vec<usize>>,
state: Vec<usize>,
distance_threshold: usize,
expand_distance_threshold: bool,
new_iter: bool,
}
impl<'a, T> ManhattanPermutationIter<'a, T>
where
T: Copy + PartialOrd + num_traits::Bounded + num_traits::Zero + core::ops::Sub<Output=T>,
{
pub fn new<E: AsRef<[T]>, F: Fn(&[T]) -> Option<T>>(factor_iter: impl Iterator<Item=E>, combination_fn: &'a F) -> Self {
let sorted_dists: Vec<Vec<(usize, T)>> = factor_iter
.map(|factor_dist| {
let mut sorted_elements: Vec<(usize, T)> = factor_dist.as_ref().iter().cloned().enumerate().collect();
sorted_elements.sort_by(|(_idx_a, element_a), (_idx_b, element_b)| element_b.partial_cmp(element_a).unwrap_or(Ordering::Equal));
sorted_elements
})
.collect();
let factor_count = sorted_dists.len();
let orderings = build_orderings(&sorted_dists, &combination_fn);
Self {
sorted_dists,
combination_fn,
orderings,
state: vec![0; factor_count],
distance_threshold: 0,
expand_distance_threshold: false,
new_iter: true,
}
}
pub fn factor_count(&self) -> usize {
self.sorted_dists.len()
}
fn execute_combine_fn(&self, factors: &[T]) -> Option<T> {
(self.combination_fn)(&factors)
}
fn state_to_result(&self) -> Option<(Vec<usize>, T)> {
let ordering_idx = if self.distance_threshold > 0 {
(self.distance_threshold-1).min(2)
} else {
0
};
let mut swizzled_state = vec![0; self.factor_count()];
for (i, &idx) in self.orderings[ordering_idx].iter().enumerate() {
swizzled_state[idx] = self.state[i];
}
let mut factors = Vec::with_capacity(self.factor_count());
for (slot_idx, sorted_idx) in swizzled_state.iter().enumerate() {
factors.push(self.sorted_dists[slot_idx][*sorted_idx].1);
}
let result = swizzled_state.iter()
.enumerate()
.map(|(slot_idx, sorted_factor_idx)| self.sorted_dists[slot_idx][*sorted_factor_idx].0)
.collect();
self.execute_combine_fn(&factors)
.map(|combined_val| (result, combined_val))
}
fn step(&mut self) -> (bool, Option<(Vec<usize>, T)>) {
let ordering_idx = if self.distance_threshold > 0 {
(self.distance_threshold-1).min(2)
} else {
0
};
let factor_count = self.factor_count();
let mut found_factor = factor_count-1;
let mut swizzled_factor = self.orderings[ordering_idx][found_factor];
while found_factor > 0 && (self.state[found_factor-1] == 0 || self.state[found_factor] == self.sorted_dists[swizzled_factor].len()-1) {
found_factor -= 1;
swizzled_factor = self.orderings[ordering_idx][found_factor];
}
if found_factor == 0 || self.expand_distance_threshold {
self.expand_distance_threshold = false;
self.distance_threshold += 1;
let mut remaining_distance = self.distance_threshold;
for (i, factor) in self.state.iter_mut().enumerate() {
let swizzled_i = self.orderings[ordering_idx][i];
let max_factor = self.sorted_dists[swizzled_i].len()-1;
if remaining_distance > max_factor {
*factor = max_factor;
remaining_distance -= max_factor;
} else {
*factor = remaining_distance;
remaining_distance = 0;
}
}
if remaining_distance > 0 {
return (false, None);
}
} else {
self.state[found_factor-1] -= 1;
let mut allocated_distance = 0;
for i in 0..(found_factor) {
allocated_distance += self.state[i];
}
let mut remaining_distance = self.distance_threshold - allocated_distance;
for i in found_factor..factor_count {
let swizzled_i = self.orderings[ordering_idx][i];
let max_factor = self.sorted_dists[swizzled_i].len()-1;
if remaining_distance > max_factor {
self.state[i] = max_factor;
remaining_distance -= max_factor;
} else {
self.state[i] = remaining_distance;
remaining_distance = 0;
}
}
if remaining_distance > 0 {
self.expand_distance_threshold = true;
return (true, None);
}
}
(true, self.state_to_result())
}
}
impl<T> Iterator for ManhattanPermutationIter<'_, T>
where
T: Copy + PartialOrd + num_traits::Bounded + num_traits::Zero + core::ops::Sub<Output=T>,
{
type Item = (Vec<usize>, T);
fn next(&mut self) -> Option<Self::Item> {
if self.new_iter {
self.new_iter = false;
return self.state_to_result();
}
loop {
let (keep_going, result_option) = self.step();
if !keep_going {
return None;
}
if result_option.is_some() {
return result_option;
}
}
}
}