Reputation: 12781
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
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
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