use std::collections::HashMap;
use std::hash::Hash;
#[derive(Debug, Clone)]
pub struct PartitionSolution<T: Clone + Ord + Hash, B: Clone + Eq + Hash> {
pub coefficient: usize,
pub bins: Vec<(B, Vec<T>)>,
}
pub fn partitions<T: Clone + Ord + Hash, B: Clone + Ord + Hash>(
elements: &[T],
bins: &[(B, usize)],
fill_last: bool,
repeat: bool,
) -> Vec<PartitionSolution<T, B>> {
if bins.is_empty() || elements.is_empty() {
return Vec::new();
}
let bin_sum: usize = bins.iter().map(|b| b.1).sum();
let total = elements.len();
match total.cmp(&bin_sum) {
std::cmp::Ordering::Less => return Vec::new(),
std::cmp::Ordering::Equal => {}
std::cmp::Ordering::Greater => {
if !fill_last && (!repeat || !total.is_multiple_of(bin_sum)) {
return Vec::new();
}
}
}
let mut element_groups: HashMap<T, usize> = HashMap::new();
for e in elements {
*element_groups.entry(e.clone()).or_insert(0) += 1;
}
let mut element_counts: Vec<(T, usize)> = element_groups.into_iter().collect();
element_counts.sort_by(|a, b| a.0.cmp(&b.0));
let mut sorted_bins = bins.to_vec();
if fill_last {
let last = sorted_bins.last_mut().unwrap();
last.1 += total - bin_sum;
}
if repeat {
for _ in 1..(total / bin_sum) {
sorted_bins.extend_from_slice(bins);
}
}
sorted_bins.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
let mut results: Vec<PartitionSolution<T, B>> = Vec::new();
fill_rec(
&sorted_bins,
&mut element_counts,
&mut Vec::new(),
&mut Vec::new(),
&mut Vec::new(),
&mut results,
);
for sol in &mut results {
let mut coeff = 1usize;
let mut counter = Vec::new();
for (elem, _total) in &element_counts {
counter.clear();
for (_, bin) in &sol.bins {
let c = bin.iter().filter(|be| *be == elem).count();
if c > 0 {
counter.push(c);
}
}
coeff *= multinomial(&counter);
}
let mut bin_groups: HashMap<&(B, Vec<T>), usize> = HashMap::new();
for named_bin in &sol.bins {
*bin_groups.entry(named_bin).or_insert(0) += 1;
}
for (_, count) in bin_groups {
coeff /= factorial(count);
}
sol.coefficient = coeff;
}
results
}
fn fill_rec<T: Clone + Ord + Hash, B: Clone + Eq + Hash>(
bins: &[(B, usize)],
elem_counts: &mut [(T, usize)],
single_buf: &mut Vec<T>,
single_results: &mut Vec<Vec<T>>,
accum: &mut Vec<(B, Vec<T>)>,
results: &mut Vec<PartitionSolution<T, B>>,
) {
if bins.is_empty() {
if elem_counts.iter().all(|(_, c)| *c == 0) {
results.push(PartitionSolution {
coefficient: 1,
bins: accum.clone(),
});
}
return;
}
let (bin_id, bin_len) = &bins[0];
let bin_id = bin_id.clone();
let bin_len = *bin_len;
single_results.clear();
fill_bin(bin_len, elem_counts, single_buf, single_results);
for fill in std::mem::take(single_results) {
if let Some(last) = accum.last()
&& last.0 == bin_id
&& fill.len() == last.1.len()
&& fill < last.1
{
continue;
}
for x in &fill {
if let Some((_, c)) = elem_counts.iter_mut().find(|(e, _)| *e == *x) {
*c -= 1;
}
}
accum.push((bin_id.clone(), fill.clone()));
fill_rec(
&bins[1..],
elem_counts,
single_buf,
single_results,
accum,
results,
);
accum.pop();
for x in &fill {
if let Some((_, c)) = elem_counts.iter_mut().find(|(e, _)| *e == *x) {
*c += 1;
}
}
}
}
fn fill_bin<T: Clone>(
len: usize,
elem_counts: &mut [(T, usize)],
accum: &mut Vec<T>,
results: &mut Vec<Vec<T>>,
) {
if len == 0 {
results.push(accum.clone());
return;
}
let n = elem_counts.len();
for i in 0..n {
let count = elem_counts[i].1;
if count > 0 {
elem_counts[i].1 = count - 1;
let name = elem_counts[i].0.clone();
accum.push(name);
fill_bin(len - 1, &mut elem_counts[i..], accum, results);
accum.pop();
elem_counts[i].1 = count;
}
}
}
fn factorial(n: usize) -> usize {
(1..=n).product()
}
fn multinomial(counts: &[usize]) -> usize {
let total: usize = counts.iter().sum();
let mut result = factorial(total);
for &c in counts {
result /= factorial(c);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn partition_basic_exact() {
let elements = vec![1i32, 3, 2, 3, 1];
let bins = vec![('f', 2), ('g', 2), ('f', 1)];
let sols = partitions(&elements, &bins, false, false);
assert!(!sols.is_empty(), "should produce at least one partition");
for sol in &sols {
let total: usize = sol.bins.iter().map(|(_, v)| v.len()).sum();
assert_eq!(total, 5, "each solution must partition all 5 elements");
assert!(sol.coefficient > 0, "coefficient must be positive");
}
}
#[test]
fn empty_inputs() {
assert!(partitions::<i32, char>(&[], &[('a', 1)], false, false).is_empty());
assert!(partitions::<i32, char>(&[1], &[], false, false).is_empty());
}
}