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 )