References (edited – lorraine)

Beginning Perl, Second Edition

Written by James Lee

Note – fix empty spaces

Published by Apress

Chapter 11



When we discussed lists and arrays in Chapter 4, we learned that all lists and all arrays are one-dimensional collections of scalars. Similarly, when we discussed hashes in Chapter 5, we learned that hash values are scalars. Therefore, up to this point in our discussion of Perl, we have not been able to create arrays of arrays (also known as multidimensional arrays) or other more complex data types.

However, being able to create more complex data types is something we will want to do from time to time. For instance, we might want to represent a chessboard as eight arrays of eight squares so that we can address each square by row and column (an array of arrays). We might also want to store a bunch of information about someone—their address, phone number, and occupation—and key it to their name (a hash of hashes).

In this chapter, we will look at another scalar data that will allow us to create these and other more complex data types—references.

What Is a Reference?

Put at its very simplest, a reference is a piece of data that tells us the location of another piece of data—if we told you to “See the first paragraph on page 130,” we’d effectively be giving you a reference to the text in that paragraph. It wouldn’t be the text itself, but it would tell you where to find it. This would also let us talk about (refer to) the text right away, despite the fact that it’s somewhere else in the book. That’s why references are so useful—we can specify data once, and they let us access it from wherever else we are.

In Perl, a reference is always a scalar, although the data it refers to may not be: our cross-reference in the previous paragraph wasn’t even a sentence, but referred to an entire para graph. Likewise, a reference, even though it’s only a scalar, can talk about the data stored in an array or hash.

Languages like C and C++ have a feature that’s similar to references, called pointers. They are similar to Perl references in that both point us to locations in the computer’s memory—however, C’s pointers tend to leave interpretation of what’s there for the programmer to disentangle. Perl’s references, on the other hand, only store memory locations for specific, clearly defined data structures—maybe not predefined, but defined nevertheless. They allow you to leave the arrangement of computer memory to the computer itself—this can be a huge relief for us mere mortal programmers, as the machine is far better at that sort of thing than we are.

The main use we have for references is the one we previously discussed—being able to treat arrays and hashes as single things. We can now refer unambiguously to the contents of an array or a hash using a single scalar—so we’re now in a position to do things like putting hashes inside hashes, arrays inside arrays, even hashes in arrays and vice-versa. But that’s not all . . .

Anonymity

We can also use references to create anonymous data. Anonymous data, as you might have guessed, is data that doesn’t have a variable name attached to it. Instead, it’s placed at a certain memory location, and we’re given a simple reference to that location—our array (or hash or whatever) has no name to speak of, but we know exactly where to find it, should we need to use it.

For example, instead of creating an array (1,2,3) called @array and then creating a reference to @array , we can cut out the middleman by referencing (1,2,3) directly.

This allows us to create real scalars, arrays, and hashes containing data that we can refer to and modify just as if it was a normal variable. This doesn’t mean that we leave arrays and hashes floating about randomly in our program to be plucked out of the air whenever we need them—we know where to find this anonymous data (we have a reference that’s telling us just this), and it only exists for as long as part of our program is using it.

The Life Cycle of a Reference

To understand how we deal with references, let’s look at the three areas of a reference’s life cycle—creation, use, and destruction. After that, we’ll see how we can practically use references to create more complicated data structures than simple arrays and hashes.

Reference Creation

There are two ways to create a reference, one for each of the following situations:

  1. You already have the data in an existing variable.
  2. You want to use anonymous data to go straight to a reference.

The simple rule for the first situation where the variable is already defined is as follows:

You create a reference by putting a backslash in front of the variable.

That’s it. Let’s see some examples:

my @array   = (2, 4, 6, 8, 10) ;
my $array_r = @array;

We create a perfectly normal array variable, and then take a reference to it by putting a backslash before the variable’s name—that’s literally all there is to it. In the same way, we can create a reference to a hash:

my %hash   = ( apple => "pomme", pear => "poire" );
my $hash_r = %hash;

or a scalar:

my $scalar   = 42;
my $scalar_r = $scalar;

We can treat our references just like ordinary scalars—so we can put them in an array:

my $a = 3;
my $b = 4;
my $c = 5;
my @refs = ($a, $b, $c);

We can also put references in a hash, but only as values—Perl keys are simple strings. You can certainly do this, though:

my @english = qw(January February March April May June);
my @french  = qw(Janvier Fevrier Mars Avril Mai Juin);
my %months  = ( english => @english, french => @french );

So what does this give us? We have a hash with two keys, english and french . The english key contains a reference to an array of English month names, while the french key contains a reference to an array of French month names. With these references, we can access and modify the original data, which means that, in effect, we’ve stored two arrays inside a single hash.

We can use the same trick to store arrays inside arrays:

my @array1 = (10, 20, 30, 40, 50);
my @array2 = ( 1, 2, @array1, 3, 4);

Now @array2 is made up of five scalars, and the middle one is a reference to another array. We can do this over and over again, if we want to:

my @array3 = (2, 4, @array2, 6, 8);
my @array4 = (100, 200, @array3, 300, 400);

This gives us a very versatile way to store complex data structures; what we’ve just done is to store a structure that looks like this:

Anonymous Arrays and Anonymous Hashes

Our next step is to do all this without having to go through the interim stages of creating the variables. Anonymous variables will let us go straight from our raw data to a reference, and the rules here are just as simple:

  1. To get a reference to an anonymous array, use square brackets ( [] ) instead of paren theses. 
     
  2. To get a reference to an anonymous hash, use curly braces ( {} ) instead of parentheses.

So, referring to our previous examples, instead of doing this:

my @array   = (1, 2, 3, 4, 5);
my $array_r = @array;

we can create a reference to an anonymous array like this:

my $array_r = [1, 2, 3, 4, 5];

Likewise, to get a hash reference, instead of doing this:

my %hash   = ( apple => "pomme", pear => "poire" );
my $hash_r = %hash;

we say

my $hash_r = { apple => "pomme", pear => "poire" };

Now we have two variables—the array with no name referred to only by $array_r and the hash with no name referred to by $hash_r.1

We can put anonymous variables inside hashes and arrays, just like references created from variables:

my %months  = (
  
english => ["January", "February", "March", "April", ",May", ",June"],
   french  => ["Janvier", "Fevrier", "Mars", "Avril", "Mai", "Juin"]
);

And we can put anonymous variables inside anonymous variables:

my @array = ( 100,200,[ 2,4,[ 1,2,[ 10,20,30,40,50 ],3,4 ],6,8 ],300,400 );

That’s exactly the same structure as shown in the preceding picture. Here it is again, with a lot more spacing added:

my @array = ( 100, 200,
               [ 2, 4,
                  [ 1, 2,
                     [ 10, 20, 30, 40, 50 ],
                  3, 4 ],
               6, 8 ],
            300, 400 );

Using References

Once we’ve created our references (whether to real variables or anonymous data), we’re going to want to use them—so how do we access the data? The operation we use to get data back from a reference is called dereferencing, and once again, the rule is very simple.

To dereference data, put the reference in curly braces wherever you would normally use a variable’s name.

First, we’ll see how to do this with arrays—say we’ve got an array and a reference:

my @array   = (1, 2, 3, 4, 5);
my $array_r = @array;

We can get at the array like this:

my @array2  = @{$array_r};

We put the reference, $array_r , inside curly braces, and use that instead of our original array variable name array . We can use this dereferenced array, @{$array_r} , anywhere we might otherwise use an array.

Let’s start with a simple example. We’ll just create a reference to an array, and then use it as we’d normally use an array.

#!/usr/bin/perl – w
# deref1.pl

use strict;

my @array   = (2, 4, 6, 8, 10);
my $array_r = @array;

print "This is our dereferenced array: @{$array_r}n";
foreach (@{$array_r}) {
  
print "An element: $_n";
}
print "The highest index is $#{$array_r}n"; print "This is what our reference looks like: $array_rn";

Let’s run this:

$ perl deref1.pl
This is our dereferenced array: 2 4 6 8 10 An element: 2
An element: 4
An element: 6
An element: 8
An element: 10
The highest index is 4
This is what our reference looks like: ARRAY(0x806eb8)
$

Looking at this program in more detail, we first define an array variable and its contents, and then backslash it to create a reference to it.

my @array   = (2, 4, 6, 8, 10);
my $array_r = @array;

Now we can use @{$array_r} instead of @array —both refer to exactly the same data, and both do exactly the same things. For instance, @{$array_r} will interpolate inside double quotes:

print "This is our dereferenced array: @{$array_r}n";

Just as if we’d used the original @array , our dereferenced array prints out the contents of the array, separated by spaces:

This is our dereferenced array: 2 4 6 8 10

In the same way, we can use the array in a foreach loop, with no surprises:

foreach (@{$array_r}) {
   print "An element: $_n";
}

Finally, we can also get the highest element number in the array, just as if we’d said $#array , like this:

print "The highest index is $#{$array_r}n";

Now, we take a look at what our reference actually looks like itself. After all, it’s a scalar, so it must have a value that we can print out and look at. It does, and this is what we get if we print out the reference:

This is what our reference looks like: ARRAY(0x806eb8)

The ARRAY part obviously tells us that we have an array reference, but what about the part in parentheses? We know that a reference is a memory location, telling us where the data is stored in Perl’s virtual memory. We generally don’t need to worry about this actual value, as we can’t do that much with it. Note also that it’s unlikely that you’ll get exactly the same value as this example. It will simply depend on what hardware your system has, what other software you’re running, and what Perl is doing.


Tip  There is one way you might want to make use of this value directly: to see if two references refer to the same piece of data, you can compare them as numbers using == .


If we try and manipulate it, it ceases to be a reference and becomes an ordinary number—the value of the hexadecimal shown earlier. We can see that if we run the following program:

#!/usr/bin/perl -w
# noref.pl

use strict;

my $ref = [1, 2, 3];
print "Before: $refn";
print "@{$ref}n";
$ref++;
print "After: $refn";
print "@{$ref}n";

it will give us something like this:

$ perl noref.pl
Before: ARRAY(0x800368)
1 2 3
After: 8389481
Can’t use string ("8389481") as an ARRAY ref while "strict refs" in use at noref.pl
 line 11.
$

When we tried to modify our reference, it degenerated to the ordinary number 8389480 , which is the equivalent of the hexadecimal number 0x800368 shown previously. Adding 1 to that number gave us the 8389481 , which is an ordinary string, rather than a reference—Perl then complains if we try and use it as a reference.

This is why we can’t use references as hash keys—hash keys can only be strings, so our references will get stringified to something like the preceding form. Once that happens, we’re not able to use them as references again.

Array Elements

What about the individual elements in an array—how do we access these? The rule is pretty much the same as for the array as a whole—just use the reference in curly braces in the same way you would the array name:

#!/usr/bin/perl -w
# deref2.pl

use strict;

my @band = qw(Crosby Stills Nash Young);
my $ref  = @band;
foreach (0..$#band) {
  
print "Array    : ", $band[$_] , "n";
   print "Reference: ", ${$ref}[$_], "n";
}

As you can see, $band[$_] and ${$ref}[$_] refer to the same thing:

$ perl deref2.pl
Array    : Crosby
Reference: Crosby
Array    : Stills
Reference: Stills
Array    : Nash
Reference: Nash
Array    : Young
Reference: Young
$

The important thing to note here is that these are not two different arrays—they are two ways of referring to the same piece of data—this is very important to remember when we start modifying references.

As we saw earlier, the last index of @band is $#band . We could have used the reference to obtain that value: $#{$ref} .

Reference Modification

If we want to modify the data referred to by a reference, the same rule applies as before—replace the name of the array with the reference in curly braces. However, when we do this, the data in the original array will change too.

#!/usr/bin/perl -w
# modify1.pl

use strict;

my @band = qw(Crosby Stills Nash Young);
my $ref  = @band;
print "Band members before: @bandn";
pop @{$ref};
print "Band members after: @bandn";

$ perl modify1.pl
Band members before: Crosby Stills Nash Young
Band members after: Crosby Stills Nash
$

Now CSN&Y has changed forever.2

We can still use push() , pop() , shift() , unshift() (and so on) to manipulate the array using its reference. However, in doing so, we’ll also be changing what’s stored in @band .

It’s quite possible to have multiple references to the same data—just as before, if you use one to change the data, you change it for the others too. This will give the same results as before:

my @band  = qw(Crosby Stills Nash Young);
my $ref1 = @band;
my $ref2 = @band;
print "Band members before: @bandn";
pop @{$ref1};
print "Band members after: @{$ref2}n";

and the same goes for anonymous arrays:

my $ref1 = [qw(Crosby Stills Nash Young)];
my $ref2 = $ref1;
print "Band members before: @{$ref2}n";

pop @{$ref1};
print "Band members after: @{$ref2}n";

Notice here that we’re using [qw(…)] , which is the same as saying

[(‘Crosby’, ‘Stills’, ‘Nash’, ‘Young’)]

and the parentheses inside the square brackets get removed, just like when we said ((1,2,3)) back in Chapter 4.

You can also modify individual elements, using the syntax ${$reference}[$element] :

#!/usr/bin/perl -w
# modelem.pl

use strict;

my @array = (68, 101, 114, 111, 117);
my $ref = @array;
${$ref}[0] = 100;
print "Array is now : @arrayn";

$ perl modelem.pl
Array is now 100 101 114 111 117
$

and again, you can do the same with anonymous data:

my $ref = [68, 101, 114, 111, 117];
${$ref}[0] = 100;
print "Array is now : @{$ref}n";

Hash References

For references to hashes, the rule is exactly the same—replace the hash’s name with the refer ence in curly braces. So, to access the hash pointed to by a reference, you use %{$hash_r} . If you want to get at a hash entry $hash{green} , you say ${$hash_r}{green} .

#!/usr/bin/perl – w
# hashref.pl

use strict;

my %hash = (
   1 => "January",    2 => "February", 3 => "March",     4 =>  "April",
   5 => "May",        6 => "June",     7 => "July",      8 =>  "August",
   9 => "September", 10 => "October", 11 => "November", 12 => "December"
);

my $href = %hash;
foreach (keys %{$href}) {
   print "Key: ", $_, "t";
   print "Hash: ", $hash{$_}, "t";
   print "Ref: ", ${$href}{$_}, "n";
}

As expected, we get the same data when using the hash as when using the reference:

$ perl hashref.pl
Key:  6  Hash: June      Ref: June
Key:  11 Hash: November  Ref: November
Key:  3  Hash: March     Ref: March
Key:  7  Hash: July      Ref: July
Key:  9  Hash: September Ref: September
Key:  12 Hash: December  Ref: December
Key:  2  Hash: February  Ref: February
Key:  8  Hash: August    Ref: August
Key:  1  Hash: January   Ref: January
Key:  4  Hash: April     Ref: April
Key:  10 Hash: October   Ref: October
Key:  5  Hash: May       Ref: May
$

This should also help to remind you that Perl’s hashes aren’t ordered as you might expect!

Notation Shorthand Using ->

You can run into problems when you have one reference stored inside another. If we have the following array reference:

$ref = [ 1, 2, [ 10, 20 ] ];

we can get at the internal array reference by saying ${$ref[2]} . But say we want to get at the first element (0-based) of that array—the one containing the value 20? We could store the reference inside another scalar and then dereference it, like this:

$inside  = ${$ref}[2];
$element = ${$inside}[1];

Or we could get the element directly, by repeatedly substituting references for array names:

$element = ${${ref}[2]}[1];

This gets very ugly very quickly, especially if you’re dealing with hash references where it becomes hard to tell if the curly braces surround a reference or a hash key.

So, to help us clear it up again, we introduce this rule:

  •  Instead of ${$ref} , we can say $ref-> .

Let’s demonstrate this by taking one of our previous examples, modelem.pl , and incorpo rating this into the code. Here’s the relevant piece of the original:

my @array = (68, 101, 114, 111, 117) ;
my $ref = @array;
${$ref}[0] = 100;
print "Array is now : @arrayn";

and here it is rewritten:

my @array = (68, 101, 114, 111, 117);
my $ref = @array;
$ref->[0] = 100;
print "Array is now : @arrayn";

Likewise for hashes: we can use this arrow notation to make things a bit clearer for ourselves. Recall hashref.pl from a little while ago:

foreach (keys %{$href}) {
   print "Key: ", $_, "t";
   print "Hash: ", $hash{$_}, "t";
   print "Ref: ", ${$href}{$_}, "n";
}

Instead of that, we can write the following:

foreach (keys %{$href}) {
   print "Key: ", $_, "t";
   print "Hash: ", $hash{$_}, "t";
   print "Ref: ", $href->{$_}, "n";
}

Now we can get at our array-in-an-array like this:

$ref = [ 1, 2, [ 10, 20 ] ];
$element = ${$ref->[2]}[1];

or more simply:

$element = $ref->[2]->[1];

However, we’ve got one more subrule that can simplify this even further:

  • Between sets of square brackets, the arrow is optional.

We can therefore rewrite the preceding as

$element = $ref->[2][1];

Reference Counting and Destruction

We’ve now seen all the ways you can create and use references. It is time to discuss how references are destroyed. Every piece of data in Perl has something called a reference count attached to it. This keeps track of the number of references that refer to that exact chunk of data.

When we create a reference to some data, the data’s reference count goes up by 1. When we stop referring to it—we reassign the reference variable, or “break” it (as we saw previously, when we tried to modify its value)—the reference count goes down. When nobody’s using the data and the reference count gets down to 0, the data is removed. Consider the following example:

#!/usr/bin/perl -w
# refcount.pl

use strict;

my $ref;
{
  
my @array = (1, 2, 3);
  
$ref = @array;
   my $ref2 = @array;
  
$ref2 = "Hello!";
}
undef $ref;

Now, let’s look at the references to the array (1, 2, 3) as we go through the program. To start with:

my $ref;
{
  
my @array = (1, 2, 3);

the array is created, and the data (1, 2, 3) has one reference—it’s in use by the array @array . Next we create another reference to it:

  $ref = @array;

and the reference count increases to 2. Once again we create a reference:

  my $ref2 = @array;

and the count goes up to 3. Next, we change that reference to be an ordinary string:

  $ref2 = "Hello!";

$ref2 is not pointing at our array any more, so the reference count on (1, 2, 3) goes back down to 2. Note that changing $ref2 doesn’t affect the original array—that only happens when we dereference. Now a block ends, and all the lexical variables—the my() variables—inside that block go out of scope:

}

That means that $ref2 and @array are destroyed. The reference count of the data (1, 2, 3) goes down again because @array is not referring to it. However, $ref still has a reference to it, so the reference count is still 1, and the data itself is not removed from memory. $ref still refers to (1, 2, 3) and can access and change this data as before. That is, of course, until we get rid of it:

undef $ref;

Now the final reference to the data (1, 2, 3) is removed and the memory for the array is finally freed.

Counting Anonymous References

Anonymous data works in the same way—however, it doesn’t get its initial reference count from being attached to a variable, but rather from when its first explicit reference is created:

my $ref = [1, 2, 3];

This data therefore has a reference count of 1, rather than

my @array = (1, 2, 3) ;
my $ref = @array;

which has a count of 2.

Using References for Complex Data Structures

Now that we’ve looked at what references are, you might be asking, “Why on earth would we want to use them?” As mentioned in the introduction, we often want to create data structures that are more complex than simple arrays or hashes. We may need to store arrays inside arrays, or hashes inside hashes, and references help us do this.

So let’s now take a look at a few of the complex data structures we can create with references—it won’t be exhaustive by any means, but it should serve to give you ideas as to how complex data structures look and work in Perl, and it should help you to understand the most common data structures.

Matrices

What is a matrix? No, not the thing that Neo wants out of. A matrix is simply an array of arrays—you can refer to any single element with a combination of two subscripts, which you might want to think of as a row number and a column number; it’s harking back to the chessboard example we mentioned in the introduction.

If you use the arrow syntax, matrices are very easy to use—you get at an element by saying

$array[$row]->[$column]

which can be written as

$array[$row][$column]

$array[$row] is an array reference, and we’re dereferencing the $column ’th element in it. With a chessboard example, it would look like this:

So, $array[0][0] would be the bottom left-hand corner of our chessboard, and $array[7][7] would be the top right.

Autovivification

There’s one last thing we need to know about references before we go on—if we assign values to a reference, Perl will automatically create all the appropriate references necessary to make it work. So, if we say this:

my $ref;
$ref->{UK}->{England}->{Oxford}->[1999]->{Population} = 500000;

Perl will automatically know that we need $ref to be a hash reference. So, it’ll make us a nice new anonymous hash:

$ref = {};

Then we need $ref->{UK} to be a hash reference because we’re looking for the hash key England —that hash entry needs to be an array reference, and so on. Perl effectively does this:

$ref = {};
$ref->{UK} = {};
$ref->{UK}->{England} = {};
$ref->{UK}->{England}->{Oxford} = [];
$ref->{UK}->{England}->{Oxford}->[1999] = {};
$ref->{UK}->{England}->{Oxford}->[1999]->{Population} = 500000;

What this means is that we don’t have to worry about creating all the entries ourselves. So we can just go ahead and write

my @chessboard;
$chessboard[0]->[0] = "WR";

This is called autovivification—things springing into existence. We can use it to greatly simplify the way we use references.

Now that we can represent our chessboard, let’s set up a chess game. This will consist of two stages: setting up the board, and making moves. The computer will have no idea of the rules, but will simply function as a board, allowing us to move pieces around. Here’s our program:

#!/usr/bin/perl -w
# chess.pl

use strict;

my @chessboard;
my @back = qw(R N B Q K N B R);
foreach (0..7) {
  
$chessboard[0]->[$_] = "W" . $back[$_]; # White Back Row
   $chessboard[1]->[$_] = "WP";            # White Pawns
   $chessboard[6]->[$_] = "BP";            # Black Pawns
   $chessboard[7]->[$_] = "B" . $back[$_]; # Black Back Row
}

while (1) {
   # Print board
   foreach my $i (reverse (0..7)) { # Row
       foreach my $j (0..7) {       # Column
          if (defined $chessboard[$i]->[$j]) {
            print $chessboard[$i]->[$j];
          } elsif ( ($i % 2) == ($j % 2) )
{
             print "..";
          } else {
             print " ";
          }
        
print " "; # End of cell
      }
      print "n";    # End of row
   }

   print "nStarting square [x,y]: ";
   my $move = <>;
   last unless ($move =~ /^s*([1-8]),([1-8])/);
   my $startx = $1-1; my $starty = $2-1;

   unless (defined $chessboard[$starty]->[$startx]) {
      print "There’s nothing on that square!n";

      next;
   }
   print "nEnding square [x,y]: ";
   $move = <>;
   last unless ($move =~ /([1-8]),([1-8])/);
   my $endx = $1-1; my $endy = $2-1;

   # Put starting square on ending square.
   $chessboard[$endy]->[$endx] = $chessboard[$starty]->[$startx];
   # Remove from old square 
   undef $chessboard[$starty]->[$startx];
}

Now let’s see the first part of a game in progress:

$ perl chess.pl
BR BN BB BQ BK BN BB BR
BP BP BP BP BP BP BP BP 
   ..    ..    ..    ..
..    ..    ..    ..
   ..    ..    ..    ..
..    ..    ..    ..

WP WP WP WP WP WP WP WP
WR WN WB WQ WK WN WB WR

Starting square [x,y]: 4,2

Ending square [x,y]: 4,4
BR BN BB BQ BK BN BB BR
BP BP BP BP BP BP BP BP
   ..    ..    ..    ..
..    ..    ..    .. 
   ..    WP    ..    ..
..    ..    ..    ..
WP WP WP .. WP WP WP WP
WR WN WB WQ WK WN WB WR

Starting square [x,y]: 4,7

Ending square [x,y]: 4,5
BR BN BB BQ BK BN BB BR
BP BP BP BP BP BP BP
  ..    ..     ..   ..
..   ..  BP  ..  ..
  ..     WP   ..    ..
..   ..      ..  ..
WP WP WP .. WP WP WP WP
WR WN WB WQ WK WN WB WR
 

Let’s look at this program in detail. Our first task is to set up the chessboard, with the pieces in their initial positions. Remember that we’re assigning $chessboard[$row][$column] = $thing . First, we set up an array of pieces on the “back row.” We’ll use this to make it easier to put each piece in its appropriate column.

my @back = qw(R N B Q K N B R);

Now we’ll go over each column.

foreach (0..7) {

In row 0, the back row for white, we want to place the appropriate piece from the array in each square.

  $chessboard[0]->[$_] = "W" . $back[$_]; # White Back Row

In row 1 of each column, we want a white pawn, WP.

  $chessboard[1]->[$_] = "WP";            # White Pawns

Now we do the same again for black’s pieces on rows 6 and 7.

  $chessboard[6]->[$_] = "BP";            # Black Pawns 
  $chessboard[7]->[$_] = "B" . $back[$_]; # Black Back Row
}

What about the rest of the squares on board? Well, they don’t exist right now, but will spring into existence when we try and read from them.

Next we go into our main loop, printing out the board and moving the pieces. To print the board, we obviously want to look at each piece—so we loop through each row and each column:

  foreach my $i (reverse (0..7)) { # Row
      foreach my $j (0..7) {       # Column

If the element is defined, it’s because we’ve put a piece there, so we print it out.

  if (defined $chessboard[$i]->[$j]) {
    print $chessboard[$i]->[$j];

Note that at this point, we’re accessing all 64 squares, so any square that didn’t exist before will exist from now on. This next piece of prettiness prints out the “checkered” effect. On a checkerboard, dark squares come on odd rows in odd columns and even rows in even columns. $x % 2 tests whether $x divides equally by 2—whether it is odd or even. If the “oddness” (or “evenness”) of the row and column is the same, we print a dark square.

  } elsif ( ($i % 2) == ($j % 2) ) {
     print "..";

Otherwise, we print a blank square consisting of two spaces:

  } else {
     print " ";
  }

To separate the cells, we use a single space.

     print " ";  # End of cell
  }

And at the end of each row, we print a new line.

   print "n";    # End of row
}

Now we ask for a square to move from:

print "nStarting square [x,y]: ";
my $move = <>;

We’re looking for two digits with a comma in the middle:

last unless ($move =~ /([1-8]),([1-8])/);

Now we convert human-style coordinates (1 to 8) into computer-style coordinates (0 to 7):

my $startx = $1-1; my $starty = $2-1;

Next, check if there’s actually a chess piece there. Note that a y coordinate is a row, so it goes first—look back at the diagram if you’re not sure how this works.

  unless (defined $chessboard[$starty]->[$startx]) {
    
print "There’s nothing on that square!n";
     next;
  }

We do the same for the ending square, and then move the piece. We copy the piece to the new square:

  $chessboard[$endy]->[$endx] = $chessboard[$starty]->[$startx];

And then we delete the old square:

undef $chessboard[$starty]->[$startx];

We’ve now used a matrix, a two-dimensional array. The nice thing about Perl’s autovivifi cation is that we didn’t need to say explicitly that we were dealing with references—Perl takes care of all that behind the scenes, and we just assigned the relevant values to the right places. However, if we were to look at the contents of the @chessboard array, we’d see eight array references.

Trees

We’re now going to build on the principle of matrices, by introducing tree-like data structures, in which we use hashes as well as arrays. The classic example of one of these structures is an address book—suppose we want to keep someone’s address and phone number in a hash. We could say this:

%paddy = (
   address => "23, Blue Jay Way",
   phone   => "404-6599"
);

That’s all very well, and it makes sense—the only problem is, you have to create a separate hash for each person in your address book, and put each one in a separate variable. This isn’t easy at all at run time, and is very messy to write—so instead, you use references.

What we do is create a main “address book” hash, referenced as $addressbook , with everyone else’s hashes as values off that:

$addressbook{"Paddy Malone"} = {
   address => "23, Blue Jay Way",
   phone   => "404-6599"
};


Note  Note that if you’ve included the use strict; pragma, you’ll have to declare this hash explicitly as my %addressbook; before using it.


It’s now very easy to take new entries from the user, and add them to our address book:

print "Give me a name:"; chomp($name   = <>);
print "Address:";        chomp($address= <>);
print "Phone number:";   chomp($phone  = <>);
$addressbook{$name} = {
   address => $address,
   phone   => $phone
};

To print out a single person, we’d use this:

if (exists $addressbook{$who}) {
   print "$whon";
   print "Address:  ", $addressbook{$who}{address}, "n";
   print "Phone no: ", $addressbook{$who}{phone},   "n";
}

And to print every address:

foreach $who (keys %addressbook) {
   print "$whon";
   print "Address:  ", $addressbook{$who}{address}, "n";
   print "Phone no: ", $addressbook{$who}{phone},   "n";
}

Deleting an address is very simple:

delete $addressbook{$who};

How about adding another level to our tree? Can we have an array of “friends” for each person? No problem—we just use an anonymous array:

$addressbook{"Paddy Malone"} = {
   address => "23, Blue Jay Way",
   phone   => "404-6599",
   friends => [ "Baba O’Reilly", "Mick Flaherty" ]
};

We can get at each person’s friends by saying $addressbook{$who}{friends} , and that’ll give us an anonymous array. We can then dereference that to a real array and print it out:

foreach $who (keys %addressbook) {
   print "$whon";
   print "Address:  ", $addressbook{$who}{address}, "n";
   print "Phone no: ", $addressbook{$who}{phone},   "n";
   my @friends = @{$addressbook{$who}{friends}};
   print "Friends:n";
   foreach (@friends) {
     
print "t$_n";
   }
}

This would now give us something like the following:

Paddy Malone
Address:  23, Blue Jay Way
Phone no: 404-6599
Friends:
       
Baba O’Reill y
        Mick Flaherty

What we now have is one hash (address book), containing another hash (peoples’ details), in turn containing an array (each person’s friends).

We can quite easily traverse the tree structure—that is, move from person to person—by following links. We do this by visiting a link, and then adding all of that person’s friends onto a “to do” array. We must be very careful here not to get stuck in a loop—if one person links to another, and the other links back again, we need to avoid bouncing about between them indefinitely. One simple way to keep track of the links we’ve already processed is to use a hash. Here’s how we can do it:

$, = "t"            # Set output field separator for tabulated display
my @todo = ("Paddy Malone"); # Start point my %seen;

while (@todo) {
   my $who = shift @todo; # Get person from the end
   $seen{$who}++;         # Mark them as seen.
   my @friends = @{$addressbook{$who}{friends}};
   print "$who has friends: ", @friends, "n";
   foreach (@friends) {
     
# Visit unless they’re already visited
      push @todo, $_ unless exists $seen{$_};
   }
}

The hash %seen is used to build up a table of everyone whose name has been held in the variable $who . The foreach loop at the bottom only adds names to the @todo list if they’re not defined in that hash—that is, if they’ve not been displayed already. Given a fairly closed community, we could see something like this:

Paddy Malone has friends Baba O’Reilly Mick Flaherty
Baba O’Reilly has friends Bob McDowell Mick Flaherty Andy Donahue
Mick Flaherty has friends Paddy Malone Timothy O’Leary
Bob McDowell has friends Andy Donahue Baba O’Reilly
Andy Donahue has friends Jimmy Callahan Mick Flaherty
Timothy O’Leary has friends Bob McDowell Mick Flaherty Paddy Malone
Jimmy Callahan has friends Andy Donahue Baba O’Reilly Mick Flaherty

Summary

We’ve looked at references, a way to put one type of data structure inside another. References work because they allow us to use a scalar to refer to another piece of data. They tell us where Perl stores it, and give us a way to get at it with a scalar.

We can create a reference explicitly by putting a backslash in front of a variable’s name: %hash or @array , for example. Alternatively, we can create an anonymous reference by using {} instead of () for a hash, and [] instead of () for an array. Finally, we can create a reference by creating a need for one—if a reference needs to exist for what we’re doing, Perl will spring one into existence by autovivification.

We can use a reference by placing it in curly braces where a variable name should go. @{$array_r} can replace @array everywhere. We can then access elements of array or hash refer ences using the arrow notation: $array_ref->[$element] for an array, and $hash_ref->{$key} for a hash.

We’ve also seen a few complex data structures: matrices, which are arrays of arrays; and trees, which may contain hashes or arrays. For more information on these kinds of data structure, consult the Perl “Data Structures Cookbook” documentation ( perldoc perldsc ) or the Perl “List of Lists” documentation ( perldoc perllol ).

If you’re really interested in data structures from a computer science point of view, Mastering Algorithms in Perl by Orwant et al. (O’Reilly & Associates, 1999) has some chapters on these kinds of structure: primarily trees and tree traversal. The ultimate guide to data structures is still The Art Of Computer Programming, Volume 1, by Donald Knuth (Addison Wesley, 1997)—affectionately known as “The Bible.”

Exercises

  1. Modify the chessboard example to detect when a piece is taken. This occurs when a piece is sitting in a square that another piece moves into. The piece that was originally in the square is taken by the new piece and removed from the board.
  2. Without being concerned with checks, checkmates, and castling, check to ensure that a move is valid. If you don’t know the rules of chess, just check the following things: no player may take either king (K), and no player may take their own pieces. 
     
  3. Turn the snippets of address book code into an address book management program. Allow the user to add, search for, and delete entries. See if you can think of a way to save the hash to disk and load it again.

1. Hmmm, variables with no names–now you see why they are called anonymous variables.

2. For the worse?

 

[gp-comments width="770" linklove="off" ]

chat sex hikayeleri Ensest hikaye