Perl push item to subarray

Viewed 71

I have 2 empty arrays that I add items to using a for loop.

@main = (
    "a 1 b 2 c 3 ",
    "d 4",
    "e 5 f 6 g 7 h 8",
    "i 9 j 10",
);
@arr1 = (); #only gets the letters
#supposed to look like:
#( 
#[a,b,c]
#[d]
#[e,f,g,h]
#[i,j]
#)

@arr2 = (); #only gets the numbers
#supposed to look like:
#( 
#[1,2,3]
#[4]
#[5,6,7,8]
#[9,10]
#)
for($i=0;@main;$i+=1){
    @line = split(/\s+/,shift(@main));
    push(@arr1,[]);
    push(@arr2,[]);
    while(@line){
        push(@arr1[$i],shift(@line));
        push(@arr2[$i],shift(@line));
    }
}

error:

Experimental push on scalar is now forbidden at index.pl line 29, near "))"
Experimental push on scalar is now forbidden at index.pl line 30, near "))"

It seems that @arr[$i] returns a reference to an array. How do I get this array and add items to it?

3 Answers

The first argument of push must be an array, not an array slice. I think you wanted @{ $arr1[$i] } (but that's not your only problem). Make sure that you're using use strict; use warnings;! –ikegami

for(my $i=0;@main;$i+=1){
    my @line = split(/\s+/,shift(@main));
    push(@arr1,[]);
    push(@arr2,[]);
    while(@line){
#             ↓↓           ↓ 
        push( @{ $arr1[$i] } ,shift(@line));
        push( @{ $arr2[$i] } ,shift(@line));
    }
}

Not exactly sure how it works, probably turns the reference into an actual array of something along those lines.

Another solution I came up with myself, which adds the finished array after instead of adding values slowly:

for($i=0;@main;$i+=1){
    @line = split(/\s+/,shift(@main));
    @subArr1 = ();
    @subArr2 = ();
    while(@line){
        push(@subArr1,shift(@line));
        push(@subArr2,shift(@line));
    }
    push(@arr1,[@subArr1]);
    push(@arr2,[@subArr2]);
}

As of perl 5.36, you can iterate over multiple elements of a list at once in a foreach loop, which lets you clean this up a bit:

#!/usr/bin/env perl
use 5.036;
# The next two lines aren't strictly needed because of the above, but
# I like to be explicit
use strict;
use warnings;
use experimental qw/for_list/;
use Data::Dumper;

my @main = (
    "a 1 b 2 c 3 ",
    "d 4",
    "e 5 f 6 g 7 h 8",
    "i 9 j 10",
    );
my (@arr1, @arr2);

for my $line (@main) {
    my (@sub1, @sub2);
    for my ($name, $num) (split " ", $line) {
        push @sub1, $name;
        push @sub2, $num;
    }
    push @arr1, \@sub1;
    push @arr2, \@sub2;
}

print Dumper(\@arr1, \@arr2);

You can use the same approach in older versions using natatime from the List::MoreUtils module (Available from CPAN or your OS's package manager):

#!/usr/bin/env perl
use strict;
use warnings;
use List::MoreUtils qw/natatime/;
use Data::Dumper;

my @main = (
    "a 1 b 2 c 3 ",
    "d 4",
    "e 5 f 6 g 7 h 8",
    "i 9 j 10",
    );
my (@arr1, @arr2);

for my $line (@main) {
    my (@sub1, @sub2);
    my $it = natatime 2, split(" ", $line);
    while (my ($name, $num) = $it->()) {
        push @sub1, $name;
        push @sub2, $num;
    }
    push @arr1, \@sub1;
    push @arr2, \@sub2;
}

print Dumper(\@arr1, \@arr2);

The thing these have in common is building up each subarray first, and then pushing references to them onto @arr1 and @arr2 at the end, removing the need for a lot of the arrayref dereferencing syntax. They also iterate directly over the elements you're storing in the subarrays instead of modifying the source directly with shift.

Please inspect the following demonstration code for a compliance with your problem.

The code is based upon comments in OP's code with description what result arrays should look alike.

use strict;
use warnings;
use feature 'say';

use Data::Dumper;

my(@main,@arr1,@arr2);

@main = (
    "a 1 b 2 c 3 ",
    "d 4",
    "e 5 f 6 g 7 h 8",
    "i 9 j 10",
);

for ( @main ) {
    push(@arr1,[ /(\d+)/g   ]);
    push(@arr2,[ /([a-z])/g ]);
}

say Dumper(\@arr1,\@arr2);

Output

$VAR1 = [
          [
            '1',
            '2',
            '3'
          ],
          [
            '4'
          ],
          [
            '5',
            '6',
            '7',
            '8'
          ],
          [
            '9',
            '10'
          ]
        ];

$VAR1 = [
          [
            'a',
            'b',
            'c'
          ],
          [
            'd'
          ],
          [
            'e',
            'f',
            'g',
            'h'
          ],
          [
            'i',
            'j'
          ]
        ];
Related