File Coverage

blib/lib/Set/Equivalence.pm
Criterion Covered Total %
statement 225 225 100.0
branch 60 76 78.9
condition 15 27 55.5
subroutine 72 72 100.0
pod 49 49 100.0
total 421 449 93.7


line stmt bran cond sub pod time code
1 18     18   742887 use 5.008;
  18         70  
  18         716  
2 18     18   258 use strict;
  18         34  
  18         978  
3 18     18   103 use warnings;
  18         32  
  18         1052  
4              
5             package Set::Equivalence;
6              
7             BEGIN {
8 18     18   39 $Set::Equivalence::AUTHORITY = 'cpan:TOBYINK';
9 18         418 $Set::Equivalence::VERSION = '0.003';
10             }
11              
12 18     18   92 use Carp qw( croak );
  18         32  
  18         1279  
13 18     18   112 use List::Util qw( first );
  18         37  
  18         2304  
14 18     18   45602 use List::MoreUtils qw( any );
  18         29143  
  18         1799  
15 18     18   138 use Scalar::Util qw( blessed refaddr );
  18         41  
  18         3236  
16              
17             # avoid unnecessarily importing constant.pm
18             sub true() { !!1 };
19             sub false() { !!0 };
20              
21             use overload
22             '""' => 'as_string',
23             '+' => 'union',
24             '*' => 'intersection',
25             '%' => 'symmetric_difference',
26             '-' => 'difference',
27             '==' => 'equal',
28             'eq' => 'equal',
29             '!=' => 'not_equal',
30             'ne' => 'not_equal',
31             '<' => 'proper_subset',
32             '>' => 'proper_superset',
33             '<=' => 'subset',
34             '>=' => 'superset',
35             '@{}' => 'as_array',
36 2     2   33 'bool' => sub { 1 },
37 18     18   22049 fallback => 1;
  18         14557  
  18         211  
38              
39             sub new {
40 157     157 1 15587 my $class = shift;
41 157         442 my %args = @_;
42            
43 157         348 my $init = delete($args{members});
44 157         581 my $self = bless {
45             equivalence_relation => $class->_build_equivalence_relation(\%args),
46             members => [],
47             mutable => true,
48             %args,
49             } => $class;
50            
51 157         3074 local $self->{mutable} = true;
52 157 100       671 $self->insert(@$init) if $init;
53            
54 157         887 return $self;
55             }
56              
57             sub equivalence_relation {
58 427     427 1 947 shift->{equivalence_relation};
59             }
60              
61             sub type_constraint {
62 278     278 1 27001 shift->{type_constraint};
63             }
64              
65             sub should_coerce {
66 166     166 1 196 my $self = shift;
67 166 100 66     627 $self->{coerce} and $self->{type_constraint} and $self->{type_constraint}->has_coercion
68             }
69              
70             sub is_mutable {
71 232     232 1 23718 !! shift->{mutable};
72             }
73              
74             sub is_immutable {
75 20     20 1 29578 not shift->is_mutable;
76             }
77              
78             sub make_immutable {
79 25     25 1 43 my $self = shift;
80 25         82 $self->{mutable} = false;
81 25         96 return $self;
82             }
83              
84             sub _default_equivalence_relation {
85 18     18   9213 no warnings 'uninitialized';
  18         204  
  18         47371  
86 4283     4283   4556 my ($x, $y) = @_;
87            
88             # If 'eq' says they're not equal, then trust it.
89 4283 100       11440 return false unless $x eq $y;
90            
91             # However, there are some situations where 'eq'
92             # might provide a false positive.
93            
94             # Undef is not equal to ""
95 409 50       855 return false unless !!defined($x) == !!defined($y);
96              
97             # A non-overloaded object can never be equal to a string!
98 409 50 66     1755 return false if !ref($x) && ref($y) && !overload::Overloaded($y);
      33        
99 409 100 100     1598 return false if !ref($y) && ref($x) && !overload::Overloaded($x);
      100        
100            
101             # OK then, they're equal!
102 408         3865 return true;
103             }
104              
105 157     157   1035 sub _build_equivalence_relation { \&_default_equivalence_relation };
106              
107             sub insert {
108 166     166 1 1515 my $self = shift;
109 166 100       356 croak "cannot call insert on immutable set"
110             unless $self->is_mutable;
111            
112 165         466 my $eq = $self->equivalence_relation;
113 165         356 my $tc = $self->type_constraint;
114 165         347 my $sc = $self->should_coerce;
115            
116 165         302 my $count;
117 165         411 ITEM: while (@_) {
118 609 100       1346 my $item = $sc ? $tc->coerce(shift @_) : (shift @_);
119 609 100 66     3187 $tc->check($item) || croak $tc->get_message($item) if $tc;
120 608 100   1686   3595 next ITEM if any { $eq->($_, $item) } $self->members;
  1686         3533  
121 579         3605 push @{$self->{members}}, $item;
  579         1135  
122 579         1257 $count++;
123             }
124            
125 164         269 return $count;
126             }
127              
128             sub _unshift {
129 1     1   3 my $self = shift;
130 1 50       3 croak "cannot call _unshift on immutable set"
131             unless $self->is_mutable;
132            
133 1         3 my $eq = $self->equivalence_relation;
134 1         3 my $tc = $self->type_constraint;
135 1         3 my $sc = $self->should_coerce;
136            
137 1         2 my $count;
138 1         9 ITEM: while (@_) {
139 20 50       40 my $item = $sc ? $tc->coerce(pop @_) : (pop @_);
140 20 50 0     32 $tc->check($item) || croak $tc->get_message($item) if $tc;
141 20 100   300   76 next ITEM if any { $eq->($_, $item) } $self->members;
  300         485  
142 10         31 unshift @{$self->{members}}, $item;
  10         25  
143 10         22 $count++;
144             }
145            
146 1         5 return $count;
147             }
148              
149             sub contains {
150 148     148 1 175 my $self = shift;
151            
152 148         245 my $eq = $self->equivalence_relation;
153            
154 148 100       311 return true unless @_;
155            
156 147         271 ITEM: while (@_) {
157 422         464 my $item = shift @_;
158 422 100   1949   1338 return false unless any { $eq->($_, $item) } $self->members;
  1949         3024  
159             }
160            
161 75         423 return true;
162             }
163              
164             sub member {
165 8     8 1 12 my $self = shift;
166 8         9 my $item = $_[0];
167 8         17 my $eq = $self->equivalence_relation;
168 8         47 for ($self->members) {
169 36 100       53 return $_ if $eq->($_, $item)
170             }
171 4         16 return;
172             }
173              
174             sub members {
175 1285     1285 1 1584 my $self = shift;
176 1285         1200 @{$self->{members}};
  1285         5663  
177             }
178              
179             sub size {
180 60     60 1 2435 my $self = shift;
181 60         76 scalar @{$self->{members}};
  60         487  
182             }
183              
184             sub remove {
185 14     14 1 509 my $self = shift;
186 14 100       31 croak "cannot call remove on immutable set"
187             unless $self->is_mutable;
188            
189 13         34 my $eq = $self->equivalence_relation;
190            
191 13 50       33 return 0 unless @_;
192            
193 13         24 my ($count, @new_set) = 0;
194 13         48 OLD_SET: for my $member ($self->members) {
195 111         146 REMOVALS: for my $item (@_) {
196 330 100       480 if ($eq->($member, $item)) {
197 21         22 $count++;
198 21         59 next OLD_SET;
199             }
200             }
201 90         156 push @new_set, $member;
202             }
203            
204 13         27 @{$self->{members}} = @new_set;
  13         48  
205 13         37 return $count;
206             }
207              
208             sub weaken {
209 1     1 1 12 die "unimplemented";
210             }
211              
212             sub is_weak {
213 1     1 1 5 false;
214             }
215              
216             sub strengthen {
217 1     1 1 3 $_[0];
218             }
219              
220             sub invert {
221 4     4 1 7 my $self = shift;
222 4 50       11 croak "cannot call invert on immutable set"
223             unless $self->is_mutable;
224            
225 4     25   51 my ($hasnt, $has) = List::MoreUtils::part { $self->contains($_) } @_;
  25         87  
226            
227 4 50       15 if (@{$has||[]}) {
  4 50       22  
228 4         17 $self->remove(@$has);
229             }
230 4         15 $self->insert(@$hasnt);
231             }
232              
233             sub clear {
234 3     3 1 505 my $self = shift;
235 3 100       11 croak "cannot call clear on immutable set"
236             unless $self->is_mutable;
237            
238 2         8 my $size = $self->size;
239 2         5 @{$self->{members}} = ();
  2         7  
240 2         18 return $size;
241             }
242              
243             sub as_string {
244 11     11 1 6048 my $self = shift;
245 11         30 "(" . join(" ", sort $self->members) . ")";
246             }
247              
248             sub _args
249             {
250 92     92   126 my $n = shift;
251 92         108 my ($class, @eq, @tc);
252            
253 92 100       247 if (ref $_[0])
254             {
255 90         129 $class = ref($_[0]);
256 90         219 @eq = (equivalence_relation => $_[0]->equivalence_relation);
257 90 100       206 @tc = (type_constraint => $_[0]->type_constraint) if $_[0]->type_constraint;
258             }
259             else
260             {
261 2         4 $class = shift;
262             }
263            
264 92         334 for (0 .. $n-1) {
265 174 50 33     1372 blessed($_[$_]) && $_[$_]->isa($class)
266             or croak("expected $class; got $_[$_]");
267             }
268            
269             return (
270             sub {
271 26     26   91 my @members = @_;
272 26         96 $class->new(members => \@members, @eq, @tc);
273             }, @_
274 92         516 );
275             }
276              
277             sub equal {
278 24     24 1 577 my (undef, $this, $that) = _args 2, @_;
279 24 100       168 return true if refaddr($this) == refaddr($that);
280 19 100       39 $this->contains($that->members) and $that->contains($this->members);
281             }
282              
283             sub not_equal {
284 10     10 1 31 my (undef, $this, $that) = _args 2, @_;
285 10         54 not $this->equal($that);
286             }
287              
288             sub clone {
289 5     5 1 19371 my ($maker, $this) = _args 1, @_;
290 5         14 return $maker->( $this->members );
291             }
292              
293             sub intersection {
294 7     7 1 22 my ($maker, $this, $that) = _args 2, @_;
295 7         22 return $maker->(
296             grep $that->contains($_), $this->members
297             );
298             }
299              
300             sub union {
301 3     3 1 14 my ($maker, $this, $that) = _args 2, @_;
302 3         10 return $maker->( $this->members, $that->members );
303             }
304              
305             sub difference {
306 3     3 1 15 my ($maker, $this, $that) = _args 2, @_;
307 3         10 my $new = $maker->( $this->members );
308 3         11 $new->remove($that->members);
309 3         18 return $new;
310             }
311              
312             sub symmetric_difference {
313 3     3 1 30 my ($maker, $this, $that) = _args 2, @_;
314 3         11 my $new = $maker->( $this->members );
315 3         12 $new->invert($that->members);
316 3         19 return $new;
317             }
318              
319             sub subset {
320 7     7 1 22 my (undef, $this, $that) = _args 2, @_;
321 7         52 $that->contains($this->members);
322             }
323              
324             sub proper_subset {
325 7     7 1 20 my (undef, $this, $that) = _args 2, @_;
326 7 100       33 $that->contains($this->members) and not $this->contains($that->members);
327             }
328              
329             sub superset {
330 7     7 1 23 my (undef, $this, $that) = _args 2, @_;
331 7         37 $this->contains($that->members);
332             }
333              
334             sub proper_superset {
335 7     7 1 19 my (undef, $this, $that) = _args 2, @_;
336 7 100       33 $this->contains($that->members) and not $that->contains($this->members);
337             }
338              
339             sub is_null {
340 35     35 1 64 my $self = shift;
341 35         105 $self->size == 0;
342             }
343              
344             sub compare {
345 1     1 1 8 die "unimplemented";
346             }
347              
348             sub is_disjoint {
349 4     4 1 13 my (undef, $this, $that) = _args 2, @_;
350 4         20 return $this->intersection($that)->is_null;
351             }
352              
353             sub as_string_callback {
354 1     1 1 7 die "unimplemented";
355             }
356              
357             # Aliases
358 1     1 1 4 sub includes { shift->contains(@_) }
359 1     1 1 4 sub has { shift->contains(@_) }
360 4     4 1 9 sub element { shift->member(@_) }
361 1     1 1 3 sub elements { shift->members(@_) }
362 1     1 1 5 sub delete { shift->remove(@_) }
363 2     2 1 7 sub is_empty { shift->is_null(@_) }
364              
365             # Exports
366             BEGIN {
367 18     18   97003 require Exporter::Tiny;
368 18         42611 push our(@ISA), 'Exporter::Tiny';
369 18         10421 push our(@EXPORT_OK), 'set', 'typed_set';
370             };
371 93     93 1 30706 sub set { __PACKAGE__->new(members => \@_, ) };
372 29     29 1 22996 sub typed_set { my $tc = shift; __PACKAGE__->new(members => \@_, type_constraint => $tc) };
  29         125  
373              
374             # Extra fun
375             sub as_array {
376 13     13 1 44 my $self = shift;
377 13   33     55 my $array = $self->{_array} ||= do {
378 13         1758 require Set::Equivalence::_Tie;
379 13         117 tie my @arr, 'Set::Equivalence::_Tie', $self;
380 13         63 \@arr;
381             };
382 13 50       257 Scalar::Util::weaken($self->{_array}) unless Scalar::Util::isweak($self->{_array});
383 13         75 return $array;
384             }
385              
386             sub iterator {
387 1     1 1 558 my $self = shift;
388 1         4 my @elements = $self->members;
389 1     6   6 return sub { shift @elements };
  6         24  
390             }
391              
392             sub map {
393 2     2 1 6 my ($maker, $this, $code) = _args 1, @_;
394 2 50       20 shift unless blessed $_[0];
395 2         12 return $maker->(map $code->($_), $this->members);
396             }
397              
398             sub grep {
399 1     1 1 3 my ($maker, $this, $code) = _args 1, @_;
400 1 50       6 shift unless blessed $_[0];
401 1         4 return $maker->(grep $code->($_), $this->members);
402             }
403              
404             sub reduce {
405 1     1 1 4 my (undef, $this, $code) = _args 1, @_;
406 1         8 @_ = ($code, $this->members);
407 1         16 goto \&List::Util::reduce;
408             }
409              
410             sub part {
411 1     1 1 4 my ($maker, $this, $code) = _args 1, @_;
412 1         4 return map $maker->(@$_), &List::MoreUtils::part($code, $this->members);
413             }
414              
415             sub pop {
416 4     4 1 7 my $self = shift;
417 4 50       11 croak "cannot call pop on immutable set"
418             unless $self->is_mutable;
419 4 100       22 return if $self->is_null;
420 3         15 $self->remove( my $r = $self->{members}[-1] );
421 3         10 return $r;
422             }
423              
424             sub _shift {
425 1     1   3 my $self = shift;
426 1 50       4 croak "cannot call _shift on immutable set"
427             unless $self->is_mutable;
428 1 50       6 return if $self->is_null;
429 1         6 $self->remove( my $r = $self->{members}[0] );
430 1         5 return $r;
431             }
432              
433             set -> is_null
434              
435             __END__