CIT 042 Index > Loops; Lists and Array Functions

More about Loops

Earlier, we reviewed the for loop, which lets us count our way through an array. In this section, we're going to talk about an even easier way to step through an array. We'll also talk about ways to get out of a loop "early" or to skip over specific items in an array.

The foreach loop

If you want to step through an array one item at a time, the foreach loop may be just what you want. The left side of the following example says, "the variable $item will be set to point to each entry of @array in turn, and then the code in the curly braces will be performed."

foreachEquivalent for
foreach $item (@array)
{
    print $item, "\n";
}
for ($i=0; $i < scalar(@array); $i++)
{
    print $array[$i], "\n";
}

In the foreach loop, the scalar $item (or whatever you choose to call it) is not just a copy of the array entry; it's an alias for the actual entry itself. Thus, the following code will double the value of every entry in the @data array.

foreachEquivalent for
@data = (1, 2, 7, 12);
foreach $entity (@data)
{
    $entity = $entity * 2;
}
@data = (1, 2, 7, 12);
for ($i=0; $i < scalar(@data); $i++)
{
    $data[$i] = $data[i] * 2;
}

You may also give foreach a list of values. The following example will add seven to each of the variables in the list:

foreach $var ($a, $b, $c)
{
    $var += 7;
}

And this example will print the numbers in the list:

foreach $scalar (3, 6, 7..12, 14)
{
    print $scalar, "\n";
}

Printing an array

You may have noticed that if you try to do something like this:

@array = ("x", "y", 12, 3);
print @array, "\n";

You get xy123 as output; printing an array runs all of your items together. You can use foreach to print an array more neatly:

foreach $item (@array)
{
    print $item, " ";
}
print "\n";

We'll see an easier way to do this when we discuss the join function.

Early Exit from a Loop with last

Sometimes you'll want to get out of a loop before it's processed all the data. Here's an example; let's say that people are coming into an auditorium in groups, and the auditorium can hold only 200 people. You can do it this way:

$total = 0;
while ($num = <STDIN< && $total <= 200)
{
    if ($total + $num > 200)
    {
        print "Auditorium full\n";
    }
    else
    {
        $total += $num;
        print "Attendance is now $total\n";
    }
}

This looks a bit ugly, because we're testing $total twice; once in the condition of the while and again in the if statement. By using the last statement, which says, "get out of this loop," we can rewrite the code as follows:

$total = 0;
while ($num = <STDIN>)
{
    if ($total + $num > 200)
    {
        print "Auditorium full\n";
        last;
    }
    $total += $num;
    print "Attendance is now $total\n";
}

last works with any kind of loop; while, for, and foreach. Here's a loop that goes through an array, finding the square root of each element. It stops if someone has accidentally entered negative data:

foreach $item (@data)
{
    if ($item < 0)
    {
        print "Cannot find square root of $item\n";
        last;
    }
    $result = sqrt($item);
    print "The square root of $item is $result\n";
}

Skipping Part of a Loop with next

The next statement starts the next iteration of the enclosing loop. This has the effect of skipping all the statements up to the ending curly brace of the loop. The following example takes an array of deposit and withdrawal data, and counts and adds up only deposits (positive numbers). This could as easily be done with a regular if, but we needed an example of some sort.

$total_deposits = 0;
$num_deposits = 0;
foreach $amount (@transactions)
{
    if ($amount < 0)
    {
        next;
    }
    $total_deposits += $amount;
    $num_deposits++;
}

The redo statement

This statement takes you to the beginning of the enclosing loop without re-evaluating the condition. As the book Programming Perl says, "This command is normally used by programs that wish to deceive themselves about what was just input." It's not used very often, and the only example we can give here is quite eccentric. It goes through a list of numbers; if any of them is less than one, it keeps multiplying the number by two until it becomes greater than or equal to one.

@arr = (3.5, 0.4, 0.3, 14);
foreach $item (@arr)
{
    if ($item < 1)
    {
        $item *= 2;
        redo;
    }
    print $item, " ";
}
print "\n";

# output is 3.5 1.6 1.2 14

The following diagram shows the difference between last, next, and redo.

diagram showing differences between last, next, and redo

Miscellanea about next, last, and redo

As written here, these statements will affect only the nearest enclosing loop! In the following example, the last will take you to point A, not to point B.

$grand_total = 0;
for ($i=1; $i <= 3; $i++)
{
    print "Finding sum of input set $i\n";
    $sum = 0;
    for ($j = 0; $j < 5; $j++)
    {
        chomp($data = <STDIN>);
        if (lc($data) eq "quit")
        {
            last;
        }
        $sum += $data;
        $grand_total += $data;
    }
    print "The sum of this set is $sum\n"; # POINT A
}
print "The grand total is $grand_total\n"; # POINT B

When last, next, or redo is the only statement inside an if, it makes sense to use the if modifier. The following are equivalent, but the one on the right is less to write. You will see many programmers using this form.

if (lc($data) eq "quit")
{
    last;
}
last if (lc($data) eq "quit");

List and Array Functions

There are other things that we want to be able to do with arrays; we'd like to be able to add or delete items at the beginning, end, and middle of an array. While it's possible to do these things with rather complicated for loops, Perl provides functions that make it easy to accomplish these tasks.

push and pop

These two functions operate at the end of a list. push gives you an easy way to add one or more items at the end of an array. The following table shows a sequence of statements. You may put as many items as you want after the array name.

StatementResulting List
@arr = ( 2, "x", 5, "y"); (2, "x", 5, "y")
push(@arr, 7); (2, "x", 5, "y", 7)
push(@arr,"z", 12); (2, "x", 5, "y", 7, "z", 12)
push(@arr, 9, 4, 6); (2, "x", 5, "y", 7, "z", 12, 9, 4, 6)

It's also possible to tack a whole array onto the end of another array. as shown in this example:

@x = ( 1, 2, 3 );
@y = ("a", "b", "c");
push(@x, @y);

# @x now contains (1, 2, 3, "a", "b", "c") 

You will often see push used when collecting input into an array. Note our use of last to exit a loop that would otherwise go on forever.

@name_arr = (); # an empty list
while (1)   # 1 is always "true"
{
    print("Enter name, or Q to quit ");
    $name = <STDIN>;
    chomp($name);
    last if (uc($name) eq "Q");
    push @name_arr, $name;
}

The pop function is used to remove items from the end of an array. It removes one item only and returns that value. In the table below, you see that the first pop just throws away the value; the second pop saves it in a variable.

Statement@arr$value
@arr = (5, 7, 12, 14, 18); (5, 7, 12, 14, 18) undef
pop(@arr); (5, 7, 12, 14) undef
$value = pop(@arr); (5, 7, 12) 14

You can use push and pop to simulate a concept called a stack, which acts like a stack of cafeteria trays; you push trays onto the top of the stack, and pop them off the same end of the stack. Put another way, the last thing that goes onto a stack is the first thing to come off it. Stacks are very useful in many advanced computer algorithms.

unshift and shift

unshift and shift work in the same manner as push and pop, except that they add or delete items at the beginning of the array rather than at the end.

StatementResulting List
@arr = (73, 12, 52, 64); (73, 12, 52, 64)
unshift(@arr, 19); (19, 73, 12, 52, 64)
unshift(@arr,"x", "y"); ("x", "y", 19, 73, 12, 52, 64)
unshift(@arr, 9, 4, 6); (9, 4, 6, "x", "y", 19, 73, 12, 52, 64)

It's also possible to tack a whole array onto the beginning of another array. as shown in this example:

@x = ( 1, 2, 3 );
@y = ("a", "b", "c");
unshift(@x, @y);

# @x now contains ("a", "b", "c", 1, 2, 3) 

The shift function is used to remove items from the beginning of an array. It removes one item only and returns that value. In the table below, you see that the first shift just throws away the value; the second shift saves it in a variable.

Statement@arr$value
@arr = (5, 7, 12, 14, 18); (5, 7, 12, 14, 18) undef
shift(@arr); (7, 12, 14, 18) undef
$value = shift(@arr); (12, 14, 18) 7

You can use combinations of these four functions to simulate a concept known as a queue, where items enter at one end of the array and leave at the other end. This is like a line at the bank; the first person in line is the first person served, and any new people who arrive stand behind one another.

The join function

This is an incredibly useful function. Its generic form is:

$string = join("separator", @array);
$string = join("separator", list);

This function creates a string consisting of each item of the given array or list, with separator between each one. The following table shows this function in use, with its output:

$str = join(" ", 1, 3, 5);
print $str, "\n";
1 3 5
@arr = ("this", "that", "those");
$str = join("|", @arr);
print $str, "\n";
this|that|those
@arr = ("this", "that", "those");
print join(", ", @arr), "\n";
this, that, those
$month = 5; $day=29; $year=2001;
print join("/", $month, $day, $year), "\n";
5/29/2001

The split function

split is the opposite of join; it takes as its input a delimiter and a single string; it splits the string into a list of entries separated by the delimiter.

$str = "5/04/2002";
($month, $day, $year) = split("/", $str);
$month contains string 5
$day contains string 04
$year contains string 2002
$str = "ab:cd:ef";
@arr = split(":", $str);
@arr contains elements
(ab, cd, ef)

Actually, the delimiter can be a complete regular expression, but we haven't gotten to that yet.

reverse and sort

The reverse function changes the order of the elements of an array:

@arr = ( 1, 3, 5, 7, 9 );
@new = reverse(@arr);
# @new now contains (9, 7, 5, 3, 1)

The sort function returns its input array, sorted into ASCII order.

CodeResult in @new
@arr = ("horse", "fish", "cat", "dog");
@new = sort(@arr);
("cat", "dog", "fish", "horse")
@arr = ("aardvark", "Zaire", "James", "ice");
@new = sort(@arr);
("James", "Zaire", "aardvark", "ice")
# Numbers will be converted to strings!!!
@arr = (10, 30, 1, 5, 3);
@new = sort(@arr);
("1", "10", "3", "30", "5")

If you need to sort an array numerically, use this magic formula:

@new = sort {$a <=> $b} (@arr);

You must use the variable names $a and $b for this to work! Don't worry if you're already using those variables for something else. This is truly magic, and their values won't be affected by the sort.