building_arch has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks, I am having difficulties in pruning some empty array references from a nested array. I have tried several strategies with no success. Here below I am going to paste some examples of my trials. It seems to me that the problems here are two: how to check the references for emptiness and how to delete them if they are empty. Thank you in advance. Best regards Luca
use Data::Dumper; use feature "say"; no strict; no warnings; use Scalar::Util 'reftype'; use Array::DeepUtils qw/:all/; sub prune1 { my $count = 0; my @arr = @{ $_[0] }; my $counter = 0; foreach ( @arr ) { if ( ref ( $_ ) ) { if ( not ( @{ $_[0] } ) ) { delete ( $arr[$counter] ); } else { prune1( $_ ); } $counter++; } } return ( \@{$_[0]} ); } sub prune2 { my $node = $_[0]; if ( ref( $node ) ) { for ( my $i = $#$node; $i >= 0; $i-- ) { my $subnode = $node->[$i]; if ( ref( $subnode ) and ( @$subnode == 0 ) ) { delete $node->[$i]; } else { prune2( $subnode ); } } } return ( $_[0] ); } sub prune3 { my ( $node ) = @_; my $type = reftype $node // ''; if ( $type eq 'ARRAY' ) { for ( my $i = $#$node; $i >= 0; $i-- ) { my $subnode = $node->[$i]; my $subtype = reftype( $subnode ) // ''; if ( ( $subtype eq 'ARRAY' ) and ( @$subnode == 0 ) ) { delete $node->[$i]; } else { prune3( $subnode ); } } } return ( $_[0] ); } sub prune4 { my ( $arref ) = @_; my @arr1 = @$arref; my @basket1; my $counter1 = 0; foreach my $ref1 ( @arr1 ) { my @elts1 = @$ref1; my @basket2; my $counter2 = 0; foreach my $ref2 ( @elts1 ) { my @elts2 = @$ref2; my @basket3; my $counter3 = 0; foreach my $ref3 ( @elts2 ) { my @elts3 = @$ref3; my @basket4; unless ( ( @elts3 == undef ) or ( @elts3 == () ) or ( +@{ $ref3->[$counter3] } ) ) { push ( @basket3, [ @basket4, $ref3 ] ); } $counter3++; } unless ( ( @elts2 == undef ) or ( @elts2 == () ) or ( @{ $ +ref2->[$counter2] } ) ) { push ( @basket2, [ @basket3, $ref2 ] ); } $counter2++; } unless ( ( @elts1 == undef ) or ( @elts1 == () ) or ( @{ $ref1 +->[$counter1] } ) ) { push ( @basket1, [ @basket2, $ref1 ] ); } $counter1++; } return ( \@basket1 ); } sub prune5 { # USING Array::Deeputils my $node = $_[0]; purge( $node, [] ); purge( $node, '[]' ); purge( $node, "" ); return ( $node ); } my @a = ( 1, 2, [], 4, [ 11, 22, [], 44 ] ); my $resultref = prune1( \@a ); my @result = @$resultref; #my $resultref = prune2( \@a ); my @result = @$resultref; #my $resultref = prune3( \@a ); my @result = @$resultref; #my $resultref = prune4( \@a ); my @result = @$resultref; #my $resultref = prune5( \@a ); my @result = @$resultref; say "result: " . Dumper( @result );

Replies are listed 'Best First'.
Re: pruning of empty arrayrefs in nested array
by BrowserUk (Patriarch) on Apr 26, 2015 at 18:18 UTC

    The likely cause of your problems is that (despite the name) delete on an array element doesn't completely remove that element from the array, it only sets it to be undef.

    The simplest way to completely remove unwanted from elements from an array is to use grep:  @array = grep{ <test for wanted> } @array;

    But as you want to do it recursively, something like this might work for your problem (untested!):

    sub prune { my $aref = shift; @{ $aref } = map { if( ref() eq 'ARRAY' ) { ## an arrayref? if( @{ $_ ) { ## Has values, recurse and return the prune +d ref (or nothing if it was empty after pruning). prune( $_ ); } else { ## return nothing (); } } else { ## not an array ref, return it whatever it is $_; } } @{ $aref }; return @{ $aref } ? $aref : (); ## Still contains something, retur +n it; or nothing. } my $refWanted = prune( \@nested );

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority". I'm with torvalds on this
    In the absence of evidence, opinion is indistinguishable from prejudice. Agile (and TDD) debunked
      The likely cause of your problems is that (despite the name) delete on an array element doesn't completely remove that element from the array, it only sets it to be undef.
      I certainly do not want to be nitpicking with you, but it seems to me that deleting an array element does not exactly sets it to undef, but rather to "empty slot". If you undef an array element, you obtain something like this:
      DB<14> x \@a 0 ARRAY(0x6005d1060) 0 0 1 1 2 undef 3 3 4 4
      whereas if you delete it (or if you don't define it in the first place), you get:
      DB<4> x \@array; 0 ARRAY(0x600500b60) 0 'O' 1 1 2 empty slot 3 3 4 4
      It does not make a practical difference for the programmer checking if the value is defined, both values will appear to be undefined, but it seems that, internally, Perl is not doing exactly the same thing. I have no idea of what the difference is.

      Je suis Charlie.
        I certainly do not want to be nitpicking with you, but it seems to me that deleting an array element does not exactly sets it to undef,
        @a = 1..3;; pp \@a;; [1, 2, 3] delete $a[1];; pp \@a;; [1, undef, 3]

        Close enough for me :)

        but rather to "empty slot".... I have no idea of what the difference is.

        A Perl array consists of a C array of pointers to scalars.

        delete sets the value of the C pointer to 0; and the debugger reports that as "Empty slot"; but in all other circumstances is detected and reported as undef.

        If you manually set the value of an array element to undef, then the C array pointer is set to point to the undef, which is a static pointer value that is defined to be undefined :)

        It does not make a practical difference for the programmer

        Agreed!


        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority". I'm with torvalds on this
        In the absence of evidence, opinion is indistinguishable from prejudice. Agile (and TDD) debunked
Re: pruning of empty arrayrefs in nested array
by hdb (Monsignor) on Apr 26, 2015 at 18:05 UTC

    Instead of deleting from an array I am using grep. The disadvantage is that this involves some copying (at least twice) so it involves some time and memory.

    use strict; use warnings; use Data::Dumper; sub hdb_prune { my $arr = shift; my @res = grep { not ref $_ or hdb_prune( $_ ) } @$arr; return () if not @res; @$arr = @res; return $arr; } my @arr = ( 1, 2, [], 4, [ 11, 22, [], 44, [ 12, []] ] ); hdb_prune( \@arr ); print Dumper \@arr;
Re: pruning of empty arrayrefs in nested array
by building_arch (Novice) on Apr 26, 2015 at 23:43 UTC
    Thank you very much to everybody. Both solutions work very well, that with grep and that with map. I have tried to make the map one work with foreach, but I gave up, because of the inability of erasing the empty references without leaving the slots undefined. Again, thank you. Best regard Luca
    sub prune_with_map { my $aref = shift; @{ $aref } = map { if( ref( $_ ) ) { if( @{ $_ } ) { prune_with_map( $_ ); } else { (); } } else { $_; } } @{ $aref }; return ( $aref ); } sub prune_with_foreach { my $aref = shift; my $count = 0; foreach ( @$aref ) { if( ref( $_ ) ) { if( @{ $_ } ) { prune_with_foreach( $_ ); } else { delete $aref->[ $count ]; } } else { $_; } $count++; } return ( $aref ); }