File Coverage

blib/lib/Algorithm/BinPack.pm
Criterion Covered Total %
statement 74 78 94.8
branch 19 22 86.3
condition 4 8 50.0
subroutine 10 10 100.0
pod 4 6 66.6
total 111 124 89.5


line stmt bran cond sub pod time code
1             package Algorithm::BinPack;
2              
3             our $VERSION = 0.5;
4              
5             =head1 NAME
6              
7             Algorithm::BinPack - efficiently pack items into bins
8              
9             =head1 SYNOPSIS
10              
11             C efficiently packs items into bins. The bins are
12             given a maximum size, and items are packed in with as little empty
13             space as possible. An example use would be backing up files to CD,
14             while minimizing the number of discs required.
15              
16             my $bp = Algorithm::BinPack->new(binsize => 4);
17              
18             $bp->add_item(label => "one", size => 1);
19             $bp->add_item(label => "two", size => 2);
20             $bp->add_item(label => "three", size => 3);
21             $bp->add_item(label => "four", size => 4);
22              
23             for ($bp->pack_bins) {
24             print "Bin size: ", $_->{size}, "\n";
25             print " Item: ", $_->{label}, "\n" for @{ $_->{items} };
26             }
27              
28             =cut
29              
30 1     1   23485 use strict;
  1         2  
  1         42  
31 1     1   5 use warnings;
  1         2  
  1         28  
32 1     1   5 use Carp;
  1         6  
  1         642  
33              
34             =head1 METHODS
35              
36             =over 8
37              
38             =item new
39              
40             Creates a new C object. The maximum bin size is
41             specified as a named argument 'binsize', and is required. A fudge
42             factor may be specified as a named argument 'fudge'. If a fudge factor
43             is specified, item sizes will be rounded up to a number divisible by
44             the fudge factor. This can help keep items with similar sizes in order
45             by their labels.
46              
47             my $bp = Algorithm::BinPack->new(binsize => 4);
48             my $bp = Algorithm::BinPack->new(binsize => 100, fudge => 10);
49              
50             =cut
51              
52             sub new {
53 1     1 1 13 my $name = shift;
54 1         5 my $self = { @_ };
55              
56 1 50       6 checkargs($self, qw(binsize)) or return;
57              
58 1         3 $self->{bins} = [];
59              
60 1         6 bless $self, $name;
61             }
62              
63             =item add_item
64              
65             Adds an item to be packed into a bin. Required named arguments are
66             'label' and 'size', but any others can be specified, and will be saved.
67             An optional 'bin' argument can be used to manually put an item into the
68             specified bin.
69              
70             $bp->add_item(label => 'one', size => 1);
71             $bp->add_item(label => 'two', size => 2, desc => 'The second numeral');
72             $bp->add_item(label => 'zero', size => 3, bin => 0);
73             $bp->add_item(qw(label three size 3));
74             $bp->add_item(qw(label four size 4 random key));
75              
76             =cut
77              
78             sub add_item {
79 15     15 1 5424 my $self = shift;
80 15         45 my $item = { @_ };
81              
82 15 100       26 checkargs($item, qw(label size)) or return;
83              
84 13 100       27 if (exists $item->{bin}) {
85 8         11 my ($bins, $max_binsize) = @{$self}{qw(bins binsize)};
  8         15  
86 8         12 my ($bin, $size, $label) = @{$item}{qw(bin size label)};
  8         13  
87              
88 8 100       19 if ($size > $max_binsize) {
89 2         152 carp("'$label' too big to fit in a bin\n");
90 2         102 return 0;
91             }
92              
93 6 100       26 if ($bin !~ /^\d+$/) {
94 1         97 carp("Bin number must be numeric: $bin\n");
95 1         63 return 0;
96             }
97              
98 5   100     20 my $binsize = $bins->[$bin]{size} || 0;
99 5 100       11 if ($size + $binsize > $max_binsize) {
100 2         232 carp("'$label' too big to fit in a bin #$bin size: $binsize\n");
101 2         123 return 0;
102             }
103              
104 3         5 push @{ $bins->[$bin]{items} }, $item;
  3         9  
105 3         5 $bins->[$bin]{size} += $size;
106              
107 3         7 return 1;
108             } else {
109 5 50       12 if ($self->{fudge}) {
110 0         0 require POSIX;
111              
112 0         0 my $fudge = $self->{fudge};
113 0         0 my $size = $item->{size};
114              
115 0         0 $item->{fudgesize} = POSIX::ceil($size/$fudge)*$fudge;
116             }
117              
118 5         4 push @{ $self->{items} }, $item;
  5         14  
119             }
120             }
121              
122             =item prefill_bin
123              
124             (Deprecated method) C now knows how to handle the 'bin'
125             argument directly, so this method is redundant.
126              
127             =cut
128              
129             sub prefill_bin {
130 8     8 1 3829 my $self = shift;
131 8 100       35 checkargs({ @_ }, qw(label size bin)) or return;
132 5         17 $self->add_item(@_);
133             }
134              
135             =item pack_bins
136              
137             Packs the items into bins. This method tries to leave as little empty
138             space in each bin as possible. It returns a list of hashrefs with the
139             key 'size' containing the total bin size, and 'items' containing an
140             arrayref holding the items in the bin. Each item is in turn a hashref
141             containing the keys 'label', 'size', and any others added to the item.
142             If a fudge factor was used, each item will contain a key 'fudgesize',
143             which is the size this item was fudged to.
144              
145             for my $bin ($bp->pack_bins) {
146             print "Bin size: ", $bin->{size}, "\n";
147            
148             for my $item (@{ $bin->{items} }) {
149             printf " %-6s %-20s\n", $_, $item->{$_} for keys %{ $item };
150             print " ---\n";
151             }
152             }
153              
154             =cut
155              
156             sub pack_bins {
157 3     3 1 14 my $self = shift;
158 3         5 my $binsize = $self->{binsize};
159              
160 1     1   7 no warnings 'uninitialized';
  1         2  
  1         509  
161              
162 3         3 my @bins = @{ $self->{bins} };
  3         8  
163              
164 3         10 for my $item (sort_items($self->{items})) {
165 13         13 my ($size, $label) = @{$item}{qw(size label)};
  13         27  
166              
167 13 100       33 if ($size > $binsize) {
168 1         97 carp "'$label' too big to fit in a bin\n";
169 1         31 next;
170             }
171              
172 12         12 my $i = 0;
173 12         58 $i++ until $bins[$i]{size} + $size <= $binsize;
174              
175 12         12 push @{ $bins[$i]{items} }, $item;
  12         29  
176 12         24 $bins[$i]{size} += $size;
177             }
178              
179 3         14 return @bins;
180             }
181              
182             sub checkargs {
183 24     24 0 44 my ($href, @args) = @_;
184              
185 24         26 my $success = 1;
186              
187 24         34 for (@args) {
188 55 100       129 unless (exists $href->{$_}) {
189 5         576 carp "Missing argument '$_'";
190 5         261 $success = 0;
191             }
192             }
193              
194 24         68 return $success;
195             }
196              
197             sub sort_items {
198 3     3 0 5 my $items = shift;
199              
200 13   33     45 sort {
201             # use fudgesize if it's there, otherwise use actual
202 3         13 my $asize = $a->{fudgesize} || $a->{size};
203 13   33     40 my $bsize = $b->{fudgesize} || $b->{size};
204              
205 13 50       46 $bsize <=> $asize
206             ||
207             $a->{label} cmp $b->{label}
208              
209 3         3 } @{ $items };
210             }
211              
212             1;
213              
214             =head1 SEE ALSO
215              
216             This module implements the bin packing algorithm described in 'The
217             Algorithm Design Manual' by Steven S. Skiena.
218              
219             This module is similar to L, but has a few key
220             differences. The algorithms in Algorithm::Bucketizer are based on
221             optimization by multiple iterations, so the module is set up
222             differently. By contrast, the algorithm used in Algorithm::BinPack is
223             predictable, and does not require multiple iterations. The name also
224             reflects the well-known name of the problem. Searching for variations
225             on "bin packing" finds more relevant results than variations on
226             "bucketizer".
227              
228             =head1 AUTHOR
229              
230             Carey Tilden Erevdiablo@wd39.comE
231              
232             =head1 CONTRIBUTORS
233              
234             Andrew 'Terra' Gillespie Ealgorithm_binpack@Tech.FutureQuest.netE - C
235              
236             =head1 COPYRIGHT AND LICENSE
237              
238             Copyright (C) 2004-05 by Carey Tilden
239              
240             This code is dual licensed. You may choose from one of the following:
241              
242             =over 4
243              
244             =item http://creativecommons.org/licenses/by/1.0
245              
246             A Creative Commons license that allows free use, while requiring attribution.
247              
248             =item http://d.revinc.org/pages/license
249              
250             The I Really Could Care Less About You Public License.
251              
252             =back
253              
254             =cut