File Coverage

blib/lib/Set/Bag.pm
Criterion Covered Total %
statement 108 109 99.0
branch 36 38 94.7
condition 18 22 81.8
subroutine 26 27 96.3
pod 0 17 0.0
total 188 213 88.2


line stmt bran cond sub pod time code
1             package Set::Bag;
2              
3             $VERSION = 1.012;
4              
5             =pod
6              
7             =head1 NAME
8              
9             Set::Bag - bag (multiset) class
10              
11             =head1 SYNOPSIS
12              
13             use Set::Bag;
14              
15             my $bag_a = Set::Bag->new(apples => 3, oranges => 4);
16             my $bag_b = Set::Bag->new(mangos => 3);
17             my $bag_c = Set::Bag->new(apples => 1);
18             my $bag_d = ...;
19            
20             # Methods
21              
22             $bag_b->insert(apples => 1);
23             $bag_b->delete(mangos => 1);
24              
25             $bag_b->insert(cherries => 1, $bag_c);
26              
27             my @b_elements = $bag_b->elements; # ('apples','cherries','mangos')
28             my @b_grab_app = $bag_b->grab('apples', 'cherries'); # (3, 1)
29             my @a_grab_all = $bag_a->grab; # (apples => 3, oranges => 4)
30              
31             print "bag_a sum bag_b = ", $bag_a->sum($bag_b), "\n";
32             print "bag_a difference bag_b = ", $bag_a->difference($bag_b), "\n";
33              
34             print "bag_a union bag_b = ", $bag_a->union($bag_b), "\n";
35             print "bag_a intersection bag_b = ", $bag_a->intersection($bag_b), "\n";
36              
37             print "bag_b complement = ", $bag_b->complement, "\n";
38              
39             # Operator Overloads
40              
41             print "bag_a = $bag_a\n"; # (apples => 3, oranges => 4)
42              
43             $bag_b += $bag_c; # Insert
44             $bag_b -= $bag_d; # Delete
45              
46             print "bag_b = $bag_b\n";
47              
48             print "bag_a + bag_b = ", $bag_a + $bag_b, "\n"; # Sum
49             print "bag_a - bag_b = ", $bag_a - $bag_b, "\n"; # Difference
50              
51             print "bag_a | bag_b = ", $bag_a | $bag_b, "\n"; # Union
52             print "bag_a & bag_b = ", $bag_a & $bag_b, "\n"; # Intersection
53              
54             $bag_b |= $bag_c; # Maximize
55             $bag_b &= $bag_d; # Minimize
56              
57             print "good\n" if $bag_a eq "(apples => 3, oranges => 4)"; # Eq
58             print "bad\n" unless $bag_a ne "(apples => 3, oranges => 4)"; # Ne
59              
60             print "-bag_b = ", -$bag_b"\n"; # Complement
61              
62             $bag_c->delete(apples => 5); # Would abort.
63              
64             print "Can", # Cannot ...
65             $bag_c->over_delete() ? "" : "not",
66             " over delete from bag_c\n";
67              
68             $bag_c->over_delete(1);
69             print "Can", # Can ...
70             $bag_c->over_delete() ? "" : "not",
71             " over delete from bag_c\n";
72             $bag_c->delete(apples => 5); # Would succeed.
73              
74             print $bag_c, "\n"; # ()
75              
76              
77             =head1 DESCRIPTION
78              
79             This module implements a simple bag (multiset) class.
80              
81             A bag may contain one or more instances of elements. One may add and
82             delete one or more instances at a time.
83              
84             If one attempts to delete more instances than there are to delete
85             from, the default behavious of B is to raise an exception.
86             The B method can be used to control this behaviour.
87              
88             Inserting or removing negative number of instances translates into
89             removing or inserting positive number of instances, respectively.
90              
91             The B is also known as the I. It leaves in
92             the result bag the sum of all the instances of all bags.
93              
94             Before using the B you very often will need the B.
95              
96             The B is also known as the I. It leaves in
97             the result bag the maximal number of instances in all bags.
98              
99             The B leaves in the result bag only the elements that
100             have instances in all bags and of those the minimal number of instances.
101              
102             The B will leave in the result bag the maximal number of
103             instances I seen (via B, B, B, or B)
104             in the bag minus the current number of instances in the bag.
105              
106             The B method returns the contents of a bag.
107             If used with parameters the parameters are the elements and their
108             number of instances in the bag are returned. If an element that
109             does not exist in the bag is grabbed for,
110             the number of instances returned for that element will be C.
111             If used without parameters the elements are returned in pseudorandom order.
112              
113             =head1 NOTES
114              
115             Beware the low precedence of C<|> and C<&> compared with C and C.
116              
117             =head1 AUTHOR
118              
119             David Oswald C<< >> is the current maintainer, starting with
120             release 1.010.
121              
122             Jarkko Hietaniemi C<< >> was the original author.
123              
124             =head1 LICENSE AND COPYRIGHT
125              
126             Copyright O'Reilly and Associates.
127              
128             This program is free software; you can redistribute it and/or modify it under
129             the terms of either: the GNU General Public License as published by the Free
130             Software Foundation; or the Artistic License.
131              
132             See L and L for full details.
133              
134             =cut
135              
136             require 5.004;
137 1     1   27679 use strict;
  1         3  
  1         191  
138             use overload
139 1         20 q("") => \&bag,
140             q(eq) => \&eq,
141             q(ne) => \&ne,
142             q(+=) => \&insert,
143             q(-=) => \&delete,
144             q(+) => \&sum,
145             q(-) => \&difference,
146             q(|=) => \&maximize,
147             q(&=) => \&minimize,
148             q(|) => \&union,
149             q(&) => \&intersection,
150             q(neg) => \&complement,
151             q(=) => \©,
152 1     1   1801 ;
  1         1272  
153              
154             my $over_delete = 'Set::Bag::__over_delete__';
155              
156             sub new {
157 22     22 0 2499 my $type = shift;
158 22         42 my $bag = { };
159 22         51 bless $bag, $type;
160 22         64 $bag->insert(@_);
161 22         59 return $bag;
162             }
163              
164             sub elements {
165 53     53 0 73 my $bag = shift;
166 53         69 return sort grep { $_ ne $over_delete } keys %{$bag};
  135         364  
  53         184  
167             }
168              
169             sub bag {
170 35     35 0 52 my $bag = shift;
171             return
172 62         279 "(" .
173             (join ", ",
174 62         133 map { "$_ => $bag->{$_}" }
175 35         79 sort grep { ! /^Set::Bag::/ } $bag->elements) .
176             ")";
177             }
178              
179             sub eq {
180 9 50   9 0 1108 return $_[2] ? "$_[1]" eq $_[0] : "$_[0]" eq $_[1];
181             }
182              
183             sub ne {
184 0     0 0 0 return not $_[0] eq $_[1];
185             }
186              
187             sub grab {
188 71     71 0 5410 my $bag = shift;
189 71 100       141 if (@_) {
190 56         67 return @{$bag}{@_};
  56         241  
191             } else {
192 15         22 return %{$bag};
  15         97  
193             }
194             }
195              
196             sub _merge {
197 46     46   65 my $bag = shift;
198 46         53 my $sub = shift; # Element subroutine.
199 46         48 my $ref_arg = shift; # Argument list.
200 46         72 my $ref_bag = ref $bag;
201 46         85 while (my $e = shift @{$ref_arg}) {
  108         412  
202 64 100       125 if (ref $e eq $ref_bag) {
203 13         27 foreach my $c ($e->elements) {
204 26         57 $sub->($bag, $c, $e->{$c});
205             }
206             } else {
207 51         60 $sub->($bag, $e, shift @{$ref_arg});
  51         116  
208             }
209             }
210             }
211              
212             sub _underload { # Undo overload effects on @_.
213             # If the last argument looks like it might be
214             # residue of the operator overload system, drop it.
215 46 100 66 46   387 pop @{$_[0]}
  9   100     21  
216             if (not defined $_[0]->[-1] and not ref $_[0]->[-1]) or
217             $_[0]->[-1] eq '';
218             }
219              
220             my %universe;
221              
222             sub _insert {
223 48     48   72 my ($bag, $e, $n) = @_;
224 48         99 $bag->{$e} += int $n;
225 48 100 100     295 $universe{$e} = $bag->{$e}
226             if $bag->{$e} > ($universe{$e} || 0);
227             }
228              
229             sub over_delete {
230 19     19 0 2337 my $bag = shift;
231              
232 19 100       52 if (@_ == 1) {
    100          
233 3         13 $bag->{$over_delete} = shift;
234             } elsif (@_ == 0) {
235 15   100     90 return ($bag->{$over_delete} ||= 0);
236             } else {
237 1         9 die "Set::Bag::over_delete: too many arguments (",
238             $#_+1,
239             "), want 0 or 1\n";
240             }
241             }
242              
243             sub _delete {
244 14     14   24 my ($bag, $e, $n) = @_;
245              
246 14 100       29 unless ($bag->over_delete) {
247 8   100     28 my $m = $bag->{$e} || 0;
248 8 100       674 $m >= $n or
249             die "Set::Bag::delete: '$e' $m < $n\n";
250             }
251 12         25 $bag->{$e} -= int $n;
252 12 100       65 delete $bag->{$e} if $bag->{$e} < 1;
253             }
254              
255              
256             sub insert {
257 29     29 0 83 _underload(\@_);
258 29         45 my $bag = shift;
259 50     50   90 $bag->_merge(sub { my ($bag, $e, $n) = @_;
260 50 100       90 if ($n > 0) {
    100          
261 47         99 $bag->_insert($e, $n);
262             } elsif ($n < 0) {
263 1         4 $bag->_delete($e, -$n);
264             } },
265 29         159 \@_);
266 29         314 return $bag;
267             }
268              
269             sub delete {
270 11     11 0 41 _underload(\@_);
271 11         28 my $bag = shift;
272 15     15   32 $bag->_merge(sub { my ($bag, $e, $n) = @_;
273 15 100       35 if ($n > 0) {
    100          
274 13         33 $bag->_delete($e, $n);
275             } elsif ($n < 0) {
276 1         4 $bag->_insert($e, -$n);
277             } },
278 11         63 \@_);
279 9         66 return $bag;
280             }
281              
282             sub maximize {
283 3     3 0 11 _underload(\@_);
284 3         5 my $max = shift;
285 6     6   10 $max->_merge(sub { my ($bag, $e, $n) = @_;
286 6 100 66     29 $bag->{$e} = $n
287             if not defined $bag->{$e} or $n > $bag->{$e};
288 6 50 50     35 $universe{$e} = $n
289             if $n > ($universe{$e} || 0) },
290 3         18 \@_);
291 3         15 return $max;
292             }
293              
294             sub minimize {
295 3     3 0 12 _underload(\@_);
296 3         6 my $min = shift;
297 3         5 my %min;
298 3         9 foreach my $e ($min->elements) { $min{$e} = 1 }
  6         14  
299 6     6   10 $min->_merge(sub { my ($bag, $e, $n) = @_;
300 6         10 $min{$e}++;
301 6 100 66     41 $bag->{$e} = $n
302             if defined $bag->{$e} and $n < $bag->{$e} },
303 3         18 \@_);
304 3 100       16 foreach my $e (keys %min) { delete $min->{$e} if $min{$e} == 1 }
  9         25  
305 3         9 return $min;
306             }
307              
308             sub copy {
309 14     14 0 2393 my $bag = shift;
310 14         37 return (ref $bag)->new($bag->grab);
311             }
312              
313             sub sum {
314 2     2 0 432 my $union = (shift)->copy;
315 2         9 $union->insert(@_);
316 2         6 return $union;
317             }
318              
319             sub difference {
320 3     3 0 14 my $difference = (shift)->copy;
321 3         13 $difference->delete(@_);
322 3         14 return $difference;
323             }
324              
325             sub union {
326 2     2 0 889 my $union = (shift)->copy;
327 2         12 $union->maximize(@_);
328 2         7 return $union;
329             }
330              
331             sub intersection {
332 2     2 0 421 my $intersection = (shift)->copy;
333 2         10 $intersection->minimize(@_);
334 2         6 return $intersection;
335             }
336              
337             sub complement {
338 2     2 0 836 my $bag = shift;
339 2         8 my $complement = (ref $bag)->new;
340 2         9 foreach my $e (keys %universe) {
341 6   100     26 $complement->{$e} = $universe{$e} - ($bag->{$e} || 0);
342 6 100       20 delete $complement->{$e} unless $complement->{$e};
343             }
344 2         7 return $complement;
345             }
346              
347             1;