File Coverage

blib/lib/Box/Limited.pm
Criterion Covered Total %
statement 71 72 98.6
branch 6 8 75.0
condition 3 3 100.0
subroutine 13 13 100.0
pod 7 7 100.0
total 100 103 97.0


line stmt bran cond sub pod time code
1             package Box::Limited;
2              
3             =head1 NAME
4              
5             Box::Limited - Box with a limited capacity.
6              
7             =head1 DESCRIPTION
8              
9             This class represents a box which can contain only a limited number of items
10             with a limited total weight.
11             This can be useful e.g. to form requests to a certain API which has
12             a limit on the number of items / total characters sent within one request.
13              
14             =head1 SYNOPSIS
15              
16             use Box::Limited;
17             use List::Util qw(sum0);
18              
19             my $box = Box::Limited->new(
20             size => 100,
21             max_weight => 200,
22             weight_function => sub (@items) {
23              
24             # "Weight" of item is a length of its string form in this case
25             return sum0 map { length($_) } @items;
26             },
27             );
28              
29             while (my $item = shift @items) {
30             if ($box->can_add($item)) {
31             $box->add($item);
32             } else {
33             say "Box is full";
34              
35             # ...process full box...
36              
37             }
38             }
39              
40             =cut
41              
42 1     1   121991 use Moo;
  1         3  
  1         6  
43 1     1   272 use experimental qw(signatures);
  1         2  
  1         4  
44 1     1   95 use Carp qw(croak);
  1         2  
  1         46  
45 1     1   448 use Types::Common::Numeric qw(PositiveOrZeroInt);
  1         77811  
  1         6  
46 1     1   365 use Types::Standard qw(CodeRef ArrayRef);
  1         2  
  1         4  
47              
48             our $VERSION = '0.01';
49              
50             =head1 ATTRIBUTES
51              
52             =head3 size
53              
54             Box size - the maximum amount of items the box can hold. Non-negative integer;
55             required.
56              
57             =cut
58              
59             has size => (
60             is => 'ro',
61             isa => PositiveOrZeroInt,
62             required => 1,
63             );
64              
65             =head3 max_weight
66              
67             Maximum weight of all items in the box. Non-negative integer; required.
68              
69             =cut
70              
71             has max_weight => (
72             is => 'ro',
73             isa => PositiveOrZeroInt,
74             required => 1,
75             );
76              
77             =head3 weight_function
78              
79             Code reference of weighting function; required. Its argument is an array of
80             items, and return value must be an integer representing weight of all items.
81              
82             =cut
83              
84             has weight_function => (
85             is => 'ro',
86             isa => CodeRef,
87             required => 1,
88             );
89              
90             has _items_ref => (
91             is => 'rw',
92             isa => ArrayRef,
93             default => sub { [] },
94             );
95              
96             =head1 METHODS
97              
98             =head3 can_add($item)
99              
100             Whether C<$item> can be added to the box. C<$item> is any scalar that can be
101             weighted by C.
102              
103             =cut
104              
105 49     49 1 870 sub can_add ($self, $item) {
  49         53  
  49         50  
  49         54  
106 49   100     62 return $self->items_count < $self->size
107             && $self->_get_weight_with($item) <= $self->max_weight;
108              
109             }
110              
111 45     45   321 sub _get_weight_with ($self, $item) {
  45         48  
  45         45  
  45         44  
112 45         59 return $self->weight_function->($self->items, $item);
113             }
114              
115             =head3 add($item)
116              
117             Adds C<$item> to the box and returns true. If item cannot be added, raises
118             exception.
119              
120             =cut
121              
122 26     26 1 2072 sub add ($self, $item) {
  26         31  
  26         24  
  26         27  
123 26 100       40 if (!$self->can_add($item)) {
124 3         346 croak "Cannot add item: $item";
125             }
126 23         258 push @{ $self->_items_ref }, $item;
  23         314  
127 23         114 return 1;
128             }
129              
130             =head3 items
131              
132             Returns array of items in the box in the same order they were added there.
133              
134             =cut
135              
136 47     47 1 1594 sub items ($self) {
  47         57  
  47         47  
137 47         47 return @{ $self->_items_ref };
  47         601  
138             }
139              
140             =head3 items_count
141              
142             Returns number of items in the box.
143              
144             =cut
145              
146 63     63 1 1252 sub items_count ($self) {
  63         75  
  63         63  
147 63         70 return scalar @{ $self->_items_ref };
  63         893  
148             }
149              
150             =head3 is_empty
151              
152             Whether the box is empty or not.
153              
154             =cut
155              
156 9     9 1 2774 sub is_empty ($self) {
  9         10  
  9         9  
157 9         16 return $self->items_count == 0;
158             }
159              
160             =head3 clear
161              
162             Clears the box and returns true.
163              
164             =cut
165              
166 1     1 1 554 sub clear ($self) {
  1         3  
  1         1  
167 1         21 $self->_items_ref([]);
168 1         27 return 1;
169             }
170              
171             =head3 split_to_boxes(\%constructor_arg, @items)
172              
173             In: \%constructor_arg - constructor arguments (all the attributes required
174             for new())
175             Out: @filled_boxes - array of boxes filled with @items
176              
177             Class method. Creates as many boxes as required to put all the C<@items> in
178             them, puts items there and returns boxes.
179              
180             Items are processed in the order they were passed - there is no heuristic to
181             minimize the total number of used boxes.
182              
183             =cut
184              
185 3     3 1 1422 sub split_to_boxes ($class, $constructor_arg, @items) {
  3         5  
  3         5  
  3         4  
  3         3  
186 3         4 my @filled_boxes;
187             BOX: {
188 3         4 my $box = $class->new($constructor_arg);
  6         103  
189 6         579 while (@items) {
190 19         26 my $item = $items[0];
191 19 100       29 if ($box->can_add($item)) {
192 16         199 $box->add($item);
193 16         35 shift @items;
194             }
195             else {
196 3 50       26 if ($box->is_empty) {
197 0         0 croak "Item is too big to add to the box even alone: $item";
198             }
199 3         17 push @filled_boxes, $box;
200 3         7 redo BOX;
201             }
202             }
203 3 50       5 push @filled_boxes, $box if !$box->is_empty;
204             }
205 3         28 return @filled_boxes;
206             }
207              
208             =head1 AUTHOR
209              
210             Ilya Chesnokov L.
211              
212             =head1 LICENSE
213              
214             Under the same terms as Perl itself.
215              
216             =head1 CREDITS
217              
218             Thanks to L for sponsoring work on this
219             module.
220              
221             =cut
222              
223             1;