Algorithm-BinPack

 view release on metacpan or  search on metacpan

lib/Algorithm/BinPack.pm  view on Meta::CPAN

package Algorithm::BinPack;

our $VERSION = 0.5;

=head1 NAME

Algorithm::BinPack - efficiently pack items into bins

=head1 SYNOPSIS

C<Algorithm::BinPack> efficiently packs items into bins. The bins are 
given a maximum size, and items are packed in with as little empty 
space as possible. An example use would be backing up files to CD, 
while minimizing the number of discs required. 

    my $bp = Algorithm::BinPack->new(binsize => 4);

    $bp->add_item(label => "one",   size => 1);
    $bp->add_item(label => "two",   size => 2);
    $bp->add_item(label => "three", size => 3);
    $bp->add_item(label => "four",  size => 4);

    for ($bp->pack_bins) {
        print "Bin size: ", $_->{size},  "\n";
        print "  Item: ",   $_->{label}, "\n" for @{ $_->{items} };
    }

=cut

use strict;
use warnings;
use Carp;

=head1 METHODS

=over 8

=item new

Creates a new C<Algorithm::BinPack> object. The maximum bin size is 
specified as a named argument 'binsize', and is required. A fudge 
factor may be specified as a named argument 'fudge'. If a fudge factor 
is specified, item sizes will be rounded up to a number divisible by 
the fudge factor. This can help keep items with similar sizes in order 
by their labels.

    my $bp = Algorithm::BinPack->new(binsize => 4);
    my $bp = Algorithm::BinPack->new(binsize => 100, fudge => 10);

=cut

sub new {
    my $name = shift;
    my $self = { @_ };

    checkargs($self, qw(binsize)) or return;

    $self->{bins} = [];

    bless $self, $name;
}

=item add_item

Adds an item to be packed into a bin. Required named arguments are 
'label' and 'size', but any others can be specified, and will be saved. 
An optional 'bin' argument can be used to manually put an item into the 
specified bin.

    $bp->add_item(label => 'one',  size => 1);
    $bp->add_item(label => 'two',  size => 2, desc => 'The second numeral');
    $bp->add_item(label => 'zero', size => 3, bin => 0);
    $bp->add_item(qw(label three size 3));
    $bp->add_item(qw(label four size 4 random key));

=cut

sub add_item {
    my $self = shift;
    my $item = { @_ };

    checkargs($item, qw(label size)) or return;

    if (exists $item->{bin}) {
        my ($bins, $max_binsize) = @{$self}{qw(bins binsize)};
        my ($bin, $size, $label) = @{$item}{qw(bin size label)};

        if ($size > $max_binsize) {
            carp("'$label' too big to fit in a bin\n");
            return 0;
        }

        if ($bin !~ /^\d+$/) {
            carp("Bin number must be numeric: $bin\n");
            return 0;
        }

        my $binsize = $bins->[$bin]{size} || 0;
        if ($size + $binsize > $max_binsize) {
            carp("'$label' too big to fit in a bin #$bin size: $binsize\n");
            return 0;
        }

        push @{ $bins->[$bin]{items} }, $item;
        $bins->[$bin]{size} += $size;

        return 1;
    } else {
        if ($self->{fudge}) {
            require POSIX;

            my $fudge = $self->{fudge};
            my $size  = $item->{size};

            $item->{fudgesize} = POSIX::ceil($size/$fudge)*$fudge;
        }

        push @{ $self->{items} }, $item;
    }
}



( run in 2.871 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )