http://qs321.pair.com?node_id=11125178

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

my ($C,$R)=(4,3); #Columns,Rows my @list = qw(1 2 3 4 5 6 7 8 9 10 11 12); my @AoA=(); while (@list) { push(@AoA, [ splice(@list, 0, $C) ]); } pp \@AoA; pp \@list; [[1 .. 4], [5 .. 8], [9 .. 12]] []

I'm seeking an equivalent without splice - so that @list remains unaffected. Any suggestions...?

Replies are listed 'Best First'.
Re: List into two-dimensional array
by hippo (Bishop) on Dec 14, 2020 at 19:02 UTC

    With List::MoreUtils:

    use strict; use warnings; use Test::More tests => 2; use List::MoreUtils 'part'; my ($C, $R) = (4, 3); my @list = 1..12; my $i = 0; my @AoA = part { int ($i++ / $C) } @list; is_deeply \@AoA, [[1 .. 4], [5 .. 8], [9 .. 12]]; is_deeply \@list, [1..12];

    🦛

      With more List::MoreUtils:

      use strict; use warnings; use Test::More tests => 2; use List::MoreUtils 'natatime'; my ($C, $R) = (4, 3); my @list = 1..12; my @AoA; my $iter = natatime($C, @list); while ( my @chunk = $iter->() ) { push @AoA, [@chunk]; } is_deeply \@AoA, [[1 .. 4], [5 .. 8], [9 .. 12]]; is_deeply \@list, [1..12];

      Updated: fixed two blunders in original post. Thanks LanX.

      Update: This question reminds me of another recent node: How to Split on specific occurrence of a comma (where my response contains some CPAN List module refs)

        Thanks to CPAN, most things devolve into "already-thoroughly-solved problems." MoreUtils is priceless.
Re: List into two-dimensional array
by choroba (Cardinal) on Dec 14, 2020 at 19:14 UTC
    In the spirit of TIMTOWTDI:
    my @AoA; for (0 .. $#list) { push @AoA, [] if $_ % $C == 0; push @{ $AoA[-1] }, $list[$_]; }

    or, without using indices:

    my @AoA = []; for (@list) { push @AoA, [] if $C == @{ $AoA[-1] }; push @{ $AoA[-1] }, $_; }
    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
Re: List into two-dimensional array
by Cristoforo (Curate) on Dec 14, 2020 at 20:11 UTC
    You could use natatime from List::MoreUtils
    #!/usr/bin/perl use strict; use warnings; use List::MoreUtils qw/ natatime /; my @list = qw(1 2 3 4 5 6 7 8 9 10 11 12); my @AoA; my $it = natatime 4, @list; while (my @vals = $it->()) { push @AoA, \@vals; } print "@list\n"; use Data::Dumper;print Dumper \@AoA;
    This would leave the original array unchanged.

    Output:

    1 2 3 4 5 6 7 8 9 10 11 12 $VAR1 = [ [ '1', '2', '3', '4' ], [ '5', '6', '7', '8' ], [ '9', '10', '11', '12' ] ];
Re: List into two-dimensional array
by haukex (Archbishop) on Dec 14, 2020 at 18:25 UTC
    use warnings; use strict; use Test::More tests => 2; my ($cols,$rows) = (4,3); my @list = (1..12); my @AoA = map { [ @list[ $_*$cols .. ($_+1)*$cols-1 ] ] } 0..$rows-1; is_deeply \@AoA, [[1 .. 4], [5 .. 8], [9 .. 12]] or diag explain \@AoA; is_deeply \@list, [1..12];

    TIMTOWTDI, plenty of other solutions are of course possible. Update: This one assumes that your list fits exactly into the rows and cols that you appear to know beforehand, at least based on your example code.

Re: List into two-dimensional array
by tybalt89 (Monsignor) on Dec 14, 2020 at 22:23 UTC

    Just for fun...

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11125178 use warnings; my @list = 1 .. 12; my @AoA = []; my $C = 4; push @{ @{ $AoA[-1] } < $C ? $AoA[-1] : \@AoA }, @{ $AoA[-1] } < $C ? $_ : [ $_ ] for @list; use Data::Dump 'dd'; dd \@list; dd \@AoA;

    Outputs:

    [1 .. 12] [[1 .. 4], [5 .. 8], [9 .. 12]]
Re: List into two-dimensional array
by ikegami (Patriarch) on Dec 14, 2020 at 18:43 UTC

    You could keep using splice and achieve your goal by using a sub.

    sub group { my $n = shift; my @rv; push @rv, [ splice(@_, 0, $n) ] while @_; return @rv; } my @rv = group($C, @list);

    Update: Woops! Fixed issue brought up by haukex in reply.

      sub group { my $n = shift; my @rv; push @rv, splice(@_, 0, $n) while @_; return @rv; }

      That just returns a copy of the original list. You probably meant push @rv, [ splice(@_, 0, $n) ] while @_;.

Re: List into two-dimensional array
by LanX (Saint) on Dec 14, 2020 at 21:15 UTC
    > I'm seeking an equivalent without splice - so that @list remains unaffected. Any suggestions...?

    If @list doesn't have millions of entries, I'd just copy to a new @tmp array before prior to using your solution.

    But in the sense of TIMTOWTDI and with the power of autovivification ... :)

    (demo in debugger)

    DB<260> @a= "a" .. "h" DB<261> @b=(); $i=0; $C=3 DB<262> push @{ $b[ $i++/$C ] } , $_ for @a DB<263> x @b 0 ARRAY(0x3d7e3b0) 0 'a' 1 'b' 2 'c' 1 ARRAY(0x3d81440) 0 'd' 1 'e' 2 'f' 2 ARRAY(0x3d8e7f8) 0 'g' 1 'h' DB<264> x @a 0 'a' 1 'b' 2 'c' 3 'd' 4 'e' 5 'f' 6 'g' 7 'h' DB<265>

    HTH :)

    NB: you haven't been clear about your row $R requirement.

    update

    it's a poor man's solution for hippo's part() solution in Re: List into two-dimensional array, but since List::MoreUtils isn't core and List::Util doesn't offer it ...

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

      tangential dreams

      Now imagine we could autobox a method ->push and something like the long deprecated $# was used for the current index in a loop

      $b[ $# / $C ]->push($_) for @a

      Isn't it swell?

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

Re: List into two-dimensional array
by johngg (Canon) on Dec 15, 2020 at 11:18 UTC

    You could use an on-the-fly subroutine. Probably not a good approach if the arrays are large.

    johngg@abouriou:~$ perl -Mstrict -Mwarnings -MData::Dumper -E ' my @list = qw{ 1 2 3 4 5 6 7 8 9 10 11 12 }; my( $nCols, $nRows ) = ( 4, 3 ); my @AoA = sub { my @tAoA; push @tAoA, [ splice @_, 0, $nCols ] while @_; return @tAoA; }->( @list ); print Data::Dumper->Dumpxs( [ \ @list, \ @AoA ], [ qw{ *list *AoA } ] +);' @list = ( '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12' ); @AoA = ( [ '1', '2', '3', '4' ], [ '5', '6', '7', '8' ], [ '9', '10', '11', '12' ] );

    I hope this is of interest.

    Cheers,

    JohnGG

Re: List into two-dimensional array
by kcott (Archbishop) on Dec 16, 2020 at 22:37 UTC

    On the basis of what you've provided, you can just use this for loop instead of your current while loop:

    for (my $i = 0; $i <= $#list; $i += $C) { push @AoA, [ @list[$i .. $i + $C - 1] ]; }

    Output (showing "@list remains unaffected"):

    [[1 .. 4], [5 .. 8], [9 .. 12]] [1 .. 12]

    I don't know why you've declared and initialised $R but then didn't use it. It's complete guesswork on my part, but I get the impression you're using a matrix with all elements being used. If that's the case, the code above works if you add a couple of elements to @list; giving this output:

    [[1 .. 4], [5 .. 8], [9 .. 12], [13, 14, undef, undef]] [1 .. 14]

    Here, all rows of @AoA have the same number of elements.

    If my guess was wrong, and that's not what you want, use this code:

    for (my $i = 0; $i <= $#list; $i += $C) { my $end = $i + $C - 1; $end = $#list if $end > $#list; push @AoA, [ @list[$i .. $end] ]; }

    To get this output:

    [[1 .. 4], [5 .. 8], [9 .. 12], [13, 14]] [1 .. 14]

    Here, all rows of @AoA do not have the same number of elements.

    With the original @list (holding 12 elements), this code produces the same output as the first code I posted:

    [[1 .. 4], [5 .. 8], [9 .. 12]] [1 .. 12]

    — Ken