Arrays of hashes of arrays of hashrefs
Creating arrays and hashes is all very well, but sometimes you want something a bit less flat, a little more multidimensional. Well, Perl does indeed allow for multidimensional arrays, but the syntax is a little odd. Let us enter the world of the reference.
References are like signposts to other data. To create a
multidimensional array, what you actually need to do is create a normal 1
dimensional array whose elements are signposts to other arrays. In this
way we we can fake a multidimensional array. So, how do we create a
reference? There are two ways. The first is to use the \
operator, which creates references to bits of data you have
named and defined previously:
$scalar = "hello";
$scalar_ref = \$scalar;
@array = ( qw{q w e rt y} );
$array_ref = \@array;
%hash = ( key => "value", lemon => "curd" );
$hash_ref = \%hash;
Because references are just a single signpost saying "the thing I refer to is just over there", they are $ingular data, hence they are always $calars. Creating arrayrefs and hashrefs is such a common thing to do, there is a shorthand, which allows you to create them directly, and without naming them:
$array_ref = [ "q", "w", "e", "r", "t", "y" ];
$array_ref2 = [ qw{ you can use qw to save time } ];
$hash_ref = { key => "value", lemon => "curd" };
These create references to anonymous arrays and
anonymous hashes. In the first example, we created references to
named arrays and hashes, like @array and %hash.
In this example, there is no named array or named hash, only the
[ ] brackets or {
} braces, which are never named.
You can also create references to anonymous subroutines:
$code_ref = sub { return 2**$_[0] };
Which is very useful, as we shall see. Creating references is
easy then: you just need the right mix of \,
sub and { } [ ] braces and brackets. How do we
get back to the contents? The posh word for this is
dereferencing. To see how this works, let's take an example of
the multidimensional array we wanted in the first place:
@unidimensional =
(
( "this is just a one dimensional array", "even though"),
( "we have nested", "parentheses" )
);
# parentheses don't nest in perl:
# everything gets flattened to a single list
@multidimensional =
(
[ "but this one", "really is multidimensional" ],
[ "two by", "two" ]
);
# brackets mean create references to anonymous arrays
# and put them in @multidimensional
The multidimensional array is really a one-dimensional array that contains pointers (references) to the location of other arrays. So Perl doesn't really have multidimensional arrays, but you can fake them using references. Getting at the data is a little complex, however:
#!/usr/bin/perl
use strict;
use warnings;
my @multidimensional =
(
[ "but this one", "really is multidimensional" ],
[ "two by", "two" ]
);
my $element_1_1 = $multidimensional[1]->[1]; # gets 'two'
print $element_1_1;
two
The $multidimensional[1] bit is obvious: we're just using
the usual (counting from zero) syntax for pulling out
the 2nd element of the array. If we actually captured
this:
#!/usr/bin/perl
use strict;
use warnings;
my @multidimensional =
(
[ "but this one", "really is multidimensional" ],
[ "two by", "two" ]
);
$element_1 = $multidimensional[1];
print ref $element_1;
print $element_1;
the scalar $element_1 would contain the reference itself.
We can use the ref operator to find out what the scalar
points to:
print ref $element_1;
ARRAY
We can also find out what perl calls the reference:
print $element_1;
ARRAY(0x183f1dc)
or similar, which isn't very informative! The 0x... is a
hexadecimal code to the array's location in memory, basically the 'map
reference' where perl has stored the array it points to. So although:
$multidimensional[1]
returns a reference to an array ('0x183f1dc'):
->[1]
is the bit that actually 'dereferences' the reference, chasing the
pointer to the array at 0x183f1dc. You could think of the
-> as 'follow this signpost to the real data'. The
-> dereferencing arrow can also be used to pull out bits
of multidimensional hashes:
#!/usr/bin/perl
use strict;
use warnings;
my %multi_hash =
(
name =>
{
fore => "Steve",
sur => "Cook",
},
age => 26
);
my $forename = $multi_hash{name}->{fore};
my $age = $multi_hash{age};
print "name $forename, age $age\n";
name Steve, age 26
You can see here that a multidimensional structure needn't be a boring rectangular matrix, exactly 2 by 2 or 4 by 4 by 3: it can be unbalanced and weird: you can have anything you like:
#!/usr/bin/perl
use strict;
use warnings;
my @complex =
(
"an array",
{
containing => "a hashref"
},
"a scalar",
[ # element 3 of the @complex is this big arrayref
[
"a two deep arrayref",
"with two elements"
],
{ # element 1 of this arrayref is a little hashref
lemon => "curd" # key lemon of this hashref is curd
}
]
);
print my $wanted = $complex[3]->[1]->{lemon};
Hopefully the syntax should be OK now: if you wanted the words "curd", you need:
my $wanted = $complex[3]->[1]->{lemon};
i.e. take the third element of @complex (don't
forget we count from 0!), that is the big arrayref at the end,
dereference it to get the first element (the little hashref inside the
arrayref), then dereference the hashref with the key lemon.
When we talk about objects later, we'll find that they are usually anonymous hashrefs:
$anonymous_hashref = { name => "Cornelia", species => "Elaphe guttata" };
to get at the name this time, we need the syntax:
$name = $anonymous_hashref->{name};
Note that because we are starting with a scalar (not an array as in
the last example), the very first thing we need to do is
dereference it with an arrow: the scalar $anonymous_hashref
points to an (anonymous) hash, so you can't just look at its keys,
because a scalar doesn't have keys. If this doesn't make sense,
compare these:
@array = ( qw/ some elements/ ); $zeroeth_element_of_array = $array[0]; $arrayref = [ qw/some elements/ ]; $zeroeth_element_of_arrayref = $arrayref->[0];
There is a certain shortcut you can take when dealing with multidimensional arrays and hashes (and mixtures, like arrays of arrayrefs of hashrefs). Since perl knows that an array or hash can only contain a single 1 dimensional list, it knows that:
@array = ( [ "two", "by" ], [ "two", "elements" ] ); $element_0_1 = $element[0][1];
can only mean:
$element_0_1 = $array[0]->[1];
So, in general, if you're dealing with something that is a
real array or hash at the highest level, you can miss off the
-> arrows completely unambiguously, so our earlier
example:
$wanted = $complex[3]->[1]->{lemon};
could be written with less line noise as:
$wanted = $complex[3][1]{lemon};
However, you must be careful with this syntax if you're dealing with something that is a scalar at the highest level (i.e. an anonymous hashref or arrayref):
$arrayref = [ [ "two", "by" ], [ "two", "elements" ] ];
# note this is scalar (an arrayref), not an array
$element_0_1 = $arrayref->[0]->[1];
$element_0_1 = $arrayref->[0][1];
The last two are equivalent, but note you can't dispense with the
first -> , which is actually obvious, as:
$element[0][1];
means the first element of the zeroeth arrayref contained in @element, which doesn't exist (or if it does, you'll get the value from that, which isn't what you're after).
You've now seen that constructing references can be done in many ways. TIMTOWTDI. Some examples:
$arrayref = [ 0, 1, 2, 3 ];
# create a scalar that is an anonymous arrayref
@array = ( 0, 1, 2, 3 );
$arrayref = \@array;
# create a reference to a real named array
@multi = ( \@one, \@two, \@three );
# multidimensional array constructed from references to named arrays
@multi2 = ( [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] );
# multidimensional array constructed from anonymous arrayrefs
$arrayref2 = [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ];
# multidimensional: arrayref constructed from anonymous arrayrefs
Now, oftentimes, you won't want to play with just a single element of a thingy (which is the not very descriptive term for a perl data structure composed of some gungy mass of references). You'll want a slice of a thingy, or to iterate over the whole array pointed to by an arrayref. How do we do this? Well, both need the same sort of dereferencing syntax:
$arrayref = [ 0, 1, 2, 3, 4 ];
@slice = @{ $arrayref }[ 0, 2 ];
@anonymous_array_pointed_to_by_arrayref = @{ $arrayref };
The way to read @{ $arrayref } is 'the
array referred to by $arrayref'. If you want a hash, you use
a similar syntax:
$hashref =
{
lemon => "curd",
strawberry => "jam",
orange => "marmalade",
};
@hashslice = @{ $hashref }{ lemon, orange };
# don't forget slices are plur@l data
%anonymous_hash_pointed_to_by_hashref = %{ $hashref };
This (of course?) produces yet another way of accessing the individual elements of a reference:
$hashref =
{
lemon => "curd",
strawberry => "jam",
orange => "marmalade",
};
$hashelement = ${ $hashref }{ "lemon" };
# the pairs inside the hash referred to by $hashref are $calar data
$arrayref = [ 0, 1, 2, 3, 4 ];
$arrayelement = ${ $arrayref }[2];
#the elements inside the array referred to by $arrayref are $calar too
So that if we have a dereferenced arrayref:
@{ $arrayref }
we access its elements with the usual Perl syntax:
${ $arrayref }[ INDEX ]
In simple cases, the { } braces can be omitted, although
I personally never miss them off, as I get easily confused:
@array = @{ $arrayref };
@array = @$arrayref;
are equivalent nonetheless.
All the examples so far have relied on you knowing the structure of
the Thingy (gungy reference mass) at compile time. Although there will be
times when you will create huge perl thingies in your code (especially
when you get onto object oriented
programming), more often than not, you will be building them at run
time from data entered by users or from other files. In fact, a great
deal of programming and parsing is to do with
converting data from one format to another. Data stored in flat files is
called 'serialised' data. Data stored within a script will usually be
more tree-like. Conversion from serialised input (such as XML files, text files or user input) to serialised
output (such as an HTML file, or an email) via an internal parse
tree of some sort is often a job for modules,
but you'll frequently need to create a quick and dirty converter for a
simple format such as the one below. Here, we use simple array and hash
manipulation operators (like push, for, and
hash assignment) to create a Thingy on the fly. Since references are just
pointers to hash and array structures, building and manipulating them is
a simple matter of getting up close and personal to these functions.
#!/usr/bin/perl
use strict;
use warnings;
my $record;
my @people;
while ( <DATA> )
{
chomp;
if ( /\*\*\*/ ) # start of record marker
{
$record = {}; # create an empty hashref
}
elsif ( my ( $field, $data ) = / (\w+) \s* = \s* (.*) /x )
{
if ( $field eq "pets" )
{
$data = [ split /\s*,\s*/, $data ];
# create an anonymous arrayref:
# you can wrap things that return lists in []
# and create arrayrefs as simply as this. Good, isn't it?
}
$record->{ $field } = $data;
# add key/value pair to the anonymous hashref $record
}
elsif ( /---/ ) # end of record marker
{
push @people, $record; # add the hashref to the @people array
}
else
{
next;
}
}
# we have now created a tree in memory, looking something like:
# @people =
# (
# { name=>'andy', age=>37, pets=>[] },
# { name=>'alex', age=>23, pets=>[ 'dog' ] },
# { name=>'steve',age=>26, pets=>[ 'millipede', 'snake' ] },
# );
for my $person ( @people )
{
print ucfirst "$person->{name} is $person->{age} years old ";
# ucfirst capitalises the first letter of a string
if ( my @pets = @{ $person->{pets} } )
# assignment is neatest here, as we use @pets in a minutes
{
local $" = ', ';
# the $" is a special perl variable, containing
# the thing used to separate array elements in quoted strings:
# usually it's a space, but we make it a comma and space for
# our output here
print "and has the following pets: @pets.\n";
}
else
{
print "and has no pets.\n";
}
}
# everything after the __DATA__ token is available to a perl script and
# can be read automagically via the DATA filehandle, which is opened
# automatically on running your script
__DATA__
***
name = andy
age = 37
pets =
---
***
name = alex
age = 23
pets = dog
---
***
name = steve
age = 26
pets = millipede, snake
---
Andy is 37 years old and has no pets. Alex is 23 years old and has the following pets: dog. Steve is 26 years old and has the following pets: millipede, snake.
So we take one serial format (our __DATA__ section),
convert it into an internal thingy, then dump the thingy in our own
chosen format.
What other practical uses are there for references? Well, if you've
been playing with Perl like a good hacker, and tried to
return arrays from subroutines, you'll find that it doesn't
work how you expected:
#!/usr/bin/perl
use strict;
use warnings;
my ( @one, @two ) = thingy();
print "one @one\ntwo @two\n";
sub thingy
{
my @first = qw/ lemon orange lime/;
my @second = qw/ apple pear medlar/;
return @first, @second;
}
one lemon orange lime apple pear medlar two
The problem is that arrays in list
context (such that return gives its arguments) interpolate
their members. That is, return flattens
@first and @second into a single, big list. The
second problem is that arrays are greedy, so when we return this
flattened list from the subroutine, @one slurps up
all the return values, and @two gets nothing. There
is a kludge to get around this:
my ( @one, @two ); ( @one[ 0 .. 2 ], @two[ 0 .. 2 ] ) = thingy();
but this is obviously dependent on knowing how many elements are returned: this will break if the two arrays have variable lengths. What we really need to do is pass the arrays by reference:
#!/usr/bin/perl
use strict;
use warnings;
my ( $one, $two ) = thingy();
print "one @{$one}\ntwo @{$two}\n";
sub thingy
{
my @first = qw/ lemon orange lime/;
my @second = qw/ apple pear medlar/;
return \@first, \@second;
}
The subroutine returns a list of arrayrefs (effectively an array of
arrays), which the body of the program then plays with using the @{
$ref } dereferencing syntax. You can be explicit if you want, and
dereference the arrayrefs back to real arrays if you'd rather:
my ( $one, $two ) = thingy();
my @one = @{ $one };
my @two = @{ $two };
There's something rather important to note about passing things by reference instead of by value:
#!/usr/bin/perl
use strict;
use warnings;
my @spices = qw( ginger kratchai galangal );
by_value( @spices );
print "@spices\n";
sub by_value
{
my @spices = @_;
push @spices, "turmeric";
}
ginger kratchai galangal
When you pass things by value, you are passing copies of the
data, so manipulating these data in the sub does nothing interesting to
the original data (you'd have to return the altered array, and capture it
in @spices to change the original data). However, when you
pass by reference, you are passing pointers to the original
data, so if you manipulate what the references point to, you are
modifying the original data:
#!/usr/bin/perl
use strict;
use warnings;
my @spices = qw( ginger kratchai galangal );
by_reference( \@spices );
print "@spices\n";
sub by_reference
{
my $spices = shift;
push @{ $spices }, "turmeric";
}
ginger kratchai galangal turmeric
Bear this in mind! Another source of subtle bugs is messing with the
elements of @_:
#!/usr/bin/perl
use strict;
use warnings;
my @spices = qw( ginger kratchai galangal );
by_value( @spices );
print "@spices\n";
sub by_value
{
$_[0] = 'cardamom';
}
cardamom kratchai galangal
Like $_ in a foreach loop, the items in
@_ are aliased to the items in the list of arguments passed
to the subroutine. Modifing them will modify the data in the body of the
program, which is generally something you don't want to do. It's
always best to be explicit if you want the subroutine to modify its
arguments, pass by reference, if you don't, pass by value, and
immediately copy the contents of @_ into some nice lexically
scope variables.
References can get quite
complicated, which is where the module Data::Dumper comes in
very handy. Modules can be used, like you have seen with
strict: they imports some extra functionality to your
program, in this case, a function called Dumper().
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my @complex =
(
"an array",
{
containing => "a hashref"
},
"a scalar",
[
[
"a two deep arrayref",
"with two elements"
],
{
lemon => "curd"
}
]
);
print Dumper( \@complex );
$VAR1 =
[
'an array',
{
'containing' => 'a hashref'
},
'a scalar',
[
[
'a two deep arrayref',
'with two elements'
],
{
'lemon' => 'curd'
}
]
];
Data::Dumper takes a reference to any perl data
structure and spits out the contents in a) a pretty printed format, and
b) in a format that you can actually print to a file:
open my $FILE, $file; print $FILE Dumper( \@complex );
to save it for later (this is called serialisation). You can then recover the data with:
do $file;
You may remember a while ago that I said you can create anonymous subroutines:
$coderef = sub { return 2*$_[0] };
To use these, you can dereference them like normal, using
() parentheses, as you might have guessed:
print $coderef->(3);
6
This is very powerful: it means you can actually return bits of code from subroutines:
#!/usr/bin/perl
use strict;
use warnings;
my $multiply = <STDIN>;
my $coderef = construct( $multiply );
print $coderef->( 2 );
sub construct
{
my $number = shift;
my $coderef = sub { return $number * $_[0] };
return $coderef;
}
This creates coderefs on the fly which will multiply by whatever
number you decide via STDIN. The technical term for
such things is closures: the weird thing about them is that the
$number in sub construct is a lexically scoped
my variable, and ought to disappear forever (go out of
scope) when you leave the subroutine. However, when you use:
$coderef->( 2 ); # or equivalently
&{ $coderef }( 2 );
# & is the sigil for subroutines, like arrays get @ and hashes get %
the value of $number you entered when you constructed the
coderef is still there, deeply bound into the coderef, even though it
'shouldn't' really exist outside the scope of sub construct.
This is perl magic.
So far we have only talked about so called hard references.
There are also such things as soft references, which if you try to use
when strict is on will barf. In the interests of hygiene,
I'll not tell you about them, they're not much used, for good reason, and
you can always find out about them yourself. If you ever think you need
one, it's almost certain what you actually need is a hash.
Bag of tricks
There's another way of creating code on
the fly, besides closures. This is eval. eval
comes in two flavours: string and block. The block eval
looks like:
#!/usr/bin/perl
use strict;
use warnings;
$number = <STDIN>;
my $answer;
eval
{
$answer = 2 / $number;
};
print $@ ? "Divide by zero error or similar: $@" : $answer;
This is useful (and indeed the only way) to catch exceptions, that is,
divide-by-zero errors and their ilk. Note you need a ; at
the end of the eval { BLOCK }; because eval is
a statement. If something goes wrong in the block, the special variable
$@ is set with what went wrong. So eval BLOCK
allows you to test perl code, and make fatal things
non-fatal.
If you actually want to create new code on the fly, you can
use eval STRING:
#!/usr/bin/perl
use strict;
use warnings;
my $name = <STDIN>;
eval "sub $name { return \"hello\" } ";
This will create a subroutine on the fly called $name
that returns 'hello', which you can then call normally. The string is
quoted, and the usual rules for double quoted string interpolation apply.
eval is very powerful, which you should be aware of:
my $cmd = <STDIN>; eval $cmd;
is going to get you in an awful lot of trouble if someone types in
system("rm -rf *")
on Unix, or
system("DELTREE c:\\windows")
if you're on Windows. Beware.
OK. We're getting there, in terms of having covered nearly everything but modules and classes. However, here's a random bag of other Perl tricks no-one should be without.
Firstly, telling the time:
$time = scalar( localtime ); print $time;
localtime returns the local time in an array,
perldoc -f localtime for details. The commonest way of using
it though, is calling it in scalar context, which returns a useful
descriptive string.
Secondly, going to bed:
sleep 1;
makes perl sleep for 1 second (ish: subject to some
iffiness).
Thirdly, making noises:
print "\a";
\a is an alarm beep.
Fourthly, printing things prettily.
Perl has two functions for this, printf and
sprintf. Both use the same syntax, but printf
actually prints the prettified string, whereas sprintf just
returns it, so you can store the prettified version elsewhere,
e.g. in a variable. The format is a bit complex: perldoc
-f sprintf for the gory details.
printf takes at least two arguments. The first is a
control string, the second is the string to prettify. Control strings
contain placeholders that start with %. They end with a
letter that indicates the format you want: f is a fixed
decimal floating point number. e is a scientific notation
float, s is a vanilla string, and u is an
unsigned (no + or −) integer, and so on. Between the %
and the letter can come some bits and bobs to specify the format you want
your string in. If you put a number in, it specifies the minimum field
width. If you put a - in between, it means left justify,
so:
#!/usr/bin/perl
use strict;
use warnings;
my $string1 = "carrots";
my $string2 = "beans";
printf
(
"%-10s neatly lined up\n%-10s neatly lined up too",
$string1, $string2
);
carrots neatly lined up beans neatly lined up too
See that the %-10s in the control string act like
place-holders for the list of strings that follow. You can format your
code nicely so that its readers will know which placeholder refers to
which string.
Another useful one is:
#!/usr/bin/perl use strict; use warnings; my $string = "1.23465326362643743657563"; printf( "%.3f", $string );
1.235
A . followed by a number indicates a maximum
number of decimal places. Incidentally, if you need to print a literal
% character in a control string, you'll need to escape it,
like this: %%. I won't cover them here, but another way of
messing with strings is using the pack and
unpack operators, which allow you to convert between strings
(like "100") and (for example) their binary equivalent (i.e. the
actual 8-bit binary string 00000100). perldoc -f pack for
the details.
Fifthly, I mentioned earlier about loop control. If you have a bunch of loops embedded in each other:
while ( <$FILE> )
{
while ( my $word = split /\s+/, $_ )
{
print "$word" unless $word =~ /^do_not_print_me$/i;
}
}
You'll often want to abort loops prematurely. For this you'll want
next and last. Both drop you out of the current
innermost loop: next skips any remaining code, and restarts
the loops with the next value, whilst last kills the
innermost loop dead. In this case next will move onto the
next $word, whilst last will ignore the rest of
the $words generated by split :
while ( <$FILE> )
{
while ( my $word = split /\s+/, $_ )
{
next if $word =~ /^#/;
# ignore any 'word' starting with a #
last if $word =~ /^END_OF_LINE$/;
# ignore the rest of the words in the line
print $word;
}
}
The problem with this is that maybe you want to drop out of the outer loop if you find something in the inner loop. To do this, you can use labels. For example, if you were trying to parse a Perl file (not a good idea: it is widely known that the only thing that can parse Perl code properly is the perl interpreter), you might try something like this:
LINE:
while ( <$FILE> )
{
WORD:
while ( my $word = split /\s+/, $_ )
{
next LINE if $word =~ /^#/;
# ignore the rest of the line, it's only a comment
last LINE if $word =~ /^__END__$/;
# ignore the rest of the lines if you find Perl's __END__ token
next WORD if $word =~ /rude/;
# don't print anything rude
print $word;
}
}
The LABEL:s allow you to jump out of a loop from any
depth, which is very useful. The much deprecated goto LABEL
allows you to jump to anywhere in the code, but now I've told you about
it, forget about it, there is a whole world of hurt there. There's also
redo which makes for the simplest loop possible:
LOOP:
{
print "Hello. Did you know you can kill a perl program with Control-C\n";
redo LOOP;
}
This sort of loop seems to be looked down upon, but I've found it useful from time to time.
Summary
References can be made to real variables, or created directly and anonymously:
# named arrayref
@array = ( qw/q w e rt y/ );
$array_ref = \@array;
# anonymous hashref
$hash_ref = { key => "value", lemon => "curd" };
#anonymous coderef
$code_ref = sub { return 2**$_[0] };
References can be dereferenced by chasing pointers:
#!/usr/bin/perl
use strict;
use warnings;
my $thingy =
{
name =>
{
fore => "Steve",
sur => "Cook"
},
age => sub { (localtime)[5] + 1900 - 1977 }
};
print $thingy->{name}->{fore}, $thingy->{age}->();
# or equivalently
print $thingy->{name}{fore}, $thingy->{age}();
Or accessed using the block syntax
#!/usr/bin/perl
use strict;
use warnings;
my $thingy =
{
name =>
{
fore => "Steve",
sur => "Cook"
},
age => sub { (localtime)[5] + 1900 - 1977 }
};
print values %{ $thingy->{name} };
They can be used to pass data by reference to subroutines.
eval can be used to trap exceptions (like divide-by-zero
errors), (s)printf can be used for formatted printing (as
can format and write, but you don't want to go
there, and I'm not sure if anyone's gone there in years!), and labels,
next and last can be used for loop control.
Test yourself
See if you can write a script that does the following:
- Create a data structure to describe something dear to your heart. Write some subroutines that access and pretty-print the data.
#!/usr/bin/perl
use strict;
use warnings;
my $elements =
[
{ name => 'neutron', symbol => 'n' },
{ name => 'hydrogen', symbol => 'H' },
{ name => 'helium', symbol => 'He' },
{ name => 'lithium', symbol => 'Li' },
{ name => 'beryllium', symbol => 'Be' },
{ name => 'boron', symbol => 'B' },
{ name => 'carbon', symbol => 'C' },
{ name => 'nitrogen', symbol => 'N' },
{ name => 'oxygen', symbol => 'O' },
{ name => 'fluorine', symbol => 'F' },
{ name => 'neon', symbol => 'Ne' },
{ name => 'sodium', symbol => 'Na' },
{ name => 'magnesium', symbol => 'Mg' },
];
my $number = 7;
print "The name of element 7 is ", get_name_by_number( 7 ), "\n";
print "\nThe elements...\n\n";
print "$_\n" for list_elements();
sub get_name_by_number
{
my ( $element ) = @_;
$elements->[ $element ]->{name};
}
sub list_elements
{
my @pretty;
for ( @{ $elements } )
{
push @pretty, sprintf "%3s $_->{name}", $_->{symbol};
}
return @pretty;
}
This'll be the last test yourself section. From now on, if you need encouragement to use Perl, then you're probably beyond help ☺
