use crate::error::{PackError, Result};
use crate::pack::{Bin, Pack, bins_to_packs};
use crate::sequence::{Item, Sequence};
use crate::strategy::PackingAlgorithm;
const DEFAULT_K: usize = 10;
pub struct Harmonic {
k: usize,
}
impl Harmonic {
pub fn new(k: usize) -> Self {
assert!(k >= 2, "Harmonic-K requires k >= 2");
Self { k }
}
fn classify(&self, len: usize, capacity: usize) -> usize {
if len == 0 {
return self.k - 1;
}
let ratio = capacity / len; if ratio <= 1 {
0
} else {
(ratio - 1).min(self.k - 1)
}
}
fn max_items_for_class(&self, class: usize) -> usize {
if class == self.k - 1 {
usize::MAX } else {
class + 1
}
}
}
impl Default for Harmonic {
fn default() -> Self {
Self::new(DEFAULT_K)
}
}
impl PackingAlgorithm for Harmonic {
fn pack(&self, sequences: Vec<Sequence>, capacity: usize) -> Result<Vec<Pack>> {
let items: Vec<Item> = sequences.iter().map(|s| s.to_item()).collect();
if items.is_empty() {
return Ok(Vec::new());
}
let mut bins: Vec<Bin> = Vec::new();
let mut open_bins: Vec<Option<usize>> = vec![None; self.k];
let mut catchall_open: Vec<usize> = Vec::new();
for item in &items {
if item.len > capacity {
return Err(PackError::SequenceTooLong {
length: item.len,
capacity,
});
}
let class = self.classify(item.len, capacity);
if class == self.k - 1 {
let mut placed = false;
for &bin_id in &catchall_open {
if bins[bin_id].remaining() >= item.len {
bins[bin_id].used += item.len;
bins[bin_id].items.push(item.id);
placed = true;
break;
}
}
if !placed {
let bin_id = bins.len();
let mut bin = Bin::new(bin_id, capacity);
bin.used = item.len;
bin.items.push(item.id);
bins.push(bin);
catchall_open.push(bin_id);
}
} else {
let max_items = self.max_items_for_class(class);
match open_bins[class] {
Some(bin_id)
if bins[bin_id].remaining() >= item.len
&& bins[bin_id].items.len() < max_items =>
{
bins[bin_id].used += item.len;
bins[bin_id].items.push(item.id);
if bins[bin_id].items.len() >= max_items {
open_bins[class] = None;
}
}
_ => {
let bin_id = bins.len();
let mut bin = Bin::new(bin_id, capacity);
bin.used = item.len;
bin.items.push(item.id);
bins.push(bin);
if max_items <= 1 {
open_bins[class] = None;
} else {
open_bins[class] = Some(bin_id);
}
}
}
}
}
Ok(bins_to_packs(bins, &sequences))
}
fn name(&self) -> &'static str {
"Harmonic"
}
}
#[cfg(test)]
mod tests {
use super::*;
fn seqs(lens: &[usize]) -> Vec<Sequence> {
lens.iter()
.enumerate()
.map(|(id, &len)| Sequence::new(id, len))
.collect()
}
fn harmonic() -> Harmonic {
Harmonic::default()
}
#[test]
fn test_classify_large_items() {
let h = harmonic();
assert_eq!(h.classify(51, 100), 0);
assert_eq!(h.classify(100, 100), 0);
}
#[test]
fn test_classify_medium_items() {
let h = harmonic();
assert_eq!(h.classify(34, 100), 1);
assert_eq!(h.classify(50, 100), 1);
}
#[test]
fn test_classify_small_items() {
let h = harmonic();
assert_eq!(h.classify(26, 100), 2);
assert_eq!(h.classify(33, 100), 2);
}
#[test]
fn test_classify_tiny_items() {
let h = harmonic();
assert_eq!(h.classify(1, 100), 9);
assert_eq!(h.classify(5, 100), 9);
}
#[test]
fn test_classify_zero_length() {
let h = harmonic();
assert_eq!(h.classify(0, 100), 9);
}
#[test]
fn test_empty_input() {
let packs = harmonic().pack(seqs(&[]), 10).unwrap();
assert!(packs.is_empty());
}
#[test]
fn test_single_item() {
let packs = harmonic().pack(seqs(&[5]), 10).unwrap();
assert_eq!(packs.len(), 1);
}
#[test]
fn test_oversize_error() {
let result = harmonic().pack(seqs(&[11]), 10);
assert!(matches!(
result,
Err(PackError::SequenceTooLong {
length: 11,
capacity: 10
})
));
}
#[test]
fn test_large_items_get_own_bins() {
let packs = harmonic().pack(seqs(&[60, 70, 80]), 100).unwrap();
assert_eq!(packs.len(), 3);
}
#[test]
fn test_medium_items_pair_up() {
let packs = harmonic().pack(seqs(&[40, 40]), 100).unwrap();
assert_eq!(packs.len(), 1);
}
#[test]
fn test_medium_items_three_need_two_bins() {
let packs = harmonic().pack(seqs(&[40, 40, 40]), 100).unwrap();
assert_eq!(packs.len(), 2);
}
#[test]
fn test_small_items_triple_up() {
let packs = harmonic().pack(seqs(&[30, 30, 30]), 100).unwrap();
assert_eq!(packs.len(), 1);
}
#[test]
fn test_catch_all_first_fit() {
let packs = harmonic()
.pack(seqs(&[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), 100)
.unwrap();
assert_eq!(packs.len(), 1);
}
#[test]
fn test_different_classes_get_separate_bins() {
let packs = harmonic().pack(seqs(&[60, 40]), 100).unwrap();
assert_eq!(packs.len(), 2);
}
#[test]
fn test_all_sequences_present() {
let lens = &[60, 40, 30, 25, 10, 5, 80, 35];
let packs = harmonic().pack(seqs(lens), 100).unwrap();
let mut all_ids: Vec<usize> = packs
.iter()
.flat_map(|p| p.sequences.iter().map(|s| s.id))
.collect();
all_ids.sort();
let expected: Vec<usize> = (0..lens.len()).collect();
assert_eq!(all_ids, expected);
}
#[test]
fn test_no_bin_exceeds_capacity() {
let lens = &[60, 40, 30, 25, 10, 5, 80, 35, 15, 45, 70, 20];
let capacity = 100;
let packs = harmonic().pack(seqs(lens), capacity).unwrap();
for pack in &packs {
assert!(pack.used_capacity() <= capacity);
}
}
#[test]
fn test_k_equals_2() {
let h = Harmonic::new(2);
let packs = h.pack(seqs(&[60, 40, 30, 20, 10]), 100).unwrap();
let total_items: usize = packs.iter().map(|p| p.sequences.len()).sum();
assert_eq!(total_items, 5);
}
#[test]
#[should_panic(expected = "k >= 2")]
fn test_k_less_than_2_panics() {
Harmonic::new(1);
}
#[test]
fn test_name() {
assert_eq!(harmonic().name(), "Harmonic");
}
#[test]
fn test_many_small_items() {
let packs = harmonic()
.pack(seqs(&[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), 10)
.unwrap();
assert_eq!(packs.len(), 1);
}
#[test]
fn test_exact_capacity() {
let packs = harmonic().pack(seqs(&[10]), 10).unwrap();
assert_eq!(packs.len(), 1);
assert_eq!(packs[0].used_capacity(), 10);
}
#[test]
fn test_all_same_size_large() {
let packs = harmonic().pack(seqs(&[6, 6, 6, 6]), 10).unwrap();
assert_eq!(packs.len(), 4);
}
#[test]
fn test_all_same_size_medium() {
let packs = harmonic().pack(seqs(&[4, 4, 4, 4]), 10).unwrap();
assert_eq!(packs.len(), 2); }
}