Scott Baker
Scott Baker

Reputation: 81

What is the best way in Perl to iterate a loop and extract pairs or triples

I have a flat array of coordinates and I want to iterate and extract pairs of x and y coordinates. The same logic could apply to triples for RGB colors. This is what I have so far, but it doesn't feel super flexible or elegant.

my @coords = qw(1 5 2 6 3 8 6 12 7 5);

for (my $i = 0; $i < @coords; $i += 2) {
    my $x = $coords[$i];
    my $y = $coords[$i+1];

    print "$x, $y\n";
}

There has to be a better way to do this right?

Upvotes: 8

Views: 429

Answers (6)

Never Sleep Again
Never Sleep Again

Reputation: 1350

I like to use splice without overworking it by passing the result through as the test, and also add a guard condition:

@triples % 3 and die "TRIPLES ARRAY NOT MULTIPLE OF 3 IN LENGTH";
while (@triples){
    my ($foo, $bar, $baz) = splice @triples, 0, 3;
    ...
}

in case you need @triples to survive getting consumed by the ellided code within the loop, make copies, either of the whole array or with offsets while incrementing an index. TMTOWTDI.

Upvotes: 2

Andy A.
Andy A.

Reputation: 1452

You can use shift in a while loop.

#!/usr/bin/perl

my @coords = qw/1 5 2 6 3 8 6 12 7 5/;
my ($x, $y);

while (@coords) {
    $x = shift @coords;
    $y = shift @coords;
    # another shift to get triples

    # Do something with $x, $y, ...
    say "$x, $y";
}

The while loop runs until @coords is empty. shift gets the first element out and deletes it from the array.

Avoid undef by giving a default value:

If you try above with triples, you'll get an error because there will be undefined values in the last run. $x will be 5 and then @coords is empty.

So give a default value using //.

#!/usr/bin/perl

my @coords = qw/1 5 2 6 3 8 6 12 7 5/;
my ($x, $y, $z);

while (@coords) {
    $x = shift @coords; # is defined!
    $y = shift @coords // "<undef>";
    $z = shift @coords // "<undef>";

    # ...
}

Don't use ||! Because shift @coords || "<undef>"; will be "<undef>" if the current value is evaluated to false (0, "", ...).

Upvotes: 4

mob
mob

Reputation: 118595

splice is a better way

while (my ($x,$y) = splice @coords, 0, 2) {
    ...
}

Two things to note.

  1. splice consumes the elements of @coords. If you don't want your loop to destroy the contents of your array, use a temporary array.

       my @tmp = @coords;
       while (my ($x,$y) = splice @tmp,0,2) { ... }
    
  2. If the input might not contain an even number of elements, you may want to add an additional check to make sure each iteration has access to the right number of elements

       while (2 == (my ($x,$y) = splice @coords,0,2)) { ... }
    

Upvotes: 5

Polar Bear
Polar Bear

Reputation: 6798

An old school aproach to the problem

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

my @coords = qw(1 5 2 6 3 8 6 12 7 5);
my($x,$y);

while( ($x,$y,@coords) = @coords ) {
    say "$x, $y";
}

Output

1, 5
2, 6
3, 8
6, 12
7, 5

Upvotes: 4

zdim
zdim

Reputation: 66883

The module List::MoreUtils has natatime (n-at-a-time)

use List::MoreUtils qw(natatime);

my @ary = 1..12; 

my $it = natatime 3, @ary;  # iterator

while (my @triplet = $it->()) { say "@triplet" }

Upvotes: 7

Shawn
Shawn

Reputation: 52344

You can use pairs from the core List::Util module to turn an even-number of elements list into a list of two-element array refs:

#!/usr/bin/env perl
use warnings;
use strict;
use List::Util qw/pairs/;

my @coords = qw(1 5 2 6 3 8 6 12 7 5);

for my $pair (pairs @coords) {
    my ($x, $y) = @$pair;
    # ...
}

Upvotes: 4

Related Questions