Forwarded: Fri, 25 Aug 95 13:35:21 MDT
Forwarded: Larry Wall <lwall@netlabs.com>
Return-Path: tchrist Fri
From: Tom Christiansen <tchrist@mox.perl.com>
cc: shapirj@rintintin.Colorado.EDU,merlyn@stonehenge.com
Newsgroups: comp.lang.perl.misc
Subject: Re: Question about multi-d arrays
Summary: 
References: <41js6f$3mj@Shamino.quincy.edu> <ukrb2af3ay.fsf@linda.teleport.com> <41kti7$n2u@CUBoulder.Colorado.EDU>
Reply-To: tchrist@mox.perl.com (Tom Christiansen)
Followup-To: 
Distribution: 
Organization: Perl Consulting and Training
Keywords: 

In comp.lang.perl.misc, 
    shapirj@rintintin.Colorado.EDU (Jim Shapiro) writes:
:In article <ukrb2af3ay.fsf@linda.teleport.com>,
:Randal L. Schwartz <merlyn@stonehenge.com> wrote:
:
:[... deleted]
:
:>There are enough ways to get this wrong that it is better to have
:>pounded into your head that (even in Perl5):
:>
:>	there are no lists of lists
:>	there are no lists of associative arrays (hashes)
:>	there are no hashes of lists
:>	there are no hashes of hashes
:
:From the perlref man pages:
:
:Since arrays and hashes contain scalars, you can now easily build arrays of
:arrays, arrays of hashes, hashes of arrays, arrays of hashes of functions,
:and so on.

Either Larry or I wrote that part of the perlref man page (I forget
which), whereas Randal wrote the news posting.

I'd say that Randal's statement is both correct and somewhat misleading.
You can have multilevel data structure, appearing for many intents and
purposes as arrays of arrays, arrays of hashes, hashes of hashes, and
hashes of arrays.  Nonetheless, as in C, these are implemented as an array
of references to arrays, etc, which means you must always explicitly
dereference things.  It's misleading though, because telling someone that
$grid[$x][$y][$z] isn't a multilevel array or that
$record{USER}{DOTFILES}[0] isn't a multilevel hash/array mix isn't going
to help them understand things much.

On the other hand, having just finished giving a course on data structures
in perl, I did find myself muttering what Randal said many a time to my
students, how didn't get it.  Consider this:

    #!/usr/bin/perl -w
    require 5.001;
    use strict;

    # 
    # GLOBALS HERE
    #
    my(%Logins, @Uids); 

    read_dots();
    showdots('root');
    showdots('ftp');
    showdots('tchrist');

    sub read_dots {
	my @p;
	while ( @p = getpwent() ) {
	    my @dots = ();
	    if (opendir(DIR, $p[7])) {
		@dots = sort grep /^\./, readdir DIR;
		splice(@dots, 0, 2);  # dot and dotdot, probably
		closedir DIR;
	    }

	    my $rec = {
		UID         => $p[2],
		LOGIN       => $p[0],
		DIR         => $p[7],
		DOTS        => [ @dots ],
	    };

	    $Logins{ $rec->{LOGIN} } = $rec;
	    $Uids[ $rec->{UID} ]     = $rec;
	}
    }

    sub showdots {
	my $user = shift;
	printf "User %-8s has %2d dot files: %s\n", 
		$user, 
		scalar @{ $Logins{$user}{DOTS} }, 
		join " " => @{ $Logins{$user}{DOTS}};
    } 

Which happen to print out:

    User root     has  4 dot files: .cshrc .login .ncrecent .profile
    User ftp      has  2 dot files: .hidden .welcome
    User tchrist  has 79 dot files: .Xauthority .Xdefaults .active .article [ETC]

Now, given that, you could also get at root's dotfiles this way

    $dotrefs = $Logins{root}{DOTS};
or
    $dotrefs = $Uids[0]{DOTS};

and print them out this way:

    print "root has these dot files: @$dotrefs\n";

Or in one swell foop:

    print "root has these dot files: ", join' ', @{ $Logins{root}{DOTS} }, "\n";
    print "root has these dot files: ", join' ', @{ $Uids[0]{DOTS} }, "\n";

The major areas where people make mistakes are things like this:

	my $rec = {
	    UID		=> $p[2],
	    LOGIN	=> $p[0],
	    DIR		=> $p[7],
	    DOTS	=> @dots,
	};

That won't work, because you interpolated the list into the hash.
If this ended up making an odd number of elements, you might get
something like this:

    Odd number of elements in hash list at /tmp/tp line 22.
    Can't use string (".cshrc") as an ARRAY ref while "strict refs" in use at /tmp/tp line 38.

If you added 'use diagnostics' (and are running the not-yet-standard
diagnostics module I wrote (ftp from perl.com if you want), it would
say this:

    Odd number of elements in hash list at /tmp/tp line 23 (#1)
	(S) You specified an odd number of elements to a hash list, which is odd,
	since hash lists come in key/value pairs.
	
    Can't use string (".cshrc") as an ARRAY ref while "strict refs" in use at /tmp/tp line 39 (#2)
	(F) Only hard references are allowed by "strict refs".  Symbolic references
	are disallowed.  See perlref.
	
    Uncaught exception from user code:
	    Can't use string (".cshrc") as an ARRAY ref while "strict refs" in use at /tmp/tp line 39.
	Bailing out at /usr/local/lib/perl5/diagnostics.pm line 423
	    diagnostics::death_trap called at /tmp/tp line 39
	    main::showdots called at /tmp/tp line 8

Of course, without -w and use strict, you'll be in real trouble, 
because the program would myseriously product the wrong answer.

You can't assign a list to a scalar, nor use a list as the value (or key)
of a hash.  You must use a list REFERENCE.

Another common mistake is this:

	my $rec = {
	    UID		=> $p[2],
	    LOGIN	=> $p[0],
	    DIR		=> $p[7],
	    DOTS	=> \@dots,
	};

Because that would make all the records contain
a reference to the very same space!  

Well, ok, that's not true: because of my care to employ use strict, and to
make @dots a lexical variable that gets brand-newified each loop, and the
way lexical closure and reference counting works in perl, it does actually
give the right answer.  I was very lucky.  In almost any other case,
however, it will fail, such as

    1) if @dots is a global variable
    2) if the my() declaration is moved
	to above rather than inside the loop

Does that clear any of this up?  


--tom

