List-Flat

 view release on metacpan or  search on metacpan

lib/List/Flat.pm  view on Meta::CPAN

package List::Flat;
use 5.008001;
use strict;
use warnings;

our $VERSION = "0.003";
$VERSION = eval $VERSION;

use Exporter 5.57 'import';
our @EXPORT_OK = qw/flat flat_r flat_f/;

# if PERL_LIST_FLAT_NO_REF_UTIL environment variable is set to a true
# value, or $List::Flat::NO_REF_UTIL is set to a true value,
# uses the pure-perl version of is_plain_arrayref.
# Otherwise, uses Ref::Util if it can be successfully loaded.

BEGIN {
    my $impl = $ENV{PERL_LIST_FLAT_NO_REF_UTIL}
      || our $NO_REF_UTIL;

    if ( !$impl && eval { require Ref::Util; 1 } ) {
        Ref::Util->import('is_plain_arrayref');
    }
    else {
        *is_plain_arrayref = sub { ref( $_[0] ) eq 'ARRAY' };
    }
}

{
    my $croak;

    sub flat {
        $croak = 1;
        goto &_flat;
        # call _flat with current @_
    }

    sub flat_r {
        undef $croak;
        goto &_flat;
        # call _flat with current @_
    }

    sub _flat {

        my @results;
        my @seens;

        # this uses @_ as the queue of items to process.
        # An item is plucked off the queue. If it's not an array ref,
        # put it in @results.

        # If it is an array ref, check to see if it's the same as any
        # of the arrayrefs we are currently in the middle of processing.
        # If it is, either croak if called as flat, or if called as
        # flat_r, don't do anything -- skip to the next one.
        # If it hasn't been seen before, put all the items it
        # contains back on the @_ queue.
        # Also, for each of the items, push a reference into @seens
        # that contains references to all the arrayrefs we are currently
        # in the middle of processing, plus this arrayref.
        # Note that @seens will be empty at the top level, so we must
        # handle both when it is empty and when it is not.

        while (@_) {

            if ( is_plain_arrayref( my $element = shift @_ ) ) {
                if ( !defined( my $seen_r = shift @seens ) ) {
                    unshift @_, @{$element};
                    unshift @seens, ( ( [$element] ) x scalar @{$element} );
                }
                ## no critic (ProhibitBooleanGrep)
                elsif ( !grep { $element == $_ } @$seen_r ) {
                    ## use critic
                   # until the recursion gets very deep, the overhead in calling
                   # List::Util::none seems to be taking more time than the
                   # additional comparisons required by grep
                    unshift @_, @{$element};
                    unshift @seens,
                      ( ( [ @$seen_r, $element ] ) x scalar @{$element} );
                }
                elsif ($croak) {

lib/List/Flat.pm  view on Meta::CPAN

throw an exception.

 my @list = (1, 2, 3);
 push @list, \@list;
 my @flat = flat(@list);
 # throws exception
 
But it will process it again if it's repeated but not circular.

 my @sublist = ( 4, 5, 6 );
 my @repeated = ( \@sublist, \@sublist, \@sublist);
 my @repeated_flat = flat (@repeated);
 # (4, 5, 6, 4, 5, 6, 4, 5, 6)

=item B<flat_r()>

This function takes its arguments and returns either a list (in
list context) or an array reference (in scalar context) that is
flat, so there are no (non-blessed) array references in the result.

If there are any circular references -- an array reference that has
an entry that points to itself, or an entry that points to another
array reference that refers to the first array reference -- it will
not descend infinitely. It skips any reference that it is currently
processing. So:

 my @list = (1, 2, 3);
 push @list, \@list;
 my @flat = flat(@list);
 # (1, 2, 3)
 
But it will process it again if it's repeated but not circular.

 my @sublist = ( 4, 5, 6 );
 my @repeated = ( \@sublist, \@sublist, \@sublist);
 my @repeated_flat = flat (@repeated);
 # (4, 5, 6, 4, 5, 6, 4, 5, 6)
 
=item B<flat_f()>

This function takes its arguments and returns either a list (in
list context) or an array reference (in scalar context) that is
flat, so there are no (non-blessed) array references in the result.

It does not check for circular references, and so will go into an 
infinite loop with something like

 @a = ( 1, 2, 3);
 push @a, \@a;
 @b = flat_f(\@a);

So don't do that. Use C<flat()> or C<flat_r()> instead.

When it is fed non-infinite lists, this function seems to be about 
twice as fast as C<flat()>.

=back

=head1 CONFIGURATION AND ENVIRONMENT

The functions will normally use Ref::Util to determine whether an
element is an array reference or not, but if the environment variable
$PERL_LIST_FLAT_NO_REF_UTIL is set to a true value, or the perl
variable List::Flat::NO_REF_UTIL is set to a true value before
importing it, it will use its internal pure-perl implementation.

=head1 DEPENDENCIES

It has one optional dependency, L<Ref::Util|Ref::Util>. 
If it is not present, a pure perl implementation is used instead.

=head1 SEE ALSO

There are several other modules on CPAN that do similar things.

=over

=item Array::DeepUtils

I have not tested this code, but it appears that its collapse()
routine does not handle circular references.  Also, it must be
passed an array reference rather than a list.

=item List::Flatten

List::Flatten flattens lists one level deep only, so

  1, 2, [ 3, [ 4 ] ]

is returned as 

  1, 2, 3, [ 4 ]

This might be, I suppose, useful in some circumstance or other.

=item List::Flatten::Recursive

The code from this module works well and does the same thing as
C<flat_r()>, but it seems to be somewhat slower than List::Flat (in
my testing; better testing welcome) due to its use of recursive
subroutine calls rather than using a queue of items to be processed.
Moreover, it is reliant on Exporter::Simple, which apparently does
not pass tests on perls newer than 5.10.

=item List::Flatten::XS

This is very fast and is worth using if one can accept its limitations.
These are, however, significant:

=over

=item *

It flattens blessed array references as well as unblessed ones,
which means that any array-based objects (for example,
L<Path::Tiny|Path::Tiny> objects) will be flattened as well.
Array-based objects aren't all that common, but that's not usually
what's desired.

=item *

Like all XS modules it requires a C compiler on the host system to be
installed, or some kind of special binary installation (e.g., ActiveState's 
ppm).

=item *

It goes into an infinite loop with circular references. 



( run in 1.944 second using v1.01-cache-2.11-cpan-ceb78f64989 )