use crate::engine::{ChooseFn, greedy_pack};
use crate::error::Result;
use crate::pack::{Pack, bins_to_packs};
use crate::placement::{BTreeRemainingIndex, PlacementIndex};
use crate::sequence::{Item, Sequence};
use crate::strategy::PackingAlgorithm;
pub struct BestFitDecreasing;
impl PackingAlgorithm for BestFitDecreasing {
fn pack(&self, sequences: Vec<Sequence>, capacity: usize) -> Result<Vec<Pack>> {
let mut items: Vec<Item> = sequences.iter().map(|s| s.to_item()).collect();
items.sort_unstable_by(|a, b| b.len.cmp(&a.len));
let mut index = BTreeRemainingIndex::new();
let bins = greedy_pack(
items.into_iter(),
capacity,
&mut index,
BTreeRemainingIndex::best_fit as ChooseFn<BTreeRemainingIndex>,
)?;
Ok(bins_to_packs(bins, &sequences))
}
fn name(&self) -> &'static str {
"BestFitDecreasing"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::greedy_pack;
use crate::error::PackError;
use crate::sequence::Item;
use crate::validation::validate_solution;
fn seqs(lens: &[usize]) -> Vec<Sequence> {
lens.iter()
.enumerate()
.map(|(id, &len)| Sequence::new(id, len))
.collect()
}
fn items(lens: &[usize]) -> Vec<Item> {
lens.iter()
.enumerate()
.map(|(id, &len)| Item { id, len })
.collect()
}
fn items_sorted_desc(lens: &[usize]) -> Vec<Item> {
let mut itms: Vec<Item> = lens
.iter()
.enumerate()
.map(|(id, &len)| Item { id, len })
.collect();
itms.sort_unstable_by(|a, b| b.len.cmp(&a.len));
itms
}
fn bfd_bins(lens: &[usize], capacity: usize) -> Vec<crate::pack::Bin> {
let itms = items_sorted_desc(lens);
let mut index = BTreeRemainingIndex::new();
greedy_pack(
itms.into_iter(),
capacity,
&mut index,
BTreeRemainingIndex::best_fit as ChooseFn<BTreeRemainingIndex>,
)
.unwrap()
}
#[test]
fn test_sorting_reduces_bins() {
let packs = BestFitDecreasing.pack(seqs(&[3, 8, 5, 7]), 10).unwrap();
assert_eq!(packs.len(), 3);
}
#[test]
fn test_sort_order_verified() {
let bins = bfd_bins(&[3, 5, 8], 10);
assert_eq!(bins.len(), 2);
assert_eq!(bins[0].used, 8);
assert_eq!(bins[1].used, 8); }
#[test]
fn test_large_items_first_with_tight_placement() {
let bins = bfd_bins(&[3, 4, 5, 7, 8], 10);
assert_eq!(bins.len(), 3);
assert_eq!(bins[1].used, 10);
}
#[test]
fn test_already_sorted_input() {
let bins = bfd_bins(&[8, 7, 5, 4, 3], 10);
assert_eq!(bins.len(), 3);
}
#[test]
fn test_tight_fit_after_sort() {
let bins = bfd_bins(&[2, 8, 3, 7], 10);
assert_eq!(bins.len(), 2);
assert_eq!(bins[0].remaining(), 0); assert_eq!(bins[1].remaining(), 0); }
#[test]
fn test_bfd_at_least_as_good_as_bf() {
let lens = &[3, 8, 3, 7, 2, 6, 4, 1];
let bf = {
let itms = items(lens);
let mut index = BTreeRemainingIndex::new();
greedy_pack(
itms.into_iter(),
10,
&mut index,
BTreeRemainingIndex::best_fit as ChooseFn<BTreeRemainingIndex>,
)
.unwrap()
};
let bfd = bfd_bins(lens, 10);
assert!(bfd.len() <= bf.len());
}
#[test]
fn test_oversize_error() {
let result = BestFitDecreasing.pack(seqs(&[11]), 10);
assert!(matches!(
result,
Err(PackError::SequenceTooLong {
length: 11,
capacity: 10
})
));
}
#[test]
fn test_oversize_detected_after_sort() {
let result = BestFitDecreasing.pack(seqs(&[3, 15, 5]), 10);
assert!(result.is_err());
}
#[test]
fn test_empty_input() {
let packs = BestFitDecreasing.pack(seqs(&[]), 10).unwrap();
assert!(packs.is_empty());
}
#[test]
fn test_single_item() {
let packs = BestFitDecreasing.pack(seqs(&[5]), 10).unwrap();
assert_eq!(packs.len(), 1);
}
#[test]
fn test_exact_capacity() {
let bins = bfd_bins(&[10], 10);
assert_eq!(bins[0].remaining(), 0);
}
#[test]
fn test_all_same_size() {
let bins = bfd_bins(&[5, 5, 5, 5], 10);
assert_eq!(bins.len(), 2);
}
#[test]
fn test_each_item_needs_own_bin() {
let bins = bfd_bins(&[6, 7, 8, 9], 10);
assert_eq!(bins.len(), 4);
}
#[test]
fn test_many_small_items() {
let bins = bfd_bins(&[1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 10);
assert_eq!(bins.len(), 1);
}
#[test]
fn test_validates_basic() {
let original = items(&[3, 5, 8, 7, 4]);
let sorted = items_sorted_desc(&[3, 5, 8, 7, 4]);
let mut index = BTreeRemainingIndex::new();
let bins = greedy_pack(
sorted.into_iter(),
10,
&mut index,
BTreeRemainingIndex::best_fit as ChooseFn<BTreeRemainingIndex>,
)
.unwrap();
validate_solution(&original, &bins, 10).unwrap();
}
#[test]
fn test_validates_many_items() {
let original = items(&[6, 4, 3, 7, 5, 5, 2, 8, 1, 9]);
let sorted = items_sorted_desc(&[6, 4, 3, 7, 5, 5, 2, 8, 1, 9]);
let mut index = BTreeRemainingIndex::new();
let bins = greedy_pack(
sorted.into_iter(),
10,
&mut index,
BTreeRemainingIndex::best_fit as ChooseFn<BTreeRemainingIndex>,
)
.unwrap();
validate_solution(&original, &bins, 10).unwrap();
}
#[test]
fn test_name() {
assert_eq!(BestFitDecreasing.name(), "BestFitDecreasing");
}
#[test]
fn test_pack_through_trait() {
let packs = BestFitDecreasing.pack(seqs(&[3, 7, 5, 5]), 10).unwrap();
assert_eq!(packs.len(), 2);
}
}