File Coverage

blib/lib/Set/SortedArray.pm
Criterion Covered Total %
statement 145 181 80.1
branch 41 56 73.2
condition 17 30 56.6
subroutine 26 29 89.6
pod 21 21 100.0
total 250 317 78.8


line stmt bran cond sub pod time code
1             package Set::SortedArray;
2              
3 4     4   26375 use strict;
  4         8  
  4         154  
4 4     4   19 use warnings;
  4         9  
  4         724  
5              
6             =head1 NAME
7              
8             Set::SortedArray - sets stored as sorted arrays
9              
10             =head1 VERSION
11              
12             Version 0.02
13              
14             =cut
15              
16             our $VERSION = '0.02';
17              
18             =head1 SYNOPSIS
19              
20             use Set::SortedArray;
21             my $S = Set::SortedArray->new( qw/ d b c a e /);
22             my $T = Set::SortedArray->new_presorted( qw/ b c e f g / );
23              
24             print $S->as_string, "\n";
25             print $S, "\n";
26              
27             $U = $S->union($T);
28             $I = $S->intersection($T);
29             $D = $S->difference($T);
30             $E = $S->symmetric_difference($T);
31             $A = $S->asymmetric_difference($T);
32             $V = $S->unique($T);
33              
34             $U = $S + $T; # union
35             $I = $S * $T; # intersection
36             $D = $S - $T; # difference
37             $E = $S % $T; # symmetric_difference
38             $V = $S / $T; # unique
39              
40             $eq = $S->is_equal($T);
41             $dj = $S->is_disjoint($T);
42             $ps = $S->is_proper_subset($T);
43             $pS = $S->is_proper_superset($T);
44             $is = $S->is_subset($T);
45             $iS = $S->is_superset($T);
46              
47             $eq = $S == $T; # equal
48             $dj = $S != $T; # disjoint
49             $ps = $S < $T; # is_proper_subset
50             $pS = $S > $T; # is_proper_superset
51             $is = $S <= $T; # is_subset
52             $iS = $S >= $T; # is_superset
53              
54             # amalgam of a few of the above
55             $cmp = $S->compare($T);
56             $cmp = $S <=> $T;
57              
58             =head2 DESCRIPTION
59              
60             Create a set that is stored as a sorted array. Modification is currently
61             unsupported.
62              
63             =cut
64              
65             use overload
66 4         70 '""' => \&_as_string,
67             '+' => \&merge,
68             '*' => \&binary_intersection,
69             '-' => \&difference,
70             '%' => \&symmetric_difference,
71             '/' => \&unique,
72             '==' => \&is_equal,
73             '!=' => \&is_disjoint,
74             '<' => \&is_proper_subset,
75             '>' => \&is_proper_superset,
76             '<=' => \&is_subset,
77             '>=' => \&is_superset,
78 4     4   1460 '<=>' => \&compare;
  4         1105  
79              
80             =head1 CONSTRUCTORS
81              
82             =head2 new
83              
84             $set = Set::SortedArray->new();
85             $set = Set::SortedArray->new(@members);
86              
87             =head2 new_presorted
88              
89             $set = Set::SortedArray->new_presorted(@members);
90              
91             Quicker than new, but doesn't sort data.
92              
93             =cut
94              
95             sub new {
96 11     11 1 2467 my $class = shift;
97 11         58 my $self = bless [ sort @_ ], $class;
98 11         39 return $self;
99             }
100              
101             sub new_presorted {
102 4     4 1 6 my $class = shift;
103 4         15 my $self = bless [@_], $class;
104 4         20 return $self;
105             }
106              
107             =head1 MODIFYING
108              
109             Currently unsupported. Inserting or deleting would take O(n) time.
110              
111             =cut
112              
113             =head1 DISPLAYING
114              
115             =head2 as_string
116              
117             print $S->as_string, "\n";
118             print $S, "\n";
119              
120             =head2 as_string_callback
121              
122             Set::SortedArray->as_string_callback(sub { ... });
123              
124             =cut
125              
126             # helper function that overload points to
127 18     18   8563 sub _as_string { shift->as_string(@_) }
128              
129 16     16 1 22 sub as_string { return '(' . join( ' ', @{ $_[0] } ) . ')' }
  16         631  
130              
131             sub as_string_callback {
132 1     1 1 3 my ( $class, $callback ) = @_;
133 4     4   1388 no strict 'refs';
  4         8  
  4         134  
134 4     4   20 no warnings;
  4         6  
  4         7011  
135 1         6 *{"${class}::as_string"} = $callback;
  1         14  
136             }
137              
138             =head1 QUERYING
139              
140             =head2 members
141              
142             =head2 size
143              
144             =cut
145              
146 2     2 1 7 sub members { return @{ $_[0] } }
  2         51  
147 0     0 1 0 sub size { return scalar @{ $_[0] } }
  0         0  
148              
149             =head1 DERIVING
150              
151             =head2 union
152              
153             $U = $S->union($T);
154             $U = $S->union($T, $V);
155             $U = $S + $T;
156             $U = $S + $T + $V; # inefficient
157              
158             =cut
159              
160             sub union {
161 2 100   2 1 16 return $_[0]->merge( $_[1] ) if ( @_ == 2 );
162              
163 1         3 my %members;
164 1         3 foreach my $set (@_) {
165 3         5 foreach my $member (@$set) {
166 9   66     31 $members{$member} ||= $member;
167             }
168             }
169              
170 1         9 my $union = bless [ sort values %members ], ref( $_[0] );
171 1         9 return $union;
172             }
173              
174             =head2 merge
175              
176             $U = $S->merge($T);
177             $U = $S + $T;
178              
179             Special case of union where only two sets are considered. "+" is actually
180             overloaded to merge, not union. Named merge since this is essentially the
181             "merge" step of a mergesort.
182              
183             =cut
184              
185             sub merge {
186 1     1 1 3 my ( $S, $T ) = @_;
187 1         3 my ( $i, $j ) = ( 0, 0 );
188 1         4 my $U = [];
189              
190 1   66     47 while ( ( $i < @$S ) && ( $j < @$T ) ) {
191 3         6 my $s_i = $S->[$i];
192 3         5 my $t_j = $T->[$j];
193              
194 3 100       11 if ( $s_i eq $t_j ) { push @$U, $s_i; $i++; $j++ }
  2 50       4  
  2         7  
  2         8  
195 1         3 elsif ( $s_i lt $t_j ) { push @$U, $s_i; $i++ }
  1         5  
196 0         0 else { push @$U, $t_j; $j++ }
  0         0  
197             }
198              
199 1         5 push @$U, @$S[ $i .. $#$S ];
200 1         3 push @$U, @$T[ $j .. $#$T ];
201              
202 1         8 return bless $U, ref($S);
203             }
204              
205             =head2 intersection
206              
207             $I = $S->intersection($T);
208             $I = $S->intersection($T, $U);
209             $I = $S * $T;
210             $I = $S * $T * $U; # inefficient
211              
212             =cut
213              
214             sub intersection {
215 2 100   2 1 12 return $_[0]->binary_intersection( $_[1] ) if ( @_ == 2 );
216              
217 1         2 my $total = @_;
218              
219 1         3 my %members;
220             my %counts;
221              
222 1         2 foreach my $set (@_) {
223 3         7 foreach my $member (@$set) {
224 9   66     25 $members{$member} ||= $member;
225 9         16 $counts{$member}++;
226             }
227             }
228              
229 5         15 my $intersection =
230 1         4 bless [ sort grep { $counts{$_} == $total } values %members ],
231             ref $_[0];
232 1         8 return $intersection;
233             }
234              
235             =head2 binary_intersection
236              
237             $I = $S->binary_intersection($T);
238             $I = $S * $T;
239              
240             Special case of intersection where only two sets are considered. "*" is
241             actually overloaded to binary_intersection, not intersection.
242              
243             =cut
244              
245             sub binary_intersection {
246 1     1 1 3 my ( $S, $T ) = @_;
247 1         2 my ( $i, $j ) = ( 0, 0 );
248 1         3 my $I = [];
249              
250 1   66     8 while ( ( $i < @$S ) && ( $j < @$T ) ) {
251 3         8 my $s_i = $S->[$i];
252 3         5 my $t_j = $T->[$j];
253              
254 3 100       10 if ( $s_i eq $t_j ) { push @$I, $s_i; $i++; $j++ }
  2 50       4  
  2         2  
  2         18  
255 1         23 elsif ( $s_i lt $t_j ) { $i++ }
256 0         0 else { $j++ }
257             }
258              
259 1         5 return bless $I, ref($S);
260             }
261              
262             =head2 difference
263              
264             $D = $S->difference($T);
265             $D = $S - $T;
266              
267             =cut
268              
269             sub difference {
270 2     2 1 6 my ( $S, $T ) = @_;
271 2         5 my ( $i, $j ) = ( 0, 0 );
272 2         5 my $D = [];
273              
274 2   66     20 while ( ( $i < @$S ) && ( $j < @$T ) ) {
275 6         14 my $s_i = $S->[$i];
276 6         11 my $t_j = $T->[$j];
277              
278 6 100       20 if ( $s_i eq $t_j ) { $i++; $j++ }
  3 50       5  
  3         11  
279 3         6 elsif ( $s_i lt $t_j ) { push @$D, $s_i; $i++ }
  3         16  
280 0         0 else { $j++ }
281             }
282              
283 2         7 push @$D, @$S[ $i .. $#$S ];
284              
285 2         15 return bless $D, ref($S);
286             }
287              
288             =head2 symmetric_difference
289              
290             $E = $S->symmetric_difference($T);
291             $E = $S % $T;
292              
293             =cut
294              
295             sub symmetric_difference {
296 2     2 1 5 my ( $S, $T ) = @_;
297 2         3 my ( $i, $j ) = ( 0, 0 );
298 2         5 my $E = [];
299              
300 2   66     15 while ( ( $i < @$S ) && ( $j < @$T ) ) {
301 6         8 my $s_i = $S->[$i];
302 6         9 my $t_j = $T->[$j];
303              
304 6 100       14 if ( $s_i eq $t_j ) { $i++; $j++ }
  3 50       4  
  3         11  
305 3         4 elsif ( $s_i lt $t_j ) { push @$E, $s_i; $i++ }
  3         13  
306 0         0 else { push @$E, $t_j; $j++ }
  0         0  
307             }
308              
309 2         6 push @$E, @$S[ $i .. $#$S ];
310 2         6 push @$E, @$T[ $j .. $#$T ];
311              
312 2         11 return bless $E, ref($S);
313             }
314              
315             =head2 asymmetric_difference
316              
317             $A = $S->asymmetric_difference($T);
318              
319             Returns [ $S - $T, $T - $S ], but more efficiently.
320              
321             =cut
322              
323             sub asymmetric_difference {
324 0     0 1 0 my ( $S, $T ) = @_;
325 0         0 my ( $i, $j ) = ( 0, 0 );
326              
327             # $D = $S - $T, $B = $T - $S
328             # "B" chosen because "b" looks like mirror of "d"
329 0         0 my ( $D, $B ) = ( [], [] );
330              
331 0   0     0 while ( ( $i < @$S ) && ( $j < @$T ) ) {
332 0         0 my $s_i = $S->[$i];
333 0         0 my $t_j = $T->[$j];
334              
335 0 0       0 if ( $s_i eq $t_j ) { $i++; $j++ }
  0 0       0  
  0         0  
336 0         0 elsif ( $s_i lt $t_j ) { push @$D, $s_i; $i++ }
  0         0  
337 0         0 else { push @$B, $t_j; $j++ }
  0         0  
338             }
339 0         0 push @$D, @$S[ $i .. $#$S ];
340 0         0 push @$B, @$T[ $j .. $#$T ];
341              
342 0         0 my $class = ref($S);
343 0         0 bless $D, $class;
344 0         0 bless $B, $class;
345 0         0 return [ $D, $B ];
346             }
347              
348             =head2 unique
349              
350             $V = $S->unique($T);
351             $V = $S / $T;
352              
353             =cut
354              
355             sub unique {
356 0 0 0 0 1 0 pop if ( ( @_ == 3 ) && ( !UNIVERSAL::isa( $_[2], __PACKAGE__ ) ) );
357              
358 0         0 my %members;
359             my %counts;
360              
361 0         0 foreach my $set (@_) {
362 0         0 foreach my $member (@$set) {
363 0         0 $counts{$member}++;
364             }
365             }
366              
367 0         0 my $unique =
368 0         0 bless [ sort grep { $counts{$_} == 1 } values %members ],
369             ref $_[0];
370 0         0 return $unique;
371             }
372              
373             =head1 COMPARING
374              
375             =head2 is_equal
376              
377             $eq = $S->is_equal($T);
378             $eq = $S == $T;
379              
380             =cut
381              
382             sub is_equal {
383 4     4 1 11 my ( $S, $T ) = @_;
384 4 100       51 return unless ( @$S == @$T );
385 2         5 return _is_equal( $S, $T );
386             }
387              
388             sub _is_equal {
389 6     6   11 my ( $S, $T ) = @_;
390 6         19 for ( my $i = 0 ; $i < @$S ; $i++ ) {
391 10 100       40 return unless ( $S->[$i] eq $T->[$i] );
392             }
393 4         22 return 1;
394             }
395              
396             =head2 is_disjoint
397              
398             $dj = $S->is_disjoint($T);
399             $dj = $S != $T;
400              
401             =cut
402              
403             sub is_disjoint {
404 4     4 1 7 my ( $S, $T ) = @_;
405              
406 4         6 my $i = 0;
407 4         5 my $j = 0;
408              
409 4   66     21 while ( ( $i < @$S ) && ( $j < @$T ) ) {
410 6         7 my $s_i = $S->[$i];
411 6         8 my $t_j = $T->[$j];
412              
413 6 100       15 if ( $s_i eq $t_j ) { return }
  2 50       15  
414 4         15 elsif ( $s_i lt $t_j ) { $i++ }
415 0         0 else { $j++ }
416             }
417              
418 2         10 return 1;
419             }
420              
421             =head2 is_proper_subset
422              
423             $ps = $S->is_proper_subset($T);
424             $ps = $S < $T;
425              
426             =head2 is_proper_superset
427              
428             $pS = $S->is_proper_superset($T);
429             $pS = $S > $T;
430              
431             =head2 is_subset
432              
433             $is = $S->is_subset($T);
434             $is = $S <= $T;
435              
436             =head2 is_superset
437              
438             $iS = $S->is_superset($T);
439             $iS = $S >= $T;
440              
441             =cut
442              
443             sub is_proper_subset {
444 6     6 1 11 my ( $S, $T ) = @_;
445 6 100       29 return unless ( @$S < @$T );
446 2         9 return _is_subset( $S, $T );
447             }
448              
449             sub is_proper_superset {
450 6     6 1 9 my ( $S, $T ) = @_;
451 6 100       37 return unless ( @$S > @$T );
452 2         19 return _is_subset( $T, $S );
453             }
454              
455             sub is_subset {
456 6     6 1 10 my ( $S, $T ) = @_;
457 6 50       16 return unless ( @$S <= @$T );
458 6         10 return _is_subset( $S, $T );
459             }
460              
461             sub is_superset {
462 6     6 1 10 my ( $S, $T ) = @_;
463 6 50       570 return unless ( @$S >= @$T );
464 6         12 return _is_subset( $T, $S );
465             }
466              
467             sub _is_subset {
468 20     20   25 my ( $S, $T ) = @_;
469              
470 20         23 my $i = 0;
471 20         19 my $j = 0;
472              
473 20   100     92 while ( ( $i < @$S ) && ( $j < @$T ) ) {
474 38         70 my $s_i = $S->[$i];
475 38         45 my $t_j = $T->[$j];
476              
477 38 100       68 if ( $s_i eq $t_j ) { $i++; $j++; }
  32 100       32  
  32         105  
478 4         17 elsif ( $s_i gt $t_j ) { $j++ }
479 2         10 else { return }
480             }
481              
482 18         101 return $i == @$S;
483             }
484              
485             =head2 compare
486              
487             $cmp = $S->compare($T);
488             $cmp = $S <=> $T;
489              
490             C returns:
491              
492             0 if $S == $T
493             1 if $S > $T
494             -1 if $S < $T
495             () otherwise
496              
497             =cut
498              
499             sub compare {
500 8     8 1 356 my ( $S, $T ) = @_;
501              
502 8 100       23 if ( my $cmp = $#$S <=> $#$T ) {
503 4 50       13 return $cmp == 1
    50          
    100          
504             ? ( _is_subset( $T, $S ) ? 1 : () )
505             : ( _is_subset( $S, $T ) ? -1 : () );
506             }
507 4 100       10 else { return _is_equal( $S, $T ) ? 0 : () }
508             }
509              
510             =head1 AUTHOR
511              
512             "Kevin Galinsky", C
513              
514             =head1 BUGS
515              
516             Please report any bugs or feature requests to C, or through
517             the web interface at L. I will be notified, and then you'll
518             automatically be notified of progress on your bug as I make changes.
519              
520             =head1 SUPPORT
521              
522             You can find documentation for this module with the perldoc command.
523              
524             perldoc Set::SortedArray
525              
526             You can also look for information at:
527              
528             =over 4
529              
530             =item * RT: CPAN's request tracker (report bugs here)
531              
532             L
533              
534             =item * AnnoCPAN: Annotated CPAN documentation
535              
536             L
537              
538             =item * CPAN Ratings
539              
540             L
541              
542             =item * Search CPAN
543              
544             L
545              
546             =back
547              
548             =head1 ACKNOWLEDGEMENTS
549              
550             =head1 LICENSE AND COPYRIGHT
551              
552             Copyright 2011-2012 "Kevin Galinsky".
553              
554             This program is free software; you can redistribute it and/or modify it
555             under the terms of either: the GNU General Public License as published
556             by the Free Software Foundation; or the Artistic License.
557              
558             See http://dev.perl.org/licenses/ for more information.
559              
560             =cut
561              
562             1; # End of Set::SortedArray