Iterator-Flex
view release on metacpan or search on metacpan
lib/Iterator/Flex/Product.pm view on Meta::CPAN
package Iterator::Flex::Product;
# ABSTRACT: An iterator which produces a Cartesian product of iterators
use v5.28;
use strict;
use warnings;
use experimental qw( signatures declared_refs refaliasing );
our $VERSION = '0.34';
use Iterator::Flex::Utils
qw( RETURN STATE EXHAUSTION :IterAttrs :IterStates can_meth throw_failure );
use Iterator::Flex::Factory 'to_iterator';
use parent 'Iterator::Flex::Base';
use Ref::Util;
use List::Util;
use namespace::clean;
sub new ( $class, @args ) {
my $pars = Ref::Util::is_hashref( $args[-1] ) ? pop @args : {};
throw_failure( parameter => 'not enough parameters' )
unless @args;
my @iterators;
my @keys;
# distinguish between ( key => iterator, key =>iterator ) and ( iterator, iterator );
if ( Ref::Util::is_ref( $args[0] ) ) {
@iterators = @args;
}
else {
throw_failure( parameter => 'expected an even number of arguments' )
if @args % 2;
while ( @args ) {
push @keys, shift @args;
push @iterators, shift @args;
}
}
$class->SUPER::new( { keys => \@keys, depends => \@iterators, value => [] }, $pars );
}
sub construct ( $class, $state ) { ## no critic (ExcessComplexity)
throw_failure( parameter => q{state must be a HASH reference} )
unless Ref::Util::is_hashref( $state );
$state->{value} //= [];
my ( \@depends, \@keys, \@value, $thaw )
= @{$state}{qw[ depends keys value thaw ]};
# transform into iterators if required.
my @iterators
= map { to_iterator( $_, { ( +EXHAUSTION ) => RETURN } ) } @depends;
# can only work if the iterators support a rewind method
throw_failure( parameter => q{all iterables must provide a rewind method} )
unless List::Util::all { defined can_meth( $_, 'rewind' ) } @iterators;
throw_failure( parameter => q{number of keys not equal to number of iterators} )
if @keys && @keys != @iterators;
@value = map { $_->current } @iterators
if $thaw;
## no critic ( AmbiguousNames )
my @set = ( 1 ) x @value;
my @src = map { [ $_, $_->can( 'is_exhausted' ) ] } @iterators;
my $self;
my $iterator_state;
my %params = (
( +_SELF ) => \$self,
( +STATE ) => \$iterator_state,
( +NEXT ) => sub {
return $self->signal_exhaustion if $iterator_state == IterState_EXHAUSTED;
# first time through
if ( !@value ) {
for my $src ( @src ) {
my ( $iter, $is_exhausted ) = $src->@*;
push @value, $iter->();
return $self->signal_exhaustion
if $iter->$is_exhausted;
}
@set = ( 1 ) x @value;
}
else {
my ( $iter, $is_exhausted ) = $src[-1]->@*;
$value[-1] = $iter->();
if ( $iter->$is_exhausted ) {
$set[-1] = 0;
my $idx = @src - 1;
while ( --$idx >= 0 ) {
( $iter, $is_exhausted ) = $src[$idx]->@*;
( run in 0.550 second using v1.01-cache-2.11-cpan-75ffa21a3d4 )