Josh
Josh

Reputation: 12781

Perl - Splitting an array, and looping through its sub-arrays

Say I have an array like the following in Perl:

@names = ("one", "two", "three", "four", "five", "six");

I would like to loop through this array in sub-arrays ("chunks") of pre-defined size. More specifically, I would like to have a variable, e.g. @chunk within my loop that holds the corresponding sub-array in each iteration:

for ? {
   say @chunk;
}

For example, if I use 2 as my sub-array/chunk size, we would iterate 3 times, and @chunk would hold the following values across the :

("one", "two")
("three", "four")
("five", "six")

If I use 4 as my chunk size, it would iterate only twice, and @chunk would hold:

("one", "two", "three", "four")
("five", "six") # Only two items left, so the last chunk is of size 2

Are there any built-ins/libraries to do this easily in Perl?

Upvotes: 3

Views: 1412

Answers (2)

Ilmari Karonen
Ilmari Karonen

Reputation: 50328

One way to do this is by using the built-in splice function:

my @names = qw(one two three four five six);
while (@names) {
    my @chunk = splice @names, 0, 2;
    say "@chunk";
}

Note that this will destroy the @names array; if that's a problem, make a copy of it first.


If you like, you could even encapsulate this in a function, like this:

sub chunk_map (&$@) {
    my $code = shift;
    my $n = shift;
    my @out;
    while (@_) {
        push @out, $code->(splice @_, 0, $n);
    }
    return @out;
}

Because of the prototype, you can use this very much like the built-in function map, except that it takes an extra second parameter giving the chunk size, and passes the chunk to the callback function in @_ rather than in $_. You could use it e.g. like this:

chunk_map { say "@_" } 2, @names;

or even:

say "@$_" for chunk_map { [@_] } 2, @names;

Edit: To tell in advance how many chunks there will be, you can simply divide the length of the array by the chunk size and round it up. To tell how many chunks you've processed so far, the easiest way is probably to maintain a counter:

my @names = qw(one two three four five six);
my $n = 4;

my $chunks = int((@names + $n - 1) / $n);  # round @names / $n up
my $i = 0;
while (@names) {
    $i++;
    my @chunk = splice @names, 0, $n;
    say "chunk $i / $chunks: @chunk";
}

Or, using the chunk_map function given above:

my @names = qw(one two three four five six);

my $chunks = my @chunks = chunk_map { [@_] } 4, @names;
foreach my $i (1 .. @chunks) {
    my @chunk = @{ $chunks[$i-1] };
    say "chunk $i / $chunks: @chunk";
}

Upvotes: 4

ThisSuitIsBlackNot
ThisSuitIsBlackNot

Reputation: 24063

You can do this with natatime (read N at a time) from List::MoreUtils:

my @names = qw(one two three four five six);

# Chunks of 3
my $it = natatime 3, @names;

while (my @vals = $it->())
{
    print "@vals\n";
}

To change the "chunk" size, simply change the first parameter to natatime:

# Chunks of 4
my $it = natatime 4, @names;

Upvotes: 5

Related Questions