File Coverage

blib/lib/Set/Hash/Keys.pm
Criterion Covered Total %
statement 44 45 97.7
branch 16 18 88.8
condition n/a
subroutine 18 19 94.7
pod 6 7 85.7
total 84 89 94.3


line stmt bran cond sub pod time code
1             package Set::Hash::Keys;
2              
3             =head1 NAME
4              
5             Set::Hash::Keys - Treat Hashes as Sets, based on the keys only
6              
7             =head1 VERSION 0.01
8              
9             =cut
10              
11             our $VERSION = '0.01';
12              
13 9     9   773859 use List::Util 'reduce';
  9         82  
  9         2227  
14              
15             =SYNOPSIS
16              
17             use Set::Hash::Keys;
18             my $set1 = Set::Hash::Keys->new(
19             foo => 'blue',
20             bar => 'july',
21             );
22             my $set2 = Set::Hash::Keys->new(
23             foo => 'bike',
24             baz => 'fish',
25             );
26            
27             my $set3 = $set1 + $set2; # union
28             # foo => 'bike', # only the last remains
29             # bar => 'july',
30             # baz => 'fish',
31            
32             my $set4 = $set1 * $set2; # intersection
33             # foo => 'bike', # only the last remains
34            
35             my $set5 = $set1 - $set2; # difference
36             # bar => 'july',
37            
38             my ($sub1, $sub2) = $set1 / $set2;
39            
40             my $set5 += { qux => 'moon', ... }; # add new elements
41             # bar => 'july',
42             # qux => 'moon',
43            
44             my $set3 -= { foo => 'sofa', ... };
45             # bar => 'july',
46             # baz => 'fish',
47            
48              
49             =head1 DESCRIPTION
50              
51             This module will help to check two or more hashes for which keys they have in
52             common and which not. It is all based on 'Set Theory' and works as expected. But
53             keep in mind that it only considders the keys to create unions, differences or
54             intersections. And that just like ordinary hash operations, the last key/value
55             pair wins.
56              
57             Other moules will treat operations in respect to the values too, and only will
58             do a difference or union if both key and value are the same in both hashes or.
59             sets.
60              
61             =cut
62              
63             use overload(
64 4 100   4   10341 '+' => sub { pop @_ ? union($_[1],$_[0]) : union($_[0],$_[1]) },
65 4 100   4   2944 '-' => sub { pop @_ ? difference($_[1],$_[0]) : difference($_[0],$_[1]) },
66 4 100   4   3175 '*' => sub { pop @_ ? intersection($_[1],$_[0]) : intersection($_[0],$_[1]) },
67 0 0   0   0 '/' => sub { pop @_ ? exclusive($_[1],$_[0]) : exclusive($_[0],$_[1]) },
68 4     4   3064 '%' => sub { symmetrical($_[0],$_[1]) },
69 9     9   54 );
  9         14  
  9         166  
70              
71             =head1 IMPORTS
72              
73             For convenience, the C constructor has been imported in your current
74             namespace, so you can do:
75              
76             my $set_h = set_hash( foo => 'boat', bar => 'just' );
77              
78             All other functions mentioned below can be imported individually, or using the
79             C<:all> tag.
80              
81             =cut
82              
83 9     9   963 use Exporter 'import';
  9         26  
  9         4513  
84              
85             @EXPORT = qw (
86             &set_hash
87             );
88              
89             @EXPORT_OK = qw (
90             &union
91             &intersection
92             &difference
93             &exclusive
94             &symmetrical
95             );
96              
97             %EXPORT_TAGS = (
98             'all' => \@EXPORT_OK,
99             );
100              
101             sub set_hash {
102 1     1 0 1723 __PACKAGE__->new(@_)
103             }
104              
105             sub new {
106 84     84 1 62057 my $class = shift;
107 84         162 my %data = @_;
108            
109 84         221 return bless \%data, $class
110             }
111              
112             =head1 CONSTRUCTORS
113              
114             =cut
115              
116             =head2 new
117              
118             =cut
119              
120             =head1 SET OPERATIONS
121              
122             The following Set operations are provided as functions, that will take a list of
123             sets or HashRef's, or as binary (set) operators (that requires at least one of
124             the two being a L or as, or as an assignment operator. Usually
125             the function or set-operator will return a single L object. But
126             L, and L will return a list off object when evaluated in
127             list context. See below for how to use each and every set-operation.
128              
129             See L
130              
131             =cut
132              
133             =head2 union
134              
135             Based on the keys, this will produce a new unified L object
136             from the sets passed in.
137              
138             my $set_1 = union(
139             {
140             foo => 'blue',
141             bar => 'july',
142             },
143             {
144             foo => 'bike',
145             baz => 'fish',
146             },
147             {
148             qux => 'wood',
149             },
150             );
151             print values %$set_1; # july, fish, bike, wood
152            
153             my $set_2 = $set_1 + { bar => 'hand' };
154             print values %$set_2; # hand, fish, bike, wood
155            
156             $set_2 += { foo => 'wipe', xyz => 'bell' }
157             print values %$set_2; # hand, fish, wipe, wood, bell
158              
159             NOTE: like ordinary hashes, when using the same key more than once, the value of
160             the last one used will remain.
161              
162             =cut
163              
164             sub union {
165 15 100   15 1 10638 return unless defined $_[0];
166              
167             my $hash_ref = reduce {
168 16     16   86 +{ %$a, %$b }
169 13         59 } @_;
170            
171 13         62 __PACKAGE__->new( %$hash_ref );
172             }
173              
174             =head2 intersection
175              
176             The C will produce a L thas has all keys in
177             common.
178              
179             my $set_1 = intersection(
180             {
181             foo => 'blue',
182             bar => 'july',
183             },
184             {
185             foo => 'bike',
186             baz => 'fish',
187             },
188             {
189             qux => 'wood',
190             },
191             );
192             print values %$set_1; # bike
193            
194             my $set_2 = $set_1 * { foo => 'hand', qux => 'just' };
195             print values %$set_2; # hand
196            
197             $set_1 *= { foo => 'wipe', xyz => 'bell' }
198             print values %$set_1; # wipe
199              
200             NOTE: the value stored with any key, will be the value of the last set passed in
201              
202             =cut
203              
204             sub intersection {
205 7 100   7 1 11095 return unless defined $_[0];
206              
207             my $hash_ref = reduce {
208             +{
209             map {
210 8         36 $_, $b->{$_}
211             } grep {
212 8     8   48 exists $b->{$_}
  13         28  
213             } keys %$a
214             }
215 6         36 } @_;
216            
217 6         29 __PACKAGE__->new( %$hash_ref );
218             }
219              
220             =head2 difference
221              
222             In scalar context, this will produce a set from the first set, minus all
223             key/value pairs mentioned after the first set.
224              
225             my $set_1 = difference(
226             {
227             foo => 'blue',
228             bar => 'july',
229             },
230             {
231             foo => 'bike',
232             baz => 'fish',
233             },
234             {
235             qux => 'wood',
236             },
237             );
238             print values %$set_1; # blue
239            
240             my $set_2 = $set_1 - { foo => 'hand', qux => 'just' };
241             print values %$set_2; # -
242            
243             $set_1 -= { foo => 'wipe', xyz => 'bell' }
244             print values %$set_1; # -
245              
246             In list context, this will produce a list of set, where the difference is
247             produced by taking each passed in set, minus all the key/values from the other
248             sets. And as such producing a list of sets that have unique values per set.
249              
250             my @diffs = difference(
251             {
252             foo => 'blue',
253             bar => 'july',
254             },
255             {
256             foo => 'bike',
257             baz => 'fish',
258             },
259             {
260             qux => 'wood',
261             },
262             );
263             print values %$diffs[0]; # july
264             print values %$diffs[1]; # fish
265             print values %$diffs[2]; # wood
266              
267             NOTE: it will retain the key/value pairs from the first set.
268              
269             =cut
270              
271             sub difference {
272 40 100   40 1 6413 return unless defined $_[0];
273            
274 36 100       61 if ( wantarray() ) {
275 9         24 my $sets_ref = [];
276 9         32 for my $i ( 0 .. $#_ ) {
277 21         38 my @other = @_; # make a clone, since splice mutates it
278 21         30 my $set_i = splice( @other, $i, 1 );
279 21         42 my $set_d = difference( $set_i, @other );
280 21         47 push @$sets_ref, $set_d;
281             }
282 9         27 return @$sets_ref
283             }
284            
285             my $hash_ref = reduce {
286             +{
287             map {
288 40         100 $_, $a->{$_}
289             } grep {
290 38     38   160 !exists $b->{$_}
  69         140  
291             } keys %$a
292             }
293 27         120 } @_;
294            
295 27         113 __PACKAGE__->new( %$hash_ref )
296             }
297              
298             =head2 exclusive
299              
300             In list context, this will produce a list of sets where each set will only
301             contain those key/value pairs that are exclusive to each set, in respect to the
302             other sets in the argument list.
303              
304             This is basicly the same as in list context.
305              
306             In scalar context, it will return the C of the before mentioned sets. So,
307             these key/value pairs are not mentioned in any other set.
308              
309             my $set_x = exclusive(
310             {
311             foo => 'blue',
312             bar => 'july',
313             },
314             {
315             foo => 'bike',
316             baz => 'fish',
317             },
318             {
319             qux => 'wood',
320             },
321             );
322             print values %$set_x # july, fish, wood
323              
324             my $set_1 = Set::Hash::Keys->new( foo => 'blue', bar => 'july' );
325            
326             my $set_2 = $set / { foo => 'bike' , baz => 'fish' }
327             print values %$set_2 # july, fish,
328            
329             $set_2 /= { qux => 'wood' };
330             print values %$set_2 # july, fish, wood
331            
332             # for liust context, see `difference`
333              
334             NOTE: for two sets, this basically produces the 'symmetrical difference'
335              
336             =cut
337              
338             sub exclusive {
339 4 100   4 1 2949 wantarray() ? difference( @_ ) : union( difference( @_ ) )
340             }
341              
342             =head2 symmetrical
343              
344             Produces the symmetrical difference from a list of sets. This is quite obvious
345             for two sets and returns those key/value pairs that are in either sets but not
346             in both.
347              
348             However, when passing in multiple sets, this gets confusing, but basically it
349             will hold those key/value pairs that have an odd count, even counts will not be
350             in the set. For more information see proper Set Theory explenation.
351              
352             As mentioned before, the symmetrical difference for two sets, is the same as the
353             union of the exclusive key/value pairs.
354              
355             my $set_s = symmetrical(
356             {
357             foo => 'blue',
358             bar => 'july',
359             },
360             {
361             foo => 'bike',
362             baz => 'fish',
363             },
364             {
365             foo => 'moon',
366             baz => 'wood',
367             },
368             print values %$set_1 # july, moon
369              
370             =cut
371              
372             sub symmetrical {
373 6     6   20 reduce { union ( difference( $a, $b ) ) } @_
374 6     6 1 1701 }
375              
376             =head1 AUTHOR
377              
378             Theo J. van Hoesel L
379              
380             =head1 COPYRIGHT AND LICENSE
381              
382             This software is copyright (c) 2018 by Theo J. van Hoesel - THEMA-MEDIA
383              
384             This is free software; you can redistribute it and/or modify it under the same
385             terms as the Perl 5 programming language system itself.
386              
387             Terms of the Perl programming language system itself
388              
389             a) the GNU General Public License as published by the Free Software Foundation;
390             either version 1, or (at your option) any later version, or
391             b) the "Artistic License"
392              
393             =cut
394              
395             1;