File Coverage

blib/lib/Box/Calc.pm
Criterion Covered Total %
statement 194 219 88.5
branch 23 36 63.8
condition 22 55 40.0
subroutine 26 29 89.6
pod 18 19 94.7
total 283 358 79.0


line stmt bran cond sub pod time code
1             package Box::Calc;
2             $Box::Calc::VERSION = '1.0200';
3 7     7   41619 use strict;
  7         13  
  7         166  
4 7     7   4356 use Moose;
  7         2705066  
  7         54  
5 7     7   51508 use Box::Calc::BoxType;
  7         98572  
  7         310  
6 7     7   4552 use Box::Calc::Item;
  7         1937  
  7         245  
7 7     7   4402 use Box::Calc::Box;
  7         9859  
  7         298  
8 7     7   62 use List::MoreUtils qw(natatime);
  7         14  
  7         115  
9 7     7   3111 use List::Util qw(max);
  7         13  
  7         492  
10 7     7   33 use Ouch;
  7         16  
  7         479  
11 7     7   35 use Log::Any qw($log);
  7         14  
  7         59  
12              
13             =head1 NAME
14              
15             Box::Calc - Packing Algorithm
16              
17             =head1 VERSION
18              
19             version 1.0200
20              
21             =head1 SYNOPSIS
22              
23             use Box::Calc;
24            
25             my $box_calc = Box::Calc->new;
26            
27             # define the possible box types
28             $box_calc->add_box_type( x => 12, y => 12, z => 18, weight => 16, name => 'big box' );
29             $box_calc->add_box_type( x => 4, y => 6, z => 8, weight => 6, name => 'small box' );
30              
31             # define the items you want to put into boxes
32             $box_calc->add_item( 3, { x => 6, y => 3, z => 3, weight => 12, name => 'soda' });
33             $box_calc->add_item( 1, { x => 3.3, y => 3, z => 4, weight => 4.5, name => 'apple' });
34             $box_calc->add_item( 2, { x => 8, y => 2.5, z => 2.5, weight => 14, name => 'water bottle' });
35              
36             # figure out what you need to pack this stuff
37             $box_calc->pack_items;
38            
39             # how many boxes do you need
40             my $box_count = $box_calc->count_boxes; # 2
41            
42             # interrogate the boxes
43             my $box = $box_calc->get_box(-1); # the last box
44             my $weight = $box->calculate_weight;
45            
46             # get a packing list
47             my $packing_list = $box_calc->packing_list;
48            
49             =head1 DESCRIPTION
50              
51             Box::Calc helps you determine what can fit into a box for shipping or storage purposes. It will try to use the smallest box possible of the box types. If every item won't fit into your largest box, then it will span the boxes letting you know how many boxes you'll need.
52              
53             Once it's done packing the boxes, you can get a packing list for each box, as well as the weight of each box.
54              
55             =head2 How The Algorithm Works
56              
57             Box::Calc is intended to pack boxes in the simplest way possible. Here's what it does:
58              
59             =over
60              
61             =item 1
62              
63             Sort all the items by volume.
64              
65             =item 2
66              
67             Eliminate all boxes that won't fit the largest items.
68              
69             =item 3
70              
71             Choose the smallest box still available.
72              
73             =item 4
74              
75             Place the items in a row starting with the largest items.
76              
77             =item 5
78              
79             When the row runs out of space, add another.
80              
81             =item 6
82              
83             When you run out of space to add rows, add a layer.
84              
85             =item 7
86              
87             When you run out of layers either start over with a bigger box, or if there are no bigger boxes span to a second box.
88              
89             =item 8
90              
91             Repeat from step 3 until all items are packed into boxes.
92              
93             =back
94              
95             =head2 Motivation
96              
97             At The Game Crafter (L<http://www.thegamecrafter.com>) we ship a lot of games and game pieces. We tried using a more complicated system for figuring out which size box to use, or how many boxes would be needed in a spanning situation. The problem was that those algorithms made the boxes pack so tightly that our staff spent a lot more time putting the boxes together. This algorithm is relatively dumb, but dumb in a good way. The boxes are easy and fast to pack. By releasing this, we hope it can help those who are either using too complicated a system, or no system at all for figuring out how many boxes they need for shipping/storing materials.
98              
99             =head2 Tips
100              
101             When adding items, be sure to use the outer most dimensions of oddly shaped items, otherwise they may not fit the box.
102              
103             When adding box types, be sure to use the inside dimensions of the box. If you plan to line the box with padding, then subtract the padding from the dimensions, and also add the padding to the weight of the box.
104              
105             What units you use (inches, centimeters, ounces, pounds, grams, kilograms, etc) don't matter as long as you use them consistently.
106              
107             =head1 METHODS
108              
109             =head2 new()
110              
111             Constructor.
112              
113             =head2 box_types()
114              
115             Returns an array reference of the L<Box::Calc::BoxType>s registered.
116              
117             =head2 count_box_types()
118              
119             Returns the number of L<Box::Calc::BoxType>s registered.
120              
121             =head2 get_box_type(index)
122              
123             Returns a specific L<Box::Calc::BoxType> from the list of C<box_types>
124              
125             =over
126              
127             =item index
128              
129             An array index. For example this would return the last box type added:
130              
131             $box_calc->get_box_type(-1)
132              
133             =back
134              
135             =cut
136              
137             has box_types => (
138             is => 'rw',
139             isa => 'ArrayRef[Box::Calc::BoxType]',
140             default => sub { [] },
141             traits => ['Array'],
142             handles => {
143             push_box_types => 'push',
144             count_box_types => 'count',
145             get_box_type => 'get',
146             }
147             );
148              
149             =head2 add_box_type(params)
150              
151             Adds a new L<Box::Calc::BoxType> to the list of C<box_types>. Returns the newly created L<Box::Calc::BoxType> instance.
152              
153             =over
154              
155             =item params
156              
157             The list of constructor parameters for L<Box::Calc::BoxType>.
158              
159             B<NOTE:> You can optionally include an argument of "categories" and a box type will be created for each category so you don't have to do it manually.
160              
161             =back
162              
163             =cut
164              
165             sub add_box_type {
166 28     28 1 1951 my $self = shift;
167 28         38 my $args;
168 28 100       75 if (ref $_[0] eq 'HASH') {
169 10         15 $args = shift;
170             }
171             else {
172 18         76 $args = { @_ };
173             }
174 28         55 my $categories = delete $args->{categories};
175 28 100       68 if (defined $categories) {
176 3         14 foreach my $category (@{$categories}) {
  3         8  
177 4         7 my %copy = %{$args};
  4         22  
178 4         12 $copy{category} = $category;
179 4         181 $self->push_box_types(Box::Calc::BoxType->new(%copy));
180             }
181             }
182             else {
183 25         808 $self->push_box_types(Box::Calc::BoxType->new($args));
184             }
185 28         1049 return $self->get_box_type(-1);
186             }
187              
188              
189             =head2 box_type_categories()
190              
191             Returns an array reference of categories associated with the box types.
192              
193             =cut
194              
195             has box_type_categories => (
196             is => 'rw',
197             lazy => 1,
198             isa => 'ArrayRef',
199             default => sub {
200             my $self = shift;
201             my %categories = ();
202             foreach my $box_type (@{$self->box_types}) {
203             next if $box_type->category eq '';
204             $categories{$box_type->category} = 1;
205             }
206             return [sort keys %categories];
207             },
208             );
209              
210              
211             =head2 sort_box_types_by_volume()
212              
213             Sorts the list of C<box_types> by volume and then returns an array reference of that list.
214              
215             =over
216              
217             =item types
218              
219             Optional. Array ref of box types. Will call C<box_types> if not passed in.
220              
221             =back
222              
223             =cut
224              
225             sub sort_box_types_by_volume {
226 17     17 1 34 my $self = shift;
227 17   33     570 my $types = shift || $self->box_types;
228 17         45 my @sorted = sort { ($a->volume) <=> ($b->volume ) } @{$types};
  61         1924  
  17         55  
229 17         64 return \@sorted;
230             }
231              
232             =head2 determine_viable_box_types(category)
233              
234             Given the list of C<items> and the list of C<box_types> this method rules out box types that cannot hold the largest item, and returns the list of box types that will work sorted by volume.
235              
236             =over
237              
238             =item category
239              
240             Optional. If this is specified, it will match this category name to the categories attached to the boxes and only provide a list of boxes that match that category.
241              
242             =back
243              
244             =cut
245              
246             sub determine_viable_box_types {
247 16     16 1 1627 my ($self, $category) = @_;
248 16         28 my ($item_x, $item_y, $item_z) = sort {$b <=> $a} @{$self->find_max_dimensions_of_items};
  48         85  
  16         600  
249 16         33 my @viable;
250 16         24 foreach my $box_type (@{$self->sort_box_types_by_volume}) {
  16         59  
251 52 100       123 if (defined $category) {
252 18 100       524 next unless $category eq $box_type->category;
253             }
254 39         47 my ($box_type_x, $box_type_y, $box_type_z) = @{$box_type->dimensions};
  39         2280  
255 39 50 66     265 if ($item_x <= $box_type_x && $item_y <= $box_type_y && $item_z <= $box_type_z) {
      66        
256 33         75 push @viable, $box_type;
257             }
258             }
259 16 100       66 unless (scalar @viable) {
260 1         10 $log->fatal('There are no box types that can fit the items.');
261 1         52 ouch 'no viable box types', 'There are no box types that can fit the items. ('.join(', ', $item_x, $item_y, $item_z).')', [$item_x, $item_y, $item_z];
262             }
263 15         66 return \@viable;
264             }
265              
266             =head2 items()
267              
268             Returns an array reference of the L<Box::Calc::Item>s registered.
269              
270             =head2 count_items()
271              
272             Returns the number of L<Box::Calc::Item>s registered.
273              
274             =head2 get_item(index)
275              
276             Returns a specific L<Box::Calc::Item>.
277              
278             =over
279              
280             =item index
281              
282             The array index of the item as it was registered.
283              
284             =back
285              
286             =cut
287              
288             has items => (
289             is => 'rw',
290             isa => 'ArrayRef[Box::Calc::Item]',
291             default => sub { [] },
292             traits => ['Array'],
293             handles => {
294             push_items => 'push',
295             count_items => 'count',
296             get_item => 'get',
297             }
298             );
299              
300             =head2 add_item(quantity, params)
301              
302             Registers a new item. Returns the new item registered.
303              
304             =over
305              
306             =item quantity
307              
308             How many copies of this item should be included in the package?
309              
310             =item params
311              
312             The constructor parameters for the L<Box::Calc::Item>.
313              
314             =back
315              
316             =cut
317              
318             sub add_item {
319 26     26 1 11662 my ($self, $quantity, @params) = @_;
320 26         889 my $item = Box::Calc::Item->new(@params);
321 26         78 for (1..$quantity) {
322 10589         367584 $self->push_items($item);
323             }
324 26         960 return $self->get_item(-1);
325             }
326              
327             =head2 load(payload)
328              
329             Allows the loading of an entire dataset.
330              
331             =over
332              
333             =item payload
334              
335             A hash reference containing the output of the C<dump> method, with two exceptions:
336              
337             =over
338              
339             =item *
340              
341             You can create a C<categories> element that is an array ref in each box type rather than creating duplicate box types for each category.
342              
343             =item *
344              
345             You can create a C<quantity> element in each item rather than creating duplicate items to represent the quantity.
346              
347             =back
348              
349             =back
350              
351             =cut
352              
353             sub load {
354 1     1 1 7 my ($self, $payload) = @_;
355             # note that we copy the box type and item to avoid modifying the original
356 1         2 foreach my $type (@{$payload->{box_types}}) {
  1         3  
357 7         8 $self->add_box_type(%{$type});
  7         25  
358             }
359 1         2 foreach my $item (@{$payload->{items}}) {
  1         49  
360 6   50     22 $self->add_item($item->{quantity} || 1, %{$item});
  6         25  
361             }
362             }
363              
364             =head2 dump()
365              
366             =cut
367              
368             sub dump {
369 2     2 1 6 my ($self) = @_;
370 2         4 my $payload = {};
371 2         5 foreach my $type (@{$self->box_types}) {
  2         57  
372 14         15 push @{$payload->{box_types}}, $type->describe;
  14         50  
373             }
374 2         5 foreach my $item (@{$self->items}) {
  2         55  
375 12         15 push @{$payload->{items}}, $item->describe;
  12         45  
376             }
377 2         7 return $payload;
378             }
379              
380              
381             =head2 sort_items_by_volume()
382              
383             Returns an array reference of the list of C<items> registered sorted by volume.
384              
385             =over
386              
387             =item items
388              
389             Optional. An array reference of items. Will call C<items> if not passed in.
390              
391             =back
392              
393             =cut
394              
395             sub sort_items_by_volume {
396 1     1 1 4 my $self = shift;
397 1   33     49 my $items = shift || $self->items;
398 1         4 my @sorted = sort { ($a->volume) <=> ($b->volume ) } @{$items};
  10         372  
  1         6  
399 1         6 return \@sorted;
400             }
401              
402             =head2 sort_items_by_zxy()
403              
404             Returns an array reference of the list of C<items> registered sorted by z, then x, then y, ascending.
405              
406             =over
407              
408             =item items
409              
410             Optional. An array reference of items. Will call C<items> if not passed in.
411              
412             =back
413              
414             =cut
415              
416             sub sort_items_by_zxy {
417 0     0 1 0 my $self = shift;
418 0   0     0 my $items = shift || $self->items;
419             my @sorted = sort {
420 0 0 0     0 $a->z <=> $b->z
421             || $a->x <=> $b->x
422             || $a->y <=> $b->y
423 0         0 } @{$items};
  0         0  
424 0         0 return \@sorted;
425             }
426              
427             =head2 sort_items_by_z_desc_A()
428              
429             Returns an array reference of the list of C<items> registered sorted by z DESC, then area DESC
430              
431             =over
432              
433             =item items
434              
435             Optional. An array reference of items. Will call C<items> if not passed in.
436              
437             =back
438              
439             =cut
440              
441             sub sort_items_by_z_desc_A {
442 0     0 1 0 my $self = shift;
443 0   0     0 my $items = shift || $self->items;
444 0         0 my @sorted = map { $_->[1] }
445             sort {
446             $b->[0]->{z} <=> $a->[0]->{z}
447             || $b->[0]->{A} <=> $a->[0]->{A}
448 0 0       0 }
449             ##Fetch Z and calculate A
450 0         0 map { [ { z=>$_->z, A=>$_->x*$_->y }, $_ ] } @{$items};
  0         0  
  0         0  
451 0         0 return \@sorted;
452             }
453              
454             =head2 sort_items_by_zA()
455              
456             Returns an array reference of the list of C<items> registered sorted by z ASC, then area DESC
457              
458             =over
459              
460             =item items
461              
462             Optional. An array reference of items. Will call C<items> if not passed in.
463              
464             =back
465              
466             =cut
467              
468             sub sort_items_by_zA {
469 10     10 1 22 my $self = shift;
470 10   66     302 my $items = shift || $self->items;
471 10552         13539 my @sorted = map { $_->[1] }
472             sort {
473             $a->[0]->{z} <=> $b->[0]->{z}
474             || $b->[0]->{A} <=> $a->[0]->{A}
475 10642 50       23685 }
476             ##Fetch Z and calculate A
477 10         20 map { [ { z=>$_->z, A=>$_->x*$_->y }, $_ ] } @{$items};
  10552         310291  
  10         150  
478 10         4216 return \@sorted;
479             }
480              
481             =head2 sort_items_by_Az()
482              
483             =over
484              
485             =item items
486              
487             Optional. An array reference of items. Will call C<items> if not passed in.
488              
489             =back
490              
491             Returns an array reference of the list of C<items> registered sorted by A DESC, then z ASC
492              
493             =cut
494              
495             sub sort_items_by_Az {
496 0     0 1 0 my $self = shift;
497 0   0     0 my $items = shift || $self->items;
498 0         0 my @sorted = map { $_->[1] }
499             sort {
500             $b->[0]->{A} <=> $a->[0]->{A}
501             || $a->[0]->{z} <=> $b->[0]->{z}
502 0 0       0 }
503             ##Fetch Z and calculate A
504 0         0 map { [ { z=>$_->z, A=>$_->x*$_->y }, $_ ] } @{$items};
  0         0  
  0         0  
505 0         0 return \@sorted;
506             }
507              
508             =head2 find_max_dimensions_of_items()
509              
510             Given the registered C<items>, returns the max C<x>, C<y>, and C<z> of all items registered as an array reference.
511              
512             =cut
513              
514             has find_max_dimensions_of_items => (
515             is => 'rw',
516             lazy => 1,
517             isa => 'ArrayRef',
518             clearer => 'clear_max_dimensions_of_items',
519             default => sub {
520             my $self = shift;
521             my $x = 0;
522             my $y = 0;
523             my $z = 0;
524             foreach my $item (@{$self->items}) {
525             my ($ex, $ey, $ez) = @{$item->dimensions};
526             $x = $ex if $ex > $x;
527             $y = $ey if $ey > $y;
528             $z = $ez if $ez > $z;
529             }
530             return [$x, $y, $z];
531             }
532             );
533              
534             =head2 boxes()
535              
536             Returns an array reference of the list of L<Box::Calc::Box>es needed to pack up the items.
537              
538             B<NOTE:> This will be empty until you call C<pack_items>.
539              
540             =head2 count_boxes()
541              
542             Returns the number of boxes needed to pack up the items.
543              
544             =head2 get_box(index)
545              
546             Fetches a specific box from the list of <boxes>.
547              
548             =over
549              
550             =item index
551              
552             The array index of the box you wish to fetc.
553              
554             =back
555              
556             =cut
557              
558             has boxes => (
559             is => 'rw',
560             isa => 'ArrayRef[Box::Calc::Box]',
561             default => sub { [] },
562             traits => ['Array'],
563             handles => {
564             push_boxes => 'push',
565             count_boxes => 'count',
566             get_box => 'get',
567             }
568             );
569              
570             =head2 reset_boxes()
571              
572             Deletes the list of C<boxes>.
573              
574             If you wish to rerun the packing you should use this to delete the list of C<boxes> first. This is handy if you needed to add an extra item or extra box type after you already ran C<pack_items>.
575              
576             =cut
577              
578             sub reset_boxes {
579 4     4 1 71204 my $self = shift;
580 4         144 $self->boxes([]);
581             }
582              
583             =head2 reset_items()
584              
585             Deletes the list of C<items>.
586              
587             For the sake of speed you may wish to reuse a L<Box::Calc> instance with the box types already pre-loaded. In that case you'll want to use this method to remove the items you've already registered. You'll probably also want to call C<reset_boxes>.
588              
589             =cut
590              
591             sub reset_items {
592 1     1 1 2 my $self = shift;
593 1         28 $self->items([]);
594 1         39 $self->clear_max_dimensions_of_items;
595             }
596              
597             sub make_box {
598 31     31 0 72 my ($self, $box_type) = @_;
599 31         1112 return Box::Calc::Box->new(
600             swap_xy => 1,
601             mail_service_name => $box_type->mail_service_name,
602             x => $box_type->x,
603             y => $box_type->y,
604             z => $box_type->z,
605             weight => $box_type->weight,
606             max_weight => $box_type->max_weight,
607             name => $box_type->name,
608             outer_x => $box_type->outer_x,
609             outer_y => $box_type->outer_y,
610             outer_z => $box_type->outer_z,
611             );
612             }
613              
614             =head2 find_tallest_z ( [ items ] )
615              
616             Determines the median of z across all items in the list.
617              
618             =over
619              
620             =item items
621              
622             An array reference of items. Optional. Defaults to C<items>.
623              
624             =back
625              
626             =cut
627              
628             sub find_tallest_z {
629 3     3 1 9 my $self = shift;
630 3   66     34 my $items = shift || $self->items;
631 3         4 return max map { $_->z } @{$items};
  165         4439  
  3         5  
632             }
633              
634              
635             =head2 stack_like_items( options )
636              
637             Stacks all like-sized items into stacks of C<stack_height> for denser packing. Could be used as an optimizer before running C<pack_items>.
638              
639             =over
640              
641             =item options
642              
643             A hash.
644              
645             =over
646              
647             =item items
648              
649             Optional. If not specified, will be the C<items> list.
650              
651             =item stack_height
652              
653             Optional. If not specified, will be determined by calling C<find_tallest_z>.
654              
655             =back
656              
657             =back
658              
659             =cut
660              
661             sub stack_like_items {
662 2     2 1 3 my ($self, %options) = @_;
663 2   33     63 my $items = $options{items} || $self->items;
664 2   33     9 my $stack_height = $options{stack_height} || $self->find_tallest_z($items);
665 2         7 my %like;
666 2         2 foreach my $item (@{$items}) {
  2         5  
667 110         105 push @{$like{$item->extent}}, $item;
  110         3116  
668             }
669 2         3 my @stacks;
670 2         5 foreach my $kind (values %like) {
671 6 50       7 if (scalar @{$kind} == 1) {
  6         15  
672 0         0 push @stacks, $kind->[0];
673             }
674             else {
675 6   50     186 my $items_per_stack = int($stack_height / $kind->[0]->z) || 1;
676 6         10 my $iterator = natatime($items_per_stack, @{$kind});
  6         56  
677 6         41 while (my @items = $iterator->()) {
678 12         15 my $count = scalar @items;
679 12 100       22 if ($count == 1) {
680 6         35 push @stacks, $items[0];
681             }
682             else {
683 6         8 my $item = $items[0];
684 6         190 push @stacks, Box::Calc::Item->new(
685             x => $item->x,
686             y => $item->y,
687             z => $item->z * $count,
688             weight => $item->weight * $count,
689             name => 'Stack of '.$count.' '.$item->name,
690             no_sort => 1,
691             );
692             }
693             }
694             }
695             }
696 2         17 return \@stacks;
697             }
698              
699             =head2 pack_items(options)
700              
701             Uses the list of C<box_types> and the list of C<items> to create the list of boxes to be packed. This method populates the C<boxes> list.
702              
703             =over
704              
705             =item options
706              
707             A hash.
708              
709             =over
710              
711             =item items
712              
713             Optional. If omitted the items list will be populated with whatever the current B<best> general purpose preprocessed item list is. Currently that is C<sort_items_by_zA>.
714              
715             =item category
716              
717             Optional. If this is specified, it will match this category name to the categories attached to the boxes and only pack in boxes that match that category.
718              
719             =back
720              
721             =back
722              
723             =cut
724              
725             sub pack_items {
726 10     10 1 480 my ($self, %options) = @_;
727 10         31 my $category = $options{category};
728 10   66     82 my $items = $options{items} || $self->sort_items_by_zA;
729 10         21 my $item_count = scalar(@{$items});
  10         26  
730 10         13 my @box_types = @{$self->determine_viable_box_types($category)};
  10         49  
731 10         29 my $countdown = scalar(@box_types);
732 10         26 BOXTYPE: foreach my $box_type (@box_types) {
733 19         624 $log->info("Box Type: ".$box_type->name);
734 19         389 $countdown--;
735 19         84 my $box = $self->make_box($box_type);
736 19         44 ITEM: foreach my $item (@{$items}) {
  19         53  
737 17295         514754 $log->info("Item: ".$item->name);
738            
739             # swap the item's x & y if it will make the item fit tighter
740 17295 50 33     781044 if ($item->x > 0 && $item->y > 0) {
741 17295         44673 $log->debug("Item's dimensions are not 0.");
742 17295 50 33     715619 if ($box->x >= $item->y && $box->y >= $item->x) { # see if the item would still fit in the box if it swapped
743 17295         43667 $log->debug('Item would still fit in the box if we rotated it.');
744 17295         703420 my $original_x_per_layer = int($box->x / $item->x);
745 17295         511559 my $original_y_per_layer = int($box->y / $item->y);
746 17295         28504 my $original_count_per_layer = $original_x_per_layer * $original_y_per_layer;
747 17295         504194 my $new_count_per_layer = int($box->x / $item->y) * int($box->y / $item->x);
748 17295 50 66     126309 if ( $new_count_per_layer > $original_count_per_layer # you can fit more items per layer in a swap
      66        
749             || $original_x_per_layer == 0 || $original_y_per_layer == 0 # if we keep it the current rotation we definitely won't fit, probably due to previous rotation
750             ) {
751 5         32 $log->info('Rotating '.$item->{name}.', because we can fit more per layer if we rotate.');
752 5         252 my $temp_x = $item->x;
753 5         150 $item->x($item->y);
754 5         156 $item->y($temp_x);
755             }
756             }
757             }
758             else {
759 0         0 $log->error('Item has a zero (0) dimension. That should not happen.');
760             }
761              
762             # pack the item into the box
763 17295 100       53835 unless ($box->pack_item($item)) {
764 21 100       72 if ($countdown) { # we still have other boxes to try
765 9         35 $log->info("moving to next box type");
766 9         426 next BOXTYPE;
767             }
768             else { # no more boxes to try, time for spanning
769 12 50       29 if (scalar(@{$self->boxes}) > $item_count) {
  12         376  
770 0         0 $log->warn("More boxes than items.");
771             #ouch 'more boxes than items', 'The number of boxes has exceded the number of items, which should never happen.';
772             }
773 12         115 $log->info("no more box types, spanning");
774 12         600 $self->push_boxes($box);
775 12         62 $box = $self->make_box($box_type);
776 12         56 redo ITEM;
777             }
778             }
779             }
780            
781             # we made it through our entire item list, yay!
782 10         48 $log->info("finished!");
783 10         506 $self->push_boxes($box);
784 10         285 last BOXTYPE;
785             }
786             }
787              
788             =head2 packing_list()
789              
790             Returns a data structure with all the item names and quantities packed into boxes. This can be used to generate manifests.
791              
792             [
793             { # box one
794             id => "xxx",
795             name => "big box",
796             weight => 30.1,
797             packing_list => {
798             "soda" => 3,
799             "apple" => 1,
800             "water bottle" => 2,
801             }
802             }
803             ]
804              
805             =cut
806              
807             sub packing_list {
808 2     2 1 49142 my $self = shift;
809 2         5 my @boxes;
810 2         6 foreach my $box (@{$self->boxes}) {
  2         75  
811 14         51 my ($weight, $list) = $box->packing_list;
812 14         419 push @boxes, {
813             id => $box->id,
814             name => $box->name,
815             weight => $weight,
816             packing_list => $list,
817             };
818             }
819 2         13 return \@boxes;
820             }
821              
822             =head2 packing_instructions()
823              
824             Returns a data structure with all the item names individually packed into rows, layers, and boxes. This can be used to build documentation on how to pack a set of boxes, and to generate a complete build history.
825              
826             [
827             { # box one
828             id => "xxx",
829             name => "big box",
830             layers => [
831             { # layer one
832             rows => [
833             { # row one
834             items => [
835             { # item one
836             name => "apple",
837             ...
838             },
839             ...
840             ],
841             },
842             ...
843             ],
844             ...
845             },
846             ],
847             },
848             ]
849              
850             =cut
851              
852             sub packing_instructions {
853 4     4 1 1641 my $self = shift;
854 4         8 my @boxes = map { $_->packing_instructions} @{ $self->boxes };
  16         88  
  4         130  
855 4         31 return \@boxes;
856             }
857              
858             =head1 TODO
859              
860             There are some additional optimizations that could be done to speed things up a bit. We might also be able to get a better fill percentage (less void space), although that's not really the intent of Box::Calc.
861              
862             =head1 PREREQS
863              
864             L<Moose>
865             L<Ouch>
866             L<Log::Any>
867             L<Data::GUID>
868              
869             =head1 SUPPORT
870              
871             =over
872              
873             =item Repository
874              
875             L<http://github.com/rizen/Box-Calc>
876              
877             =item Bug Reports
878              
879             L<http://github.com/rizen/Box-Calc/issues>
880              
881             =back
882              
883              
884             =head1 SEE ALSO
885              
886             Although these modules don't solve the same problem as this module, they may help you build something that does if Box::Calc doesn't quite help you do what you want.
887              
888             =over
889              
890             =item L<Algorithm::Knapsack>
891              
892             =item L<Algorithm::Bucketizer>
893              
894             =item L<Algorithm::Knap01DP>
895              
896             =back
897              
898             =head1 AUTHOR
899              
900             =over
901              
902             =item JT Smith <jt_at_plainblack_dot_com>
903              
904             =item Colin Kuskie <colink_at_plainblack_dot_com>
905              
906             =back
907              
908             =head1 LEGAL
909              
910             Box::Calc is Copyright 2012 Plain Black Corporation (L<http://www.plainblack.com>) and is licensed under the same terms as Perl itself.
911              
912             =cut
913              
914 7     7   18828 no Moose;
  7         16  
  7         59  
915             __PACKAGE__->meta->make_immutable;