use crate::engine::{ChooseFn, greedy_pack};
use crate::error::Result;
use crate::pack::{Pack, bins_to_packs};
use crate::placement::{PlacementIndex, SegmentTreeIndex};
use crate::sequence::{Item, Sequence};
use crate::strategy::PackingAlgorithm;
pub struct FirstFit;
impl PackingAlgorithm for FirstFit {
fn pack(&self, sequences: Vec<Sequence>, capacity: usize) -> Result<Vec<Pack>> {
let items: Vec<Item> = sequences.iter().map(|s| s.to_item()).collect();
let mut index = SegmentTreeIndex::with_capacity(items.len() / 2 + 1);
let bins = greedy_pack(
items.into_iter(),
capacity,
&mut index,
SegmentTreeIndex::first_fit as ChooseFn<SegmentTreeIndex>,
)?;
Ok(bins_to_packs(bins, &sequences))
}
fn name(&self) -> &'static str {
"FirstFit"
}
}
#[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 ff_bins(lens: &[usize], capacity: usize) -> Vec<crate::pack::Bin> {
let itms = items(lens);
let mut index = SegmentTreeIndex::with_capacity(itms.len() / 2 + 1);
greedy_pack(
itms.into_iter(),
capacity,
&mut index,
SegmentTreeIndex::first_fit as ChooseFn<SegmentTreeIndex>,
)
.unwrap()
}
#[test]
fn test_basic() {
let packs = FirstFit.pack(seqs(&[6, 4, 6]), 10).unwrap();
assert_eq!(packs.len(), 2);
}
#[test]
fn test_leftmost_preference() {
let bins = ff_bins(&[8, 7, 2, 3], 10);
assert_eq!(bins.len(), 2);
assert_eq!(&bins[0].items[..], &[0, 2]); assert_eq!(&bins[1].items[..], &[1, 3]); }
#[test]
fn test_skips_full_bins() {
let bins = ff_bins(&[10, 5, 5], 10);
assert_eq!(bins.len(), 2);
assert_eq!(&bins[0].items[..], &[0]);
assert_eq!(&bins[1].items[..], &[1, 2]);
}
#[test]
fn test_revisits_earlier_bins() {
let bins = ff_bins(&[7, 8, 3, 2], 10);
assert_eq!(bins.len(), 2);
assert_eq!(&bins[0].items[..], &[0, 2]); assert_eq!(&bins[1].items[..], &[1, 3]); }
#[test]
fn test_five_items_walkthrough() {
let bins = ff_bins(&[7, 5, 3, 8, 4], 10);
assert_eq!(bins.len(), 3);
assert_eq!(&bins[0].items[..], &[0, 2]); assert_eq!(&bins[1].items[..], &[1, 4]); assert_eq!(&bins[2].items[..], &[3]); }
#[test]
fn test_fewer_bins_than_next_fit() {
let bins = ff_bins(&[6, 5, 6, 5], 10);
assert_eq!(bins.len(), 3); }
#[test]
fn test_oversize_error() {
let result = FirstFit.pack(seqs(&[11]), 10);
assert!(matches!(
result,
Err(PackError::SequenceTooLong {
length: 11,
capacity: 10
})
));
}
#[test]
fn test_empty_input() {
let packs = FirstFit.pack(seqs(&[]), 10).unwrap();
assert!(packs.is_empty());
}
#[test]
fn test_single_item() {
let packs = FirstFit.pack(seqs(&[5]), 10).unwrap();
assert_eq!(packs.len(), 1);
}
#[test]
fn test_exact_capacity() {
let bins = ff_bins(&[10], 10);
assert_eq!(bins[0].remaining(), 0);
}
#[test]
fn test_all_same_size() {
let bins = ff_bins(&[5, 5, 5, 5], 10);
assert_eq!(bins.len(), 2);
assert_eq!(&bins[0].items[..], &[0, 1]);
assert_eq!(&bins[1].items[..], &[2, 3]);
}
#[test]
fn test_each_item_needs_own_bin() {
let bins = ff_bins(&[6, 7, 8, 9], 10);
assert_eq!(bins.len(), 4);
}
#[test]
fn test_many_small_items() {
let bins = ff_bins(&[1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 10);
assert_eq!(bins.len(), 1);
assert_eq!(bins[0].used, 10);
}
#[test]
fn test_validates_basic() {
let itms = items(&[7, 5, 3, 8, 4]);
let mut index = SegmentTreeIndex::with_capacity(5);
let bins = greedy_pack(
itms.iter().copied(),
10,
&mut index,
SegmentTreeIndex::first_fit as ChooseFn<SegmentTreeIndex>,
)
.unwrap();
validate_solution(&itms, &bins, 10).unwrap();
}
#[test]
fn test_validates_many_items() {
let lens: Vec<usize> = vec![6, 4, 3, 7, 5, 5, 2, 8, 1, 9];
let itms = items(&lens);
let mut index = SegmentTreeIndex::with_capacity(10);
let bins = greedy_pack(
itms.iter().copied(),
10,
&mut index,
SegmentTreeIndex::first_fit as ChooseFn<SegmentTreeIndex>,
)
.unwrap();
validate_solution(&itms, &bins, 10).unwrap();
}
#[test]
fn test_name() {
assert_eq!(FirstFit.name(), "FirstFit");
}
}