File Coverage

blib/lib/Acme/IEnumerable.pm
Criterion Covered Total %
statement 136 423 32.1
branch 8 108 7.4
condition 0 12 0.0
subroutine 39 98 39.8
pod 2 32 6.2
total 185 673 27.4


line stmt bran cond sub pod time code
1             package Acme::IEnumerable;
2 3     3   71704 use strict;
  3         9  
  3         118  
3 3     3   16 use warnings;
  3         6  
  3         84  
4 3     3   15 use Exporter;
  3         10  
  3         154  
5            
6 3     3   15 use vars qw{ $VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS };
  3         6  
  3         464  
7            
8             BEGIN {
9 3     3   60 @ISA = qw(Exporter);
10 3         16 %EXPORT_TAGS = ( 'all' => [ qw(
11             ) ] );
12            
13 3         10 @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
  3         10  
14 3         5 @EXPORT = @EXPORT_OK;
15 3         95 $VERSION = '0.6';
16             };
17            
18             #####################################################################
19             #
20             #####################################################################
21             package Acme::IEnumerable::List;
22 3     3   17 use base qw/Acme::IEnumerable/;
  3         5  
  3         395  
23 3     3   14 use strict;
  3         6  
  3         90  
24 3     3   15 use warnings;
  3         10  
  3         87  
25 3     3   52 use v5.10;
  3         11  
  3         140  
26 3     3   20 use Carp;
  3         5  
  3         2441  
27            
28             sub _create {
29 3         40 bless {
30             _list => $_[0],
31             _zero => 0,
32 3     3   7 _last => scalar(@{ $_[0] }) - 1,
33             _dir => 1,
34             _new => $_[1],
35             }, __PACKAGE__;
36             }
37            
38             sub count {
39 8     8   9 my ($self) = @_;
40 8         40 $self->{_dir} * ($self->{_last} - $self->{_zero}) + 1;
41             }
42            
43             sub element_at {
44 3     3   5 my ($self, $index) = @_;
45 3 50       11 Carp::cluck unless defined $index;
46 3 50       7 croak unless $self->count > $index;
47 3         8 my $projected = $self->{_zero} + $index * $self->{_dir};
48 3         14 $self->{_list}->[$projected];
49             }
50            
51             sub last {
52 0     0   0 my ($self) = @_;
53 0 0       0 croak unless $self->count;
54 0         0 $self->element_at($self->count - 1);
55             }
56            
57             sub last_or_default {
58 0     0   0 my ($self, $default) = @_;
59 0 0       0 return $default unless $self->count;
60 0         0 $self->element_at($self->count - 1);
61             }
62            
63             sub first {
64 0     0   0 my ($self) = @_;
65 0 0       0 croak "No elements for 'first'" unless $self->count;
66 0         0 $self->element_at(0);
67             }
68            
69             sub first_or_default {
70 0     0   0 my ($self, $default) = @_;
71 0 0       0 return $default unless $self->count;
72 0         0 $self->element_at(0);
73             }
74            
75             sub from_list {
76 3     3   26 my $class = shift;
77 3         9 my @list = @_;
78             return _create \@list, sub {
79             return sub {
80 8         10 state $index = 0;
81 8 100       19 return unless $index <= $#list;
82 6         20 return \($list[$index++]);
83 2     2   9 };
84 3         29 };
85             }
86            
87             sub skip {
88 0     0   0 my ($self, $count) = @_;
89             return Acme::IEnumerable::_create(sub {
90             return sub {
91 0         0 state $index = $count;
92 0 0       0 return unless $index < $self->count;
93 0         0 return \($self->element_at($index++));
94 0     0   0 };
95 0         0 });
96             }
97            
98             sub reverse {
99 1     1   3 my ($self) = @_;
100            
101 1 50       6 return $self->to_list unless $self->count;
102            
103 1         3 my $new;
104 1         112 $new = bless {
105             _list => $self->{_list},
106             _last => 0,
107             _zero => scalar(@{ $self->{_list} }) - 1,
108             _dir => -1,
109             _new => sub {
110             return sub {
111 4         5 state $index = 0;
112 4 100       9 return unless $index < $new->count;
113 3         12 return \($new->element_at($index++));
114             }
115 1     1   7 },
116 1         4 }, __PACKAGE__;
117             }
118            
119             #####################################################################
120             #
121             #####################################################################
122             # sub find { ... }
123             # sub find_index { ... }
124             # sub find_last { ... }
125             # sub find_last_idex { ... }
126             # sub exists { ... }
127             # sub find_all { ... }
128             # sub binary_search { ... }
129             # sub index_of { ... }
130             # sub last_index_of { ... }
131            
132             1;
133            
134             #####################################################################
135             #
136             #####################################################################
137             package Acme::IEnumerable::Ordered;
138 3     3   17 use strict;
  3         5  
  3         89  
139 3     3   14 use warnings;
  3         7  
  3         85  
140 3     3   37 use v5.10;
  3         11  
  3         129  
141 3     3   22 use Carp;
  3         6  
  3         170  
142 3     3   16 use base qw/Acme::IEnumerable/;
  3         5  
  3         1781  
143            
144             sub _create {
145 1     1   15 bless {
146             _key => $_[0],
147             _sgn => $_[1],
148             _par => $_[2],
149             _new => $_[3],
150             }, __PACKAGE__;
151             }
152            
153             sub order_by {
154             # This assumes to_enumerable will remove the ::Ordered base type
155 0     0   0 my ($self) = @_;
156 0         0 $self->to_enumerable->order_by(@_);
157             }
158            
159             sub order_by_descending {
160             # This assumes to_enumerable will remove the ::Ordered base type
161 0     0   0 my ($self) = @_;
162 0         0 $self->to_enumerable->order_by_descending(@_);
163             }
164            
165             sub then_by_descending {
166 0     0   0 _then_by(@_[0..1], -1);
167             }
168            
169             sub then_by {
170 1     1   4 _then_by(@_[0..1], 1);
171             }
172            
173             sub _then_by {
174 1     1   3 my ($self, $key_extractor, $sign) = @_;
175             return _create $key_extractor, $sign, $self, sub {
176 1     1   1 my $top = $self;
177 1         3 my @ext = $key_extractor;
178 1         1 my @sgn = $sign;
179 1         11 for (my $c = $self; $c->isa(__PACKAGE__); $c = $c->{_par}) {
180 0         0 $top = $c;
181 0         0 unshift @ext, $c->{_key};
182 0         0 unshift @sgn, $c->{_sgn};
183             }
184 1         7 my @list = $top->to_perl;
185            
186             # This is not written with efficiency in mind.
187 3         5 my @ordered = sort {
188 1         7 my $cmp = 0;
189 3         19 for (my $ix = 0; $ix < @ext; ++$ix) {
190 3         5 my $ext = $ext[$ix];
191 3         3 my $k1 = do { local $_ = $a; $ext->($_) };
  3         4  
  3         7  
192 3         9 my $k2 = do { local $_ = $b; $ext->($_) };
  3         12  
  3         7  
193 3         8 $cmp = $sgn[$ix] * ($k1 <=> $k2);
194 3 50       8 last if $cmp;
195             };
196 3         6 return $cmp;
197             } @list;
198            
199 1         4 return Acme::IEnumerable->from_list(@ordered)->new;
200 1         11 };
201             }
202            
203             1;
204            
205             #####################################################################
206             #
207             #####################################################################
208             package Acme::IEnumerable::Grouping;
209 3     3   25 use strict;
  3         4  
  3         85  
210 3     3   16 use warnings;
  3         6  
  3         89  
211 3     3   46 use v5.10;
  3         10  
  3         158  
212 3     3   14 use Carp;
  3         5  
  3         233  
213 3     3   17 use base qw/Acme::IEnumerable::List/;
  3         6  
  3         2153  
214            
215             sub from_list {
216 0     0   0 my $class = shift;
217 0         0 my $key = shift;
218 0         0 my $self = Acme::IEnumerable->from_list(@_);
219 0         0 $self->{key} = $key;
220 0         0 bless $self, __PACKAGE__;
221             }
222            
223 0     0   0 sub key { $_[0]->{key} }
224            
225             1;
226            
227             #####################################################################
228             #
229             #####################################################################
230             package Acme::IEnumerable;
231 3     3   27 use strict;
  3         5  
  3         89  
232 3     3   16 use warnings;
  3         4  
  3         141  
233 3     3   53 use v5.10;
  3         10  
  3         138  
234 3     3   23 use Carp;
  3         6  
  3         293  
235            
236             do {
237 3     3   17 no warnings 'once';
  3         5  
  3         3932  
238             *from_list = \&Acme::IEnumerable::List::from_list;
239             *to_array = \&Acme::IEnumerable::to_perl;
240             *order_by = \&Acme::IEnumerable::Ordered::then_by;
241             *order_by_descending =
242             \&Acme::IEnumerable::Ordered::then_by_descending;
243             };
244            
245             sub _create {
246 0     0   0 bless {
247             _new => $_[0],
248             }, __PACKAGE__;
249             }
250            
251 4     4 0 21 sub new { $_[0]->{_new}->() }
252            
253             sub range {
254 0     0 1 0 my ($class, $from, $count) = @_;
255            
256 0 0       0 if (defined $count) {
257             # ...
258             }
259            
260             return _create sub {
261             return sub {
262 0   0     0 state $counter = $from // 0;
263 0         0 return \($counter++);
264 0     0   0 };
265 0         0 };
266             }
267            
268             sub take {
269 0     0 1 0 my ($self, $count) = @_;
270             return _create sub {
271             return sub {
272 0         0 state $left = $count;
273 0 0       0 return unless $left;
274 0         0 $left--;
275 0         0 state $base = $self->new();
276 0         0 my $item = $base->();
277 0 0       0 return unless ref $item;
278 0         0 return $item;
279 0     0   0 };
280 0         0 };
281             }
282            
283             sub take_until {
284 0     0 0 0 my ($self, $predicate) = @_;
285             return $self->take_while(sub {
286 0     0   0 !$predicate->($_);
287 0         0 });
288             }
289            
290             sub take_while {
291 0     0 0 0 my ($self, $predicate) = @_;
292             return _create sub {
293             return sub {
294 0         0 state $base = $self->new();
295 0         0 my $item = $base->();
296 0 0       0 return unless ref $item;
297 0         0 local $_ = $$item;
298 0 0       0 return unless $predicate->($_);
299 0         0 return $item;
300 0     0   0 };
301 0         0 };
302             }
303            
304             sub group_by {
305 0     0 0 0 my ($self, $key_extractor) = @_;
306             return _create sub {
307 0     0   0 my $base = $self->new;
308 0         0 my %temp;
309 0         0 while (1) {
310 0         0 my $item = $base->();
311 0 0       0 last unless ref $item;
312 0         0 local $_ = $$item;
313 0         0 my $key = $key_extractor->($_);
314 0         0 push @{ $temp{$key} }, $_;
  0         0  
315             }
316            
317 0         0 my @temp = map {
318 0         0 Acme::IEnumerable::Grouping->from_list($_, @{$temp{$_}})
  0         0  
319             } keys %temp;
320            
321 0         0 return Acme::IEnumerable->from_list(@temp)->new;
322 0         0 };
323             }
324            
325             sub stack_by {
326 0     0 0 0 my ($self, $key_extractor) = @_;
327             return _create sub {
328             # TODO: make this more lazy?
329 0     0   0 my $base = $self->new;
330 0         0 my @list;
331 0         0 while (1) {
332 0         0 my $item = $base->();
333 0 0       0 last unless ref $item;
334 0         0 local $_ = $$item;
335 0         0 my $key = $key_extractor->($_);
336 0 0 0     0 if (not @list or $key ne $list[-1]->{key}) {
337 0         0 push @list, {
338             key => $key,
339             };
340             }
341 0         0 push @{ $list[-1]->{value} }, $_;
  0         0  
342             }
343            
344 0         0 my @temp = map {
345 0         0 Acme::IEnumerable::Grouping->from_list($_->{key}, @{ $_->{value} })
  0         0  
346             } @list;
347            
348 0         0 return Acme::IEnumerable->from_list(@temp)->new;
349 0         0 };
350             }
351            
352             sub skip {
353 0     0 0 0 my ($self, $count) = @_;
354             return _create sub {
355             return sub {
356 0         0 state $base = $self->new();
357 0         0 state $left = $count;
358 0         0 while ($left) {
359 0         0 my $item = $base->();
360 0 0       0 return unless ref $item;
361 0         0 $left--;
362             }
363 0         0 return $base->();
364 0     0   0 };
365 0         0 };
366             }
367            
368             sub skip_while {
369 0     0 0 0 my ($self, $predicate) = @_;
370             return _create sub {
371             return sub {
372 0         0 state $base = $self->new();
373 0         0 state $skip = 1;
374 0         0 while ($skip) {
375 0         0 my $item = $base->();
376 0 0       0 return unless ref $item;
377 0         0 local $_ = $$item;
378 0         0 $skip &= !! $predicate->($_);
379 0 0       0 return $item unless $skip;
380             }
381 0         0 return $base->();
382 0     0   0 };
383             }
384 0         0 }
385            
386             sub element_at {
387 0     0 0 0 my ($self, $index) = @_;
388            
389 0 0       0 Carp::cluck "Index out of range for element_at" if $index < 0;
390            
391 0         0 my $base = $self->new();
392 0         0 while (1) {
393 0         0 my $item = $base->();
394 0 0       0 do {
395 3     3   15793 use Data::Dumper;
  3         36177  
  3         5783  
396 0     0   0 warn Dumper[$self->count(sub { warn Data::Dumper::Dumper($_); 1; })];
  0         0  
  0         0  
397 0         0 Carp::cluck "Index out of range for element_at";
398             } unless ref $item;
399 0 0       0 return $$item unless $index--;
400             }
401 0         0 Carp::confess("Impossible");
402             }
403            
404             sub last {
405 0     0 0 0 my ($self) = @_;
406 0         0 my $base = $self->new();
407 0         0 my $last;
408 0         0 while (1) {
409 0         0 my $item = $base->();
410 0 0 0     0 croak unless ref $item or ref $last;
411 0 0       0 return $$last unless ref $item;
412 0         0 $last = $item;
413             }
414 0         0 Carp::confess("Impossible");
415             }
416            
417             sub first {
418 0     0 0 0 $_[0]->element_at(0);
419             }
420            
421             sub first_or_default {
422 0     0 0 0 my ($self, $default) = @_;
423 0         0 my $base = $self->new();
424 0         0 my $item = $base->();
425 0 0       0 return $default unless ref $item;
426 0         0 return $$item;
427             }
428            
429             sub last_or_default {
430 0     0 0 0 my ($self, $default) = @_;
431 0         0 my $base = $self->new();
432 0         0 my $item = $base->();
433 0 0       0 return $default unless ref $item;
434 0         0 while (1) {
435 0         0 my $next = $base->();
436 0 0       0 return $$item unless ref $next;
437 0         0 $item = $next;
438             }
439             }
440            
441             sub count {
442 0     0 0 0 my ($self, $predicate) = @_;
443 0   0 0   0 $predicate //= sub { 1 };
  0         0  
444 0         0 my $base = $self->new();
445 0         0 while (1) {
446 0         0 my $counter = 0;
447 0         0 my $item = $base->();
448 0 0       0 return $counter unless ref $item;
449 0         0 local $_ = $$item;
450 0         0 $counter += 0 + !! $predicate->($_);
451             }
452 0         0 Carp::confess("Impossible");
453             }
454            
455             sub select {
456 0     0 0 0 my ($self, $projection) = @_;
457             return _create sub {
458             return sub {
459 0         0 state $base = $self->new();
460 0         0 my $item = $base->();
461 0 0       0 return unless ref $item;
462 0         0 local $_ = $$item;
463 0         0 return \($projection->($_));
464 0     0   0 };
465 0         0 };
466             }
467            
468             sub where {
469 0     0 0 0 my ($self, $predicate) = @_;
470             return _create sub {
471             return sub {
472 0         0 state $base = $self->new();
473 0         0 while (1) {
474 0         0 my $item = $base->();
475 0 0       0 return unless ref $item;
476 0         0 local $_ = $$item;
477 0 0       0 next unless $predicate->($_);
478 0         0 return $item;
479             }
480 0     0   0 };
481 0         0 };
482             }
483            
484             sub zip {
485 0     0 0 0 my ($self, $other) = @_;
486             return _create sub {
487             return sub {
488 0         0 state $base1 = $self->new();
489 0         0 state $base2 = $other->new();
490 0         0 while (1) {
491 0         0 my $item1 = $base1->();
492 0 0       0 return unless ref $item1;
493 0         0 my $item2 = $base2->();
494 0 0       0 return unless ref $item2;
495 0         0 return \[$$item1, $$item2]
496             }
497 0     0   0 };
498 0         0 };
499             }
500            
501             sub pairwise {
502             # TODO: make variant with a seed?
503 0     0 0 0 my ($self, $func) = @_;
504 0         0 return $self->each_cons(2, $func);
505            
506             # ...
507 0         0 my $base = $self->new();
508 0         0 my $prev = $base->();
509 0 0       0 return unless ref $prev;
510 0         0 while (1) {
511 0         0 my $curr = $base->();
512 0 0       0 return unless ref $curr;
513 0         0 $func->($$prev, $$curr);
514 0         0 $prev = $curr;
515             }
516 0         0 Carp::confess("Impossible");
517             }
518            
519             sub each_cons {
520 0     0 0 0 my ($self, $count, $func) = @_;
521 0         0 my $base = $self->new();
522 0         0 my @prev;
523 0         0 while ($count-- > 1) {
524 0         0 my $prev = $base->();
525 0 0       0 return unless ref $prev;
526 0         0 push @prev, $$prev;
527             }
528 0         0 while (1) {
529 0         0 my $curr = $base->();
530 0 0       0 return unless ref $curr;
531 0         0 $func->(@prev, $$curr);
532 0         0 push @prev, $$curr;
533 0         0 shift @prev;
534             }
535 0         0 Carp::confess("Impossible");
536             }
537            
538            
539             sub aggregate {
540 0     0 0 0 my $self = shift;
541 0         0 my $base = $self->new();
542 0         0 my ($func, $seed);
543            
544 0 0       0 if (@_ == 1) {
    0          
545 0         0 $func = shift;
546 0         0 my $item = $base->();
547 0 0       0 croak unless ref $item;
548 0         0 $seed = $$item;
549             } elsif (@_ == 2) {
550 0         0 $seed = shift;
551 0         0 $func = shift;
552             } else {
553             # ...
554             }
555            
556 0         0 while (1) {
557 0         0 my $item = $base->();
558 0 0       0 return $seed unless ref $item;
559 0         0 $seed = $func->($seed, $$item);
560             }
561 0         0 Carp::confess("Impossible");
562             }
563            
564             sub average {
565 0     0 0 0 my ($self) = @_;
566 0         0 my $base = $self->new();
567            
568 0         0 my $item = $base->();
569 0 0       0 return unless ref $item;
570            
571 0         0 my $count = 0;
572 0         0 my $total = 0;
573            
574 0         0 while (1) {
575 0         0 $total += $$item;
576 0         0 $count += 1;
577 0         0 $item = $base->();
578 0 0       0 return $total/$count unless ref $item;
579             }
580             }
581            
582             sub min {
583 0     0 0 0 my ($self) = @_;
584             return $self->aggregate(sub {
585 0 0   0   0 $_[0] < $_[1] ? $_[0] : $_[1]
586 0         0 });
587             }
588            
589             sub max {
590 0     0 0 0 my ($self) = @_;
591             return $self->aggregate(sub {
592 0 0   0   0 $_[0] > $_[1] ? $_[0] : $_[1]
593 0         0 });
594             }
595            
596             sub all {
597 0     0 0 0 my ($self, $predicate) = @_;
598 0         0 my $base = $self->new();
599 0         0 while (1) {
600 0         0 my $item = $base->();
601 0 0       0 return 1 unless ref $item;
602 0         0 local $_ = $$item;
603 0 0       0 return 0 unless $predicate->($_);
604             }
605 0         0 Carp::confess("Impossible");
606             }
607            
608             sub allplus {
609 0     0 0 0 my ($self, $predicate) = @_;
610 0         0 my $base = $self->new();
611 0         0 my $okay = 0;
612 0         0 while (1) {
613 0         0 my $item = $base->();
614 0 0       0 return $okay unless ref $item;
615 0         0 local $_ = $$item;
616 0         0 $okay = $predicate->($_);
617 0 0       0 return 0 unless $okay;
618             }
619 0         0 Carp::confess("Impossible");
620             }
621            
622             sub any {
623 0     0 0 0 my ($self, $predicate) = @_;
624 0   0 0   0 $predicate //= sub { 1 };
  0         0  
625 0         0 my $base = $self->new();
626 0         0 while (1) {
627 0         0 my $item = $base->();
628 0 0       0 return 0 unless ref $item;
629 0         0 local $_ = $$item;
630 0 0       0 return 1 if $predicate->($_);
631             }
632 0         0 Carp::confess("Impossible");
633             }
634            
635             sub reverse {
636 0     0 0 0 my $self = shift;
637 0         0 Acme::IEnumerable->from_list(reverse $self->to_perl);
638             }
639            
640             sub sum {
641 0     0 0 0 my $self = shift;
642 0     0   0 return $self->aggregate(0, sub { $_[0] + $_[1] });
  0         0  
643             }
644            
645             sub to_perl {
646 3     3 0 7 my $self = shift;
647 3         5 my @result;
648 3         21 my $enum = $self->new();
649 3         17 for (my $item = $enum->(); ref $item; $item = $enum->()) {
650 9         19 push @result, $$item;
651             }
652 3         22 @result;
653             }
654            
655             sub to_list {
656 0     0 0   my ($self) = @_;
657 0           Acme::IEnumerable->from_list($self->to_perl);
658             }
659            
660             sub for_each {
661 0     0 0   my ($self, $action) = @_;
662 0           my $enum = $self->new();
663 0           for (my $item = $enum->(); ref $item; $item = $enum->()) {
664 0           local $_ = $$item;
665 0           $action->($_);
666             }
667             }
668            
669             #####################################################################
670             #
671             #####################################################################
672             # sub select_many { ... }
673             # sub contains { ... }
674             # sub sequence_equal { ... }
675             # sub distinct { ... }
676             # sub union { ... }
677             # sub except { ... }
678             # sub intersect { ... }
679             # sub default_if_empty { ... }
680             # sub single_or_default { ... }
681             # sub concat { ... }
682             # sub group_join { ... }
683             # sub join { ... }
684             # sub empty { ... }
685             # sub cast { ... }
686             # sub to_lookup { ...}
687             # sub to_dictionary { ... }
688            
689             #####################################################################
690             #
691             #####################################################################
692             # sub distinct_by { ... }
693             # sub min_by { ... }
694             # sub max_by { ... }
695            
696             # sub to_enumerable { ... }
697            
698            
699             1;
700            
701             __END__