File Coverage

blib/lib/perl5i/1/ARRAY.pm
Criterion Covered Total %
statement 15 67 22.3
branch 0 26 0.0
condition n/a
subroutine 5 19 26.3
pod 0 12 0.0
total 20 124 16.1


line stmt bran cond sub pod time code
1             # vi: set ts=4 sw=4 ht=4 et :
2             package perl5i::1::ARRAY;
3 1     1   18 use 5.010;
  1         2  
  1         36  
4              
5 1     1   4 use strict;
  1         1  
  1         27  
6 1     1   5 use warnings;
  1         1  
  1         40  
7 1     1   4 no if $] >= 5.018000, warnings => 'experimental::smartmatch';
  1         1  
  1         7  
8              
9 1     1   56 use perl5i::1::autobox;
  1         1  
  1         6  
10              
11             sub first {
12 0     0 0   my ( $array, $filter ) = @_;
13              
14             # Deep recursion and segfault (lines 90 and 91 in first.t) if we use
15             # the same elegant approach as in grep().
16 0 0         if ( ref $filter eq 'Regexp' ) {
17 0     0     return List::Util::first( sub { $_ ~~ $filter }, @$array );
  0            
18             }
19              
20 0     0     return List::Util::first( sub { $filter->() }, @$array );
  0            
21              
22             }
23              
24             sub grep {
25 0     0 0   my ( $array, $filter ) = @_;
26              
27 0           my @result = CORE::grep { $_ ~~ $filter } @$array;
  0            
28              
29 0 0         return wantarray ? @result : \@result;
30             }
31              
32             sub all {
33 0     0 0   require List::MoreUtils;
34 0           return List::MoreUtils::all($_[1], @{$_[0]});
  0            
35             }
36              
37             sub any {
38 0     0 0   require List::MoreUtils;
39 0           return List::MoreUtils::any($_[1], @{$_[0]});
  0            
40             }
41              
42             sub none {
43 0     0 0   require List::MoreUtils;
44 0           return List::MoreUtils::none($_[1], @{$_[0]});
  0            
45             }
46              
47             sub true {
48 0     0 0   require List::MoreUtils;
49 0           return List::MoreUtils::true($_[1], @{$_[0]});
  0            
50             }
51              
52             sub false {
53 0     0 0   require List::MoreUtils;
54 0           return List::MoreUtils::false($_[1], @{$_[0]});
  0            
55             }
56              
57             sub uniq {
58 0     0 0   require List::MoreUtils;
59 0           my @uniq = List::MoreUtils::uniq(@{$_[0]});
  0            
60 0 0         return wantarray ? @uniq : \@uniq;
61             }
62              
63             sub minmax {
64 0     0 0   require List::MoreUtils;
65 0           my @minmax = List::MoreUtils::minmax(@{$_[0]});
  0            
66 0 0         return wantarray ? @minmax : \@minmax;
67             }
68              
69             sub mesh {
70 0     0 0   require List::MoreUtils;
71 0           my @mesh = List::MoreUtils::zip(@_);
72 0 0         return wantarray ? @mesh : \@mesh;
73             }
74              
75              
76             # Returns the code which will run when the object is used as a string
77             require overload;
78             my $overload_type = sub {
79             return unless ref $_[0];
80             my $str = overload::Method($_[0], q[""]);
81             my $num = overload::Method($_[0], "0+");
82             return "both" if $str and $num;
83             return "" if !$str and !$num;
84             return "str" if $str;
85             return "num" if $num;
86             };
87              
88             my $are_equal;
89              
90             # Two objects, possibly different classes, both overloaded.
91             my $equal_overload = sub {
92             my($obj1, $obj2) = @_;
93              
94             my $type1 = $overload_type->($obj1);
95             my $type2 = $overload_type->($obj2);
96              
97             # One of them is not overloaded
98             return if !$type1 or !$type2;
99              
100             if( $type1 eq 'both' and $type2 eq 'both' ) {
101             return $obj1 == $obj2 || $obj1 eq $obj2;
102             }
103             elsif(
104             ($type1 eq 'num' and $type2 eq 'str') or
105             ($type1 eq 'str' and $type2 eq 'num')
106             )
107             {
108             # They're not both numbers, not both strings, and not both both
109             # Must be str vs num.
110             return $type1 eq 'num' ? $obj1+0 eq "$obj2"
111             : $obj2+0 eq "$obj1";
112             }
113             elsif( 'num' ~~ [$type1, $type2] ) {
114             return $obj1 == $obj2;
115             }
116             elsif( 'str' ~~ [$type1, $type2] ) {
117             return $obj1 eq $obj2;
118             }
119             else {
120             die "Should never be reached";
121             }
122             };
123              
124              
125             # Two objects, same class
126             my $equal_objects = sub {
127             my($r1, $r2) = @_;
128              
129             # No need to check both, they're the same class
130             my $is_overloaded = overload::Overloaded($r1);
131              
132             if( !$is_overloaded ) {
133             # Neither are overloaded, they're the same class, are they the same object?
134             return $r1 eq $r2;
135             }
136             else {
137             return $equal_overload->( $r1, $r2 );
138             }
139             };
140              
141              
142             # One overloaded object, one plain scalar
143             # STRING != OBJ
144             # NUMBER != OBJ
145             # STRING eq OBJeq
146             # STRING eq OBJboth
147             # STRING != OBJ== (using == will throw a warning)
148             # NUMBER == OBJ==
149             # NUMBER eq OBJeq
150             # NUMBER == OBJboth
151             my $equal_overload_vs_scalar = sub {
152             my($obj, $scalar) = @_;
153              
154             my $type = $overload_type->($obj);
155             return unless $type;
156              
157             if( $scalar->is_number ) {
158             if( $type eq 'str' ) {
159             $obj eq $scalar;
160             }
161             else {
162             $obj == $scalar;
163             }
164             }
165             else {
166             if( $type eq 'num' ) {
167             # Can't reliably compare
168             return;
169             }
170             else {
171             $obj eq $scalar;
172             }
173             }
174             };
175              
176             my $equal_arrays = sub {
177             my ($r1, $r2) = @_;
178             # They can only be equal if they have the same nº of elements.
179             return if @$r1 != @$r2;
180              
181             foreach my $i (0 .. @$r1 - 1) {
182             return unless $are_equal->($r1->[$i], $r2->[$i]);
183             }
184              
185             return 1;
186             };
187              
188             my $equal_hashes = sub {
189             my ($r1, $r2) = @_;
190             # Hashes can't be equal unless their keys are equal.
191             return unless ( %$r1 ~~ %$r2 );
192              
193             # Compare the equality of the values for each key.
194             foreach my $key (keys %$r1) {
195             return unless $are_equal->( $r1->{$key}, $r2->{$key} );
196             }
197              
198             return 1;
199             };
200              
201              
202             $are_equal = sub {
203             my ($r1, $r2) = @_;
204              
205             # given two scalars, decide whether they are identical or not,
206             # recursing over deep data structures. Since it uses recursion,
207             # traversal is done depth-first.
208             # Warning: complex if-then-else decision tree ahead. It's ordered on
209             # my perceived and anecdotical take on the frequency of occurrence
210             # of each reftype: most popular on top, most rare on the bottom.
211             # This way we return as early as possible.
212              
213             # undef eq undef
214             return 1 if !defined $r1 and !defined $r2;
215              
216             # One is defined, one isn't
217             return if defined $r1 xor defined $r2;
218              
219             my( $ref1, $ref2 ) = (ref $r1, ref $r2);
220              
221             if( !$ref1 and !$ref2 ) {
222             my $is_num1 = $r1->is_number;
223             my $is_num2 = $r2->is_number;
224             if( $is_num1 xor $is_num2 ) {
225             # One's looks like a number, the other doesn't.
226             # Can't be equal.
227             return 0;
228             }
229             elsif( $is_num1 ) {
230             # They're both numbers
231             return $r1 == $r2;
232             }
233             else {
234             # They're both strings
235             return $r1 eq $r2;
236             }
237             }
238             elsif( $ref1 eq $ref2 ) {
239             if ( $ref1 ~~ [qw(Regexp GLOB CODE)] ) {
240             return $r1 eq $r2;
241             }
242             elsif ( $ref1 eq 'ARRAY' ) {
243             return $equal_arrays->( $r1, $r2 );
244             }
245             elsif ( $ref1 eq 'HASH' ) {
246             return $equal_hashes->( $r1, $r2 );
247             }
248             elsif ( $ref1 ~~ [qw(SCALAR REF)] ) {
249             return $are_equal->($$r1, $$r2);
250             }
251             else {
252             # Must be an object
253             return $equal_objects->( $r1, $r2 );
254             }
255             }
256             elsif( $ref1 and $ref2 ) {
257             # They're both refs, but not of the same type
258             my $is_overloaded1 = overload::Overloaded($r1);
259             my $is_overloaded2 = overload::Overloaded($r2);
260              
261             if( $is_overloaded1 and $is_overloaded2 ) {
262             # Two overloaded objects
263             return $equal_overload->( $r1, $r2 );
264             }
265             else {
266             # One's an overloaded object, the other is not or
267             # Two plain refs different type or
268             # non-overloaded objects of different type.
269             return 0;
270             }
271             }
272             else {
273             # One is a ref, one is not
274             my $is_overloaded = $ref1 ? overload::Overloaded($r1)
275             : overload::Overloaded($r2);
276              
277             if( $is_overloaded ) {
278             # One's an overloaded object, one's a plain scalar
279             return $ref1 ? $equal_overload_vs_scalar->($r1, $r2)
280             : $equal_overload_vs_scalar->($r2, $r1);
281             }
282             else {
283             # One's a plain ref or object, one's a plain scalar
284             return 0;
285             }
286             }
287             };
288              
289              
290             my $diff_two = sub {
291             # Compare differences between two arrays.
292             my ($c, $d) = @_;
293              
294             my $diff = [];
295              
296             # For each element of $c, try to find if it is equal to any of the
297             # elements of $d. If not, it's unique, and has to be pushed into
298             # $diff.
299              
300             require List::MoreUtils;
301             foreach my $item (@$c) {
302             unless (
303             List::MoreUtils::any( sub { $are_equal->( $item, $_ ) }, @$d )
304             )
305             {
306             push @$diff, $item;
307             }
308             }
309              
310             return $diff;
311             };
312              
313              
314             sub diff {
315 0     0 0   my ($base, @rest) = @_;
316 0 0         unless (@rest) {
317 0 0         return wantarray ? @$base : $base;
318             }
319              
320             # XXX If I use carp here, the exception is "bizarre copy of ARRAY in
321             # ssasign ... "
322 0 0         die "Arguments must be array references" if grep { ref $_ ne 'ARRAY' } @rest;
  0            
323              
324 0           foreach my $array (@rest) {
325 0           $base = $diff_two->($base, $array);
326             }
327              
328 0 0         return wantarray ? @$base : $base;
329             }
330              
331              
332             my $intersect_two = sub {
333             # Compare differences between two arrays.
334             my ($c, $d) = @_;
335              
336             my $intersect = [];
337              
338             # For each element of $c, try to find if it is equal to any of the
339             # elements of $d. If it is, it's shared, and has to be pushed into
340             # $intersect.
341              
342             require List::MoreUtils;
343             foreach my $item (@$c) {
344             if (
345             List::MoreUtils::any( sub { $are_equal->( $item, $_ ) }, @$d )
346             )
347             {
348             push @$intersect, $item;
349             }
350             }
351              
352             return $intersect;
353             };
354              
355             sub intersect {
356 0     0 0   my ($base, @rest) = @_;
357              
358 0 0         unless (@rest) {
359 0 0         return wantarray ? @$base : $base;
360             }
361              
362             # XXX If I use carp here, the exception is "bizarre copy of ARRAY in
363             # ssasign ... "
364 0 0         die "Arguments must be array references" if grep { ref $_ ne 'ARRAY' } @rest;
  0            
365              
366 0           foreach my $array (@rest) {
367 0           $base = $intersect_two->($base, $array);
368             }
369              
370 0 0         return wantarray ? @$base : $base;
371             }
372              
373              
374             1;