Camelia

Looping over a list of values one at a time, two at a time and more

In this article we are going to look at looping over lists, arrays, hashes. We are also going to look at how to go over a list of values in pairs and how to go over two lists in parallel.

Note! This site is about Perl 6, the future version of Perl.
If you are looking for a solution for the current production version of Perl 5, please check out the Perl 5 tutorial.

Looping over a list of values one at a time, two at a time and more

In Perl 6 the standard way to iterate over the elements of a list or an array is by using the for statement. A simple version of it looks like this:

use v6;

my @fellows = <Foo Bar Baz>;
for @fellows -> $name {
    say $name;
}

This will print out the three values one under the other.

Foo
Bar
Baz

A quick explanation of the syntax:

@fellows is an array with 3 elements in it.

The loop variable ($name) in the above case is automatically declared in the loop so one does not need to declare it using "my", and it is still not global. It is scoped to the block of the loop. Actually it is a parameter of that block.

Looping over keys of a hash

The same way we can loop over keys of a hash - after fetching them using the "keys" function.

use v6;

my %phone = (
    "Foo" => 123,
    "Bar" => 456,
);

for keys %phone -> $name {
    say "$name -- { %phone{$name} }";
}

The output is:

Foo -- 123
Bar -- 456

The declaration of hashes in Perl 6 is similar to that in Perl 5, but when accessing individual elements in the hash, it keeps the % prefix. Thus the value of the key "Foo" will be %phone{"Foo"}. Similarly if $name contains "Foo" we can use the %phone{$name} expression to get back the relevant value.

As mentioned earlier the string interpolation of hashes requires curly braces around the statement.

Loop over every two elements in a list

What if we have an array of pairs of values, and we would like to go over all the pairs? We could do that by assigning the array to a hash and then going over the keys just in the next example:

use v6;

my @phones = <Foo 123 Bar 456 Moo 789>;
my %temp = @phones;
for %temp.keys -> $name {
    say "$name {%temp{$name}}";
}

Not only is the use of the temporary hash disturbing, but it also loses the original order of the pairs. Sometimes the order is important.

The nice thing about the for loop in Perl 6 is, that it also allows the looping over groups of values. In our case we can go over every two elements preserving the order:

use v6;

my @phones = <Foo 123 Bar 456 Moo 789>;
for @phones -> $name, $number {
    say "$name  $number";
}

The output is:

Foo 123
Bar 456
Moo 789

Going over elements of a hash

If you'd like to go over all the pairs in a hash, you can use the for loop with two parameters and the kv method of hashes that returns the key-value pairs.

use v6;

my %phone = (
    "Foo" => 123,
    "Bar" => 456,
);

for %phone.kv -> $key, $value {
    say "$key $value";
}

That still does not indicate any specific order (similarly to "each" in Perl 5) but now both the key and the value are in simple scalars.

Looping over any number of elements

You can also iterate over any number of elements:

Let's say we just extracted the results of the Spanish Liga football games from the soccer website http://soccernet.espn.go.com/ . Those come in groups of 4 values:

home team,
score of home team
score of guest team
guest team

use v6;

my @scores = <
    Valencia          1 1  Recreativo_Huelva
    Athletic_Bilbao   2 5  Real_Madrid
    Malaga            2 2  Sevilla_FC
    Sporting_Gijon    3 2  Deportivo_La_Coruna
    Valladolid        1 0  Getafe
    Real_Betis        0 0  Osasuna
    Racing_Santander  5 0  Numancia
    Espanyol          3 3  Mallorca
    Atletico_Madrid   3 2  Villarreal
    Almeria           0 2  Barcelona
>;

for @scores -> $home, $home_score, $guest_score, $guest {
    say "$home $guest $home_score : $guest_score";
}

We can loop over the values using a for statement with 4 scalar variables.

The output will look like this:

Valencia Recreativo_Huelva 1 : 1
Athletic_Bilbao Real_Madrid 2 : 5
Malaga Sevilla_FC 2 : 2
Sporting_Gijon Deportivo_La_Coruna 3 : 2
Valladolid Getafe 1 : 0
Real_Betis Osasuna 0 : 0
Racing_Santander Numancia 5 : 0
Espanyol Mallorca 3 : 3
Atletico_Madrid Villarreal 3 : 2
Almeria Barcelona 0 : 2

Missing values

One should ask the question what happens if the list runs out of values in the middle, of a multi-value iteration? That is, what happens to the following loop?

use v6;

for (1, 2, 3, 4, 5) -> $x, $y {
    say "$x $y";
}

In this case Rakudo throws an exception when it finds out it does not have enough values for the last iteration. It will look like this, (with a bunch of trace information afterwards).

1 2
3 4
Not enough positional parameters passed; got 1 but expected 2
  in block

In order to avoid the exception we could tell the loop that the second and subsequent values are optional by adding a question mark after the variable

use v6;

for (1, 2, 3, 4, 5) -> $x, $y? {
        say "$x $y";
}

the output looks like this:

1 2
3 4
use of uninitialized value of type Mu in string context  in block  at a.pl:4

5

As you can see, Rakudo gives a warning due to the use of an undefined value, but the last iteration is still finished.

Default value

You can even set a default value. In case there are not enough values in the last iteration, the $y variable will get 0 as a value.

use v6;

for (1, 2, 3, 4, 5) -> $x, $y = 0 {
        say "$x $y";
}

Output:

1 2
3 4
5 0

Iterating over more than one array in parallel

In the last example I'd like to show a totally different case. What if you have two (or more) array you'd like to combine somehow? How can you go over the elements of two arrays in parallel?

use v6;

my @chars   = <a b c>;
my @numbers = <1 2 3>;

for @chars Z @numbers -> $letter, $number {
    say "$letter $number";
}

The Z infix operator version of the zip function allows the parallel use of two lists.

The result looks like this:

a 1
b 2
c 3

The Z operator can be used more than once in a single expression:

use v6;

my @operator  = <+ - *>;
my @left      = <1 2 3>;
my @right     = <7 8 9>;

for @left Z @operator Z @right -> $a, $o, $b {
    say "$a $o $b";
}

Will result in this output:

1 + 7
2 - 8
3 * 9


The Perl 6 Tricks and Treats newsletter has been around for a while. If you are interested to get special notification when there is new content on this site, it is the best way to keep track:
Email:
Full name:
This is a newsletter temporarily running on my personal site (szabgab.com) using Mailman, till I implement an alternative system in Perl 6.
Gabor Szabo
Written by Gabor Szabo

Published on 2012-08-02



Comments

In the comments, please wrap your code snippets within <pre> </pre> tags and use spaces for indentation.
comments powered by Disqus

Perl 6 Tricks and Treats newsletter

Register to the free newsletter now, and get updates and news.
Email:
Name: