File Coverage

File:lib/List/Enumerator/Role.pm
Coverage:80.8%

linestmtbrancondsubpodtimecode
1package List::Enumerator::Role;
2
5
5
5
175
13
184
use Exception::Class ( "StopIteration" );
3
4
5
5
5
28
7
60
use List::Util;
5
5
5
5
198
13
38
use List::MoreUtils;
6
7
5
5
5
31
9
25
use base qw/Class::Accessor::Fast/;
8
5
5
5
27
9
29
no warnings 'once';
9
10__PACKAGE__->mk_accessors(qw/is_beginning/);
11
12# this is mix-in module
13
14sub new {
15
285
1
781
        my ($class, %opts) = @_;
16
285
2434
        my $self = $class->SUPER::new(\%opts);
17
285
34247
        $self->can("BUILD") && $self->BUILD(\%opts);
18
285
3551
        $self->is_beginning(1);
19
285
3713
        $self;
20}
21
22sub next {
23
1102
0
1807
        my ($self) = @_;
24
1102
2952
        $self->is_beginning(0);
25
1102
11111
        $self->_next();
26}
27
28sub rewind {
29
198
0
316
        my ($self) = @_;
30
198
558
        unless ($self->is_beginning) {
31
30
373
                $self->_rewind();
32
30
87
                $self->is_beginning(1);
33        }
34
198
1824
        $self;
35}
36
37sub select {
38
4
0
13
        my ($self, $block) = @_;
39
4
11
        $self->rewind;
40
41        List::Enumerator::Sub->new(
42                next => sub {
43
20
145
                        local $_;
44
20
24
                        do {
45
38
157
                                $_ = $self->next;
46                        } while (!$block->($_));
47
18
144
                        $_;
48                },
49                rewind => sub {
50
0
0
                        $self->rewind;
51                }
52
4
32
        );
53}
54*find_all = \&select;
55
56sub reject {
57
1
0
2
        my ($self, $block) = @_;
58        $self->select(sub {
59
10
21
                !$block->($_);
60
1
10
        });
61}
62
63sub reduce {
64
12
0
45
        my ($self, $result, $block) = @_;
65
12
38
        $self->rewind;
66
67
5
5
5
31
9
23
        no strict 'refs';
68
69
12
35
        if (@_ == 2) {
70
2
5
                $block = $result;
71
2
3
                $result = undef;
72        };
73
74
12
24
        my $caller = caller;
75
12
12
19
47
        local *{$caller."::a"} = \my $a;
76
12
12
19
33
        local *{$caller."::b"} = \my $b;
77
78
12
43
        my @list = $self->to_list;
79
12
118
        unshift @list, $result if defined $result;
80
81
12
16
        $a = shift @list;
82
12
28
        for (@list) {
83
67
145
                $b = $_;
84
67
124
                $a = $block->($a, $b);
85        };
86
87
12
114
        $a;
88}
89*inject = \&reduce;
90
91sub slice {
92
22
0
65
        my ($self, $start, $end) = @_;
93
22
65
        my @list = $self->to_list;
94
22
566
        if (defined $end) {
95
15
49
                return () if abs $start > @list;
96
14
31
                $start = @list + $start if $start < 0;
97
14
29
                $end = @list + $end if $end < 0;
98
14
46
                $end = $#list if $end > $#list;
99
14
30
                return () if $start > @list;
100
14
38
                return () if $start > $end;
101
102
13
38
                my @ret = @list[$start .. $end];
103
13
27
                if (wantarray) {
104
6
67
                        @ret ? @ret : ();
105                } else {
106
7
26
                        @ret ? List::Enumerator::Array->new(array => \@ret)
107                             : List::Enumerator::Array->new(array => []);
108                }
109        } else {
110
7
63
                $list[$start];
111        }
112};
113
114sub find {
115
9
0
28
        my ($self, $target) = @_;
116
9
9
38
38
        my $block = ref($target) eq "CODE" ? $target : sub { $_ eq $target };
117
9
12
        my $ret;
118        $self->each(sub {
119
20
44
                if ($block->($self)) {
120
7
26
                        $ret = $_;
121
7
22
                        $self->stop;
122                }
123
9
65
        });
124
9
71
        $ret;
125}
126
127sub first {
128
2
0
10
        my ($self) = @_;
129
2
10
        $self->rewind;
130
2
9
        my $ret = $self->next;
131
2
22
        $self->rewind;
132
2
9
        $ret;
133}
134
135sub last {
136
2
0
4
        my ($self) = @_;
137
2
10
        $self->to_a->[-1];
138}
139
140sub max {
141
1
0
3
        my ($self, $block) = @_;
142
1
4
        List::Util::max $self->to_list;
143}
144
145sub max_by {
146
1
0
3
        my ($self, $block) = @_;
147
1
7
        $self->sort_by($block)->last;
148}
149
150sub min {
151
1
0
2
        my ($self, $block) = @_;
152
1
5
        List::Util::min $self->to_list;
153}
154
155sub min_by {
156
1
0
3
        my ($self, $block) = @_;
157
1
5
        $self->sort_by($block)->first;
158}
159
160sub minmax_by {
161
2
0
4
        my ($self, $block) = @_;
162
2
3
7
6
        $block = sub { $_ } unless $block;
163
2
7
        my @ret = $self->sort_by($block)->to_list;
164
2
37
        wantarray? ($ret[0], $ret[$#ret]) : [ $ret[0], $ret[$#ret] ];
165}
166*minmax = \&minmax_by;
167
168sub sort_by {
169
5
0
9
        my ($self, $block) = @_;
170
17
42
        List::Enumerator::Array->new(array => [
171                map {
172
17
30
                        $_->[0];
173                }
174                sort {
175
17
115
                        $a->[1] <=> $b->[1];
176                }
177                map {
178
5
15
                        [$_, $block->($_)];
179                }
180                $self->to_list
181        ]);
182}
183
184sub sort {
185
3
0
16
        my ($self, $block) = @_;
186
3
3
17
8
        my @ret = $block ? sort { $block->($a, $b) } $self->to_list : sort $self->to_list;
187
3
58
        wantarray? @ret : List::Enumerator::Array->new(array => \@ret);
188}
189
190sub sum {
191
3
0
8
        my ($self) = @_;
192
3
11
14
20
        $self->reduce(0, sub { $a + $b });
193}
194
195sub uniq {
196
2
0
14
        my ($self) = @_;
197
2
9
        my @ret = List::MoreUtils::uniq($self->to_list);
198
2
101
        wantarray? @ret : List::Enumerator::Array->new(array => \@ret);
199}
200
201sub grep {
202
2
0
14
        my ($self, $block) = @_;
203
2
13
10
75
        my @ret = grep { $block->($_) } $self->to_list;
204
2
28
        wantarray? @ret : List::Enumerator::Array->new(array => \@ret);
205}
206
207sub compact {
208
3
0
14
        my ($self) = @_;
209
3
13
13
48
        my @ret = grep { defined } $self->to_list;
210
3
20
        wantarray? @ret : List::Enumerator::Array->new(array => \@ret);
211}
212
213sub reverse {
214
2
0
21
        my ($self) = @_;
215
2
8
        my @ret = reverse $self->to_list;
216
2
31
        wantarray? @ret : List::Enumerator::Array->new(array => \@ret);
217}
218
219sub flatten {
220
4
0
18
        my ($self, $level) = @_;
221
4
15
        my $ret = _flatten($self->to_a, $level);
222
4
22
        wantarray? @$ret : List::Enumerator::Array->new(array => $ret);
223}
224
225sub _flatten {
226
10
48
        my ($array, $level) = @_;
227
6
23
        (defined($level) && $level <= 0) ? $array : [
228                map {
229
10
28
40
75
                        (ref($_) eq 'ARRAY') ? @{ _flatten($_, defined($level) ? $level - 1 : undef) } : $_;
230                }
231                @$array
232        ];
233}
234
235sub length {
236
12
0
21
        my ($self) = @_;
237
12
12
15
34
        scalar @{[ $self->to_list ]};
238}
239*size = \&length;
240
241sub is_empty {
242
2
0
3
        my ($self) = @_;
243
2
8
        !$self->length;
244}
245
246sub index_of {
247
10
0
28
        my ($self, $target) = @_;
248
10
22
        $self->rewind;
249
250
10
21
50
100
        my $block = ref($target) eq "CODE" ? $target : sub { $_ eq $target };
251
252
10
14
        my $ret = 0;
253
10
15
        return eval {
254
10
51
                while (1) {
255
31
59
                        my $item = $self->next;
256
28
261
                        return $ret if $block->(local $_ = $item);
257
21
62
                        $ret++;
258                }
259
0
0
        }; if (Exception::Class->caught("StopIteration") ) { } else {
260
0
0
                my $e = Exception::Class->caught();
261
0
0
                ref $e ? $e->rethrow : die $e if $e;
262        }
263
264
0
0
        undef;
265}
266*find_index = \&index_of;
267
268
269sub chain {
270
5
0
23
        my ($self, @others) = @_;
271
5
18
        $self->rewind;
272
273
5
6
        my ($elements, $current);
274
5
10
11
24
        $elements = List::Enumerator::E([ map { List::Enumerator::E($_)->rewind } $self, @others ]);
275
5
20
        $current = $elements->next;
276
277
5
45
        my @cache = ();
278
5
8
        my $i = 0;
279        my $ret = List::Enumerator::Sub->new(
280                next => sub {
281
138
990
                        my $ret;
282
138
233
                        if ($i < @cache) {
283
72
93
                                $ret = $cache[$i];
284                        } else {
285
66
74
                                eval {
286
66
388
                                        $ret = $current->next;
287
57
763
                                        push @cache, $ret;
288
66
223
                                }; if (Exception::Class->caught("StopIteration") ) {
289
9
32
                                        $current = $elements->next;
290
5
50
                                        $ret = $current->next;
291
5
67
                                        push @cache, $ret;
292                                } else {
293
57
566
                                        my $e = Exception::Class->caught();
294
57
402
                                        ref $e ? $e->rethrow : die $e if $e;
295                                }
296                        }
297
134
150
                        $i++;
298
134
485
                        $ret;
299                },
300                rewind => sub {
301
6
53
                        $i = 0;
302                }
303
5
63
        );
304
305
5
37
        wantarray? $ret->to_list : $ret;
306}
307
308sub take {
309
22
0
78
        my ($self, $arg) = @_;
310
22
49
        $self->rewind;
311
312
22
25
        my $ret;
313
22
55
        if (ref $arg eq "CODE") {
314                $ret = List::Enumerator::Sub->new(
315                        next => sub {
316
10
82
                                local $_ = $self->next;
317
10
22
                                if ($arg->($_)) {
318
8
57
                                        $_;
319                                } else {
320
2
15
                                        StopIteration->throw;
321                                }
322                        },
323                        rewind => sub {
324
0
0
                                $self->rewind;
325                        }
326
2
19
                );
327        } else {
328
20
25
                my $i;
329                $ret = List::Enumerator::Sub->new(
330                        next => sub {
331
162
1297
                                if ($i++ < $arg) {
332
142
259
                                        $self->next;
333                                } else {
334
20
90
                                        StopIteration->throw;
335                                }
336                        },
337                        rewind => sub {
338
0
0
                                $self->rewind;
339
0
0
                                $i = 0;
340                        }
341
20
203
                );
342        }
343
22
119
        wantarray? $ret->to_list : $ret;
344}
345*take_while = \&take;
346
347sub drop {
348
10
0
21
        my ($self, $arg) = @_;
349
10
23
        $self->rewind;
350
351
10
12
        my $ret;
352
10
27
        if (ref $arg eq "CODE") {
353
2
4
                my $first;
354                $ret = List::Enumerator::Sub->new(
355                        next => sub {
356
10
71
                                my $ret;
357
10
18
                                unless ($first) {
358
2
10
4
57
                                        do { $first = $self->next } while ($arg->(local $_ = $first));
359
2
13
                                        $ret = $first;
360                                } else {
361
8
17
                                        $ret = $self->next;
362                                }
363
10
39
                                $ret;
364                        },
365                        rewind => sub {
366
0
0
                                $self->rewind;
367
0
0
                                $first = undef;
368                        }
369
2
22
                );
370        } else {
371
8
10
                my $i = $arg;
372                $ret = List::Enumerator::Sub->new(
373                        next => sub {
374
62
522
                                $self->next while (0 < $i--);
375
62
111
                                $self->next;
376                        },
377                        rewind => sub {
378
1
11
                                $self->rewind;
379
1
3
                                $i = $arg;
380                        }
381
8
93
                );
382        }
383
10
55
        wantarray? $ret->to_list : $ret;
384}
385*drop_while = \&drop;
386
387sub every {
388
2
0
5
        my ($self, $block) = @_;
389
2
7
        for ($self->to_list) {
390
7
54
                return 0 unless $block->($_);
391        }
392
1
10
        return 1;
393}
394*all = \&every;
395
396sub some {
397
6
0
11
        my ($self, $block) = @_;
398
6
17
        for ($self->to_list) {
399
14
91
                return 1 if $block->($_);
400        }
401
3
23
        return 0;
402}
403*any = \&some;
404
405
406sub none {
407
6
0
13
        my ($self, $block) = @_;
408
6
11
17
35
        $block = sub { $_ } unless $block;
409
410
6
16
        for ($self->to_list) {
411
17
93
                return 0 if $block->($_);
412        }
413
2
16
        return 1;
414}
415
416sub one {
417
6
0
10
        my ($self, $block) = @_;
418
6
12
16
29
        $block = sub { $_ } unless $block;
419
420
6
7
        my $ret = 0;
421
6
17
        for ($self->to_list) {
422
22
110
                if ($block->($_)) {
423
6
25
                        if ($ret) {
424
2
9
                                return 0;
425                        } else {
426
4
10
                                $ret = 1;
427                        }
428                }
429        }
430
4
29
        return $ret;
431}
432
433sub zip {
434
10
0
52
        my ($self, @others) = @_;
435
10
35
        $self->rewind;
436
437
16
44
        my $elements = [
438                map {
439
10
20
                        List::Enumerator::E($_)->rewind;
440                }
441                @others
442        ];
443
444
10
21
        my @cache = ();
445        my $ret = List::Enumerator::Sub->new(
446                next => sub {
447
58
440
                        my $ret = [];
448
58
137
                        push @$ret, $self->next;
449
49
315
                        for (@$elements) {
450
86
92
                                my $n;
451
86
94
                                eval {
452
86
213
                                        $n = $_->next;
453
86
763
                                }; if (Exception::Class->caught("StopIteration") ) {
454
3
9
                                        $n = undef;
455                                } else {
456
83
815
                                        my $e = Exception::Class->caught();
457
83
663
                                        ref $e ? $e->rethrow : die $e if $e;
458                                }
459
86
194
                                push @$ret, $n;
460                        }
461
49
87
                        push @cache, $ret;
462
49
128
                        $ret;
463                },
464                rewind => sub {
465
0
0
                        my $i = 0;
466                        $_->next_sub(sub {
467
0
0
                                if ($i < @cache) {
468
0
0
                                        $cache[$i++];
469                                } else {
470
0
0
                                        StopIteration->throw;
471                                }
472
0
0
                        });
473                        $_->rewind_sub(sub {
474
0
0
                                $i = 0;
475
0
0
                        });
476                }
477
10
201
        );
478
479
10
82
        wantarray? $ret->to_list : $ret;
480}
481
482sub with_index {
483
1
0
2
        my ($self, $start) = @_;
484
1
4
        $self->zip(List::Enumerator::E($start)->countup);
485}
486
487sub countup {
488
23
0
44
        my ($self, $lim) = @_;
489
23
23
41
771
        my $start = eval { $self->next } || 0;
490
23
218
        my $i = $start;
491        List::Enumerator::Sub->new(
492                next => sub {
493
200
1939
                        ($lim && $i > $lim) && StopIteration->throw;
494
192
681
                        $i++;
495                },
496                rewind => sub {
497
5
42
                        $i = $start;
498                }
499
23
260
        );
500}
501*countup_to = \&countup;
502*to = \&countup;
503
504
505sub cycle {
506
5
0
8
        my ($self) = @_;
507
5
21
        $self->rewind;
508
509
5
9
        my @cache = ();
510        List::Enumerator::Sub->new(
511                next => sub {
512
22
160
                        my ($this) = @_;
513
514
22
22
                        my $ret;
515
22
22
                        eval {
516
22
52
                                $ret = $self->next;
517
17
161
                                push @cache, $ret;
518
22
3944
                        }; if (Exception::Class->caught("StopIteration") ) {
519
5
14
                                my $i = -1;
520                                $this->next_sub(sub {
521
41
455
                                        $cache[++$i % @cache];
522
5
63
                                });
523
5
69
                                $ret = $this->next;
524                        } else {
525
17
177
                                my $e = Exception::Class->caught();
526
17
123
                                ref $e ? $e->rethrow : die $e if $e;
527                        }
528
22
90
                        $ret;
529                },
530                rewind => sub {
531
0
0
                        $self->rewind;
532
0
0
                        @cache = ();
533                }
534
5
91
        );
535}
536
537sub join {
538
3
0
15
        my ($self, $sep) = @_;
539
3
15
        join $sep || "", $self->to_list;
540}
541
542sub group_by {
543
3
0
8
        my ($self, $block) = @_;
544        $self->reduce({}, sub {
545
32
37
                local $_ = $b;
546
32
49
                my $r = $block->($b);
547
32
223
                $a->{$r} ||= [];
548
32
32
33
58
                push @{ $a->{$r} }, $b;
549
32
67
                $a;
550
3
28
        });
551}
552
553sub partition {
554
2
0
4
        my ($self, $block) = @_;
555        my $ret = $self->group_by(sub {
556
20
40
                $block->($_) ? 1 : 0;
557
2
13
        });
558
559
2
27
        wantarray? ($ret->{1}, $ret->{0}) : [$ret->{1}, $ret->{0}];
560}
561
562sub is_include {
563
4
0
6
        my ($self, $target) = @_;
564
4
8
24
36
        $self->some(sub { $_ eq $target });
565}
566*include = \&is_include;
567
568sub map {
569
3
0
18
        my ($self, $block) = @_;
570
3
11
        $self->rewind;
571
572        my $ret = List::Enumerator::Sub->new(
573                next => sub {
574
28
218
                        local $_ = $self->next;
575
26
113
                        $block->($_);
576                },
577                rewind => sub {
578
0
0
                        $self->rewind;
579                }
580
3
34
        );
581
3
16
        wantarray? $ret->to_list : $ret;
582}
583*collect = \&map;
584
585sub each {
586
51
0
97
        my ($self, $block) = @_;
587
51
129
        $self->rewind;
588
589
51
75
        eval {
590
51
1220
                while (1) {
591
324
752
                        local $_ = $self->next;
592
273
1043
                        $block->($_) if $block;
593                }
594
51
826
        }; if (Exception::Class->caught("StopIteration") ) { } else {
595
0
0
                my $e = Exception::Class->caught();
596
0
0
                ref $e ? $e->rethrow : die $e if $e;
597        }
598
599
51
162
        $self;
600}
601
602sub to_list {
603
45
0
66
        my ($self) = @_;
604
605
45
64
        my @ret = ();
606        $self->each(sub {
607
253
434
                push @ret, $_;
608
45
256
        });
609
610
45
652
        wantarray? @ret : [ @ret ];
611}
612
613sub each_index {
614
1
0
17
        my ($self, $block) = @_;
615
1
28
        $self->rewind;
616
617
1
3
        my $i = 0;
618
1
2
        eval {
619
1
2
                while (1) {
620
4
48
                        $self->next;
621
3
26
                        local $_ = $i++;
622
3
14
                        $block->($_) if $block;
623                }
624
1
8
        }; if (Exception::Class->caught("StopIteration") ) { } else {
625
0
0
                my $e = Exception::Class->caught();
626
0
0
                ref $e ? $e->rethrow : die $e if $e;
627        }
628
629
1
7
        wantarray? $self->to_list : $self;
630}
631
632sub each_slice {
633
3
0
7
        my ($self, $n, $block) = @_;
634
3
9
        $self->rewind;
635
636        my $ret = List::Enumerator::Sub->new(
637                next => sub {
638
12
95
                        my $arg = [];
639
12
17
                        my $i = $n - 1;
640
12
27
                        push @$arg, $self->next;
641
10
77
                        while ($i--) {
642
17
23
                                eval {
643
17
85
                                        push @$arg, $self->next;
644
17
120
                                }; if (Exception::Class->caught("StopIteration") ) { } else {
645
14
138
                                        my $e = Exception::Class->caught();
646
14
115
                                        ref $e ? $e->rethrow : die $e if $e;
647                                }
648                        }
649
10
37
                        $arg;
650                },
651                rewind => sub {
652
0
0
                        $self->rewind;
653                }
654
3
34
        );
655
3
11
        if ($block) {
656
2
6
                $ret->each($block);
657        }
658
3
28
        wantarray? $ret->to_list : $ret;
659}
660
661sub each_cons {
662
3
0
8
        my ($self, $n, $block) = @_;
663
3
8
        $self->rewind;
664
665
3
5
        my @memo = ();
666        my $ret = List::Enumerator::Sub->new(
667                next => sub {
668
12
116
                        if (@memo < $n) {
669
3
5
                                my $i = $n;
670
3
15
                                push @memo, $self->next while $i--;
671                        } else {
672
9
11
                                shift @memo;
673
9
17
                                push @memo, $self->next;
674                        }
675
10
90
                        [ @memo ];
676                },
677                rewind => sub {
678
0
0
                        $self->rewind;
679
0
0
                        @memo = ();
680                }
681
3
37
        );
682
3
10
        if ($block) {
683
2
8
                $ret->each($block);
684        }
685
3
78
        wantarray? $ret->to_list : $ret;
686}
687
688sub to_a {
689
24
0
66
        my ($self) = @_;
690
24
80
        [ $self->to_list ];
691}
692
693sub expand {
694
3
0
31
        my ($self) = @_;
695
3
13
        List::Enumerator::Array->new(array => $self->to_a);
696}
697*dup = \&expand;
698
699sub dump {
700
1
0
2
        my ($self) = @_;
701
1
8
        require Data::Dumper;
702
1
5
        Data::Dumper->new([ $self->to_a ])->Purity(1)->Terse(1)->Dump;
703}
704
705
706sub _next {
707
0
0
        die "Not implemented.";
708}
709
710sub _rewind {
711
0
0
        die "Not implemented.";
712}
713
714sub stop {
715
57
0
99
        my ($self) = @_;
716
57
292
        StopIteration->throw;
717}
718
7191;