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

What I'm trying to do is to turn a single list of files in my directory into an array of arrays. I am having trouble with this "for" loop:
$num_cols = 5; @files = <*>; for (@files) { undef @bb; for ($i = 0; $i < $num_cols; $i++) { push @bb, shift @files; } push @data, [ @bb ]; }
Now if $#files is relatively small, or $num_cols is relatively large, then all the elements in @files get put into @data somewhere, and @files ends up empty. However, as $#files get higher, and/or $num_cols decreases, then when the loop finished, there are still some elements left in @files, and @data doesn't have all the files I globbed. Any clues?

Replies are listed 'Best First'.
Re: Why doesn't this for loop work like I expect?
by chromatic (Archbishop) on May 18, 2000 at 00:35 UTC
    Looping over elements in an array and then changing the number of elements in that array can have unexpected results. Here's a quick fix:
    while (@files) { my @bb; for (1 .. $num_cols) { my $file = shift @files || last; push @bb, $file; } push @data, \@bb; }
    I chose while for this so that the loop will only continue while there are still elements in @files. I use the shift-or construct just in case the number of elements in @files is not a multiple of $num_cols. I push a reference onto @data because it's conceptually clearer to me (and it doesn't expand @bb into a list and then make that into an anonymous list).

    splice is your best solution, though.

Re: Why doesn't this for loop work like I expect?
by btrott (Parson) on May 18, 2000 at 00:35 UTC
    Change it to a while loop:
    while (@files) { ...
    That should fix your problem, cause it'll only loop as long as there are elements in @files. Whereas yours was actually looping *over* @files, which is a different thing, and not recommended if you're going to be modifying @files (which you did, by using shift).

    Alternatively, though, you may want to try using splice:

    push @data, [ splice @files, 0, $num_cols ] while @files;
    splice grabs chunks of an array (and can also replace those chunks w/ a list) and increases/decreases the size of the array as necessary. Sounds good for your particular use.
      Bah. btrott beat me to the splice. Here's a silly way to do it: @data = map [splice @files, 0, $num_cols], 0..@files/$num_cols;
      I am enlightened! I like this method over the while loop too, because with the while loop, if the $#files wasn't an integer multiple of $num_cols, then I'd get null links on the last row of my table. It displays correctly, but it's still not "correct". Using the splice operator fixed that too.