Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I've got myself in a problem. Whats the best way to have a variable structure for lots and lots of info? I've got people, and they each have about 30 or so variables that need to go with them.

Occassionally, they need to be called by their ID. Other times, by their name. Here is how I have it right now:

$PEOPLE{$idnumber} = [$name, $var1, $var2, $var3, $etc]; $NAMES{$name} = $idnumber;
When I want to call someone's third variable, and I've got their name, it gets a bit clumsy.
$PEOPLE{$NAMES{$name}}[2]
Is there a better way around this? There has got to be an easier way.. Thanks.

Replies are listed 'Best First'.
Re: Complex Data Structures?
by Adam (Vicar) on Dec 21, 2000 at 03:25 UTC
    This sounds like a job for OO. Build two indecies of people objects, one by id and the other by name. Of course, both indecies are hashes where the value is a reference to the correct person object. Then give your object get methods:

    result:

    { package Person; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; bless ($self, $class); # Add member variables. $self->{Name} = @_ ? shift : die "Person Constructor expec +ted a name"; $self->{Rank} = @_ ? shift : die "Person Constructor expec +ted a rank"; $self->{SerialNumber} = @_ ? shift : die "Person Constructor expec +ted a serial number"; return $self; } sub Name { my $self = shift; return $self->{Name}; } sub SerialNum { my $self = shift; return $self->{SerialNumber}; } # And whatever other methods you want. } my $dad = Person->new( 'dad', 'top dog', 1 ); my %ByName; while( ReadPeopleDataFromSomewhere() ) { my $person = Person->new( $name, $rank, $serial ); # Error check for repeat names? # I didn't, but that might not be a bad idea. $ByName{$person->Name()} = $person; # Likewise, one hopes that serial numbers are unique. $BySerial{$person->SerialNum()} = $person; }

    The above sample code is untested but gives you the idea. You should definately read perltoot.

(Ovid) Re: Complex Data Structures?
by Ovid (Cardinal) on Dec 21, 2000 at 03:27 UTC
    Well, I'm just taking a guess as to what you want from the data you have supplied, but a hash of hashes might work
    my %people = ( 7 => { name => 'Bob Jones', address => '555 Anywhere Street', city => 'Allentown', state => 'Pennsylvania' }, 3 => { name => 'Pete Hollister', address => '222 Northview Drive', city => 'Shoreham', state => 'unknown' } ); my $idnumber = 3; print $people{ $idnumber }{ name };
    The above code prints "Pete Hollister". Here's the same thing with a hash of arrays:
    my %people = ( 7 => [ 'Bob Jones', '555 Anywhere Street', 'Allentown', + 'Pennsylvania' ], 3 => [ 'Pete Hollister', '222 Northview Drive', 'Shoreh +am', 'unknown' ] ); my $idnumber = 3; print $people{ $idnumber }[0];
    Or, to be complete obnoxious (and I don't recommend this), we could go with an array of pseudo-hashes which would allow you to use this as a hash of hashes or a hash of arrays as you like:
    my $phashref = { name => 1, address => 2, city => 3, state => 4 }; my %people = ( 7 =>[$phashref, 'Bob Jones', '555 Anywhere Street', 'Al +lentown', 'Pennsylvania' ], 3 =>[$phashref, 'Pete Hollister', '222 Northview Drive' +, 'Shoreham', 'unknown' ] ); my $idnumber = 3; print $people{ $idnumber }->{ name } . "\n"; print $people{ $idnumber }->[1];
    The above resulted from my being a bit bored at work and having some free time. I was just playing around and I strongly advise against this approach. Aside from the difficulties with maintenance, merlyn mentioned that pseudo-hashes are probably not going to be in Perl 6.

    Cheers,
    Ovid

    Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

      Add my vote for the hash of hashes bit. It makes your code easier to maintain than a hash of lists. Plus, if you end up adding a field (zip?), you'll probably find it more intuitive to use $people{$idnumber}{zip} than $people{$idnumber}[4].

      See also perldsc.

      Lets say I want to know the city and all I have is the person's name. How would I find this information without a loop, going through each key of the hash until the persons named matched? Thanks a lot for your quick responses!
        As has been suggested, you may want to consider a database. However, if all you have is a person's name, how do you ensure that the names are unique? If you have two names set to "Twinkletoes", you're going to have a problem.

        Assuming that you have unique names, why not drop the id number? However, I wouldn't drop the it - a person may get married, change their name, have their real identity exposed, etc. But if you're not storing the names for long, it may not be relevant.

        An id number is used to provide a non-identifying means of getting to the data that you want. One way to do this is to map the cities to an array (we assume you might have more than one city because you may have duplicate names):

        my %people = ( 7 => { name => 'Bob Jones', address => '555 Anywhere Street', city => 'Allentown', state => 'Pennsylvania' }, 3 => { name => 'Pete Hollister', address => '222 Northview Drive', city => 'Shoreham', state => 'unknown' }, 9 => { name => 'Pete Hollister', address => '222 Northview Drive', city => 'New York', state => 'unknown' } ); my $name = 'Pete Hollister'; my @cities = map { $people{ $_ }{ name } eq $name ? $people{ $_ }{ cit +y } : () } keys %people; $,=', '; # This sets the array separator to a comma and space, allowin +g # easy printing of arrays print @cities;
        That should print "New York, Shoreham".

        However, if names are guaranteed to be unique, use them as the hash key and just print $people{ $name }{ city }. This is really the only way you're going to avoid a loop unless you have the unique id number up front.

        You're facing the problem that as your data grows more complex, your code usually must grow more complex with it. Take the information above and stuff it away into a subroutine, or better still, take Adam's suggestion and go with an object-oriented module. I'm guessing that you may not be familiar with the latter due to the nature of your question, but it's easier than you might suspect once you get used to the syntax and the new way of thinking.

        Cheers,
        Ovid

        Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

Re: Complex Data Structures?
by chipmunk (Parson) on Dec 21, 2000 at 03:28 UTC
(tye)Re: Complex Data Structures?
by tye (Sage) on Dec 21, 2000 at 22:58 UTC

    I've done this before like so:

    my %People; while( my $id= GetNextId() ) { my( $name, $age, $addr, $city, $state, $phone )= GetDataForId( $id ); my $record= { NAME=>$name, AGE=>$age, ADDR=>$addr, CITY=>$city, STATE=>$state, PHONE=>$phone }; warn "Duplicate ID numbers: $People{ID}{$id} vs. $name\n" if $People{ID}{$id}; $People{ID}{$id}= $record; foreach my $field ( qw( NAME AGE STATE PHONE ) ) { push @{ $People{$field}{ $record->{$field} } }, $record; } } print "(The first) John Smith is ", "$People{NAME}{'John Smith'}[0]{AGE} years old.\n"; print "There are ",0+@{$People{AGE}{20}}, " 20-year-olds:\n"; foreach my $person ( @{ $People{AGE}{20} } ) { print "\t",$person->{NAME},"\n"; }
    Note how easy it is to extend to index on more fields. If this confuses you, then you can change to code to prepend "By" to each field name for indices so you write:
    print "(The first) John Smith is ", "$People{ByNAME}{'John Smith'}[0]{AGE} years old.\n";
    instead.

    Note how each level is a reference so if you are dealing with one part over and over you can make short cuts:

    my $byID= $People{ID}; foreach my $person ( @$byID ) { }
    and that each of the many ways of getting at the data all get to the same copy of the data so you aren't wasting space and can update the data via any way you get to it.

            - tye (but my friends call me "Tye")
Re: Complex Data Structures? (A different view)
by extremely (Priest) on Dec 21, 2000 at 16:24 UTC
    As long as your names and idnumbers are both unique, and under your current scheme they must be, you could do this:
    #!/usr/bin/perl -w use strict; my %PERSON; my %NAME; $NAME{Mark} = $PERSON{a137} = ["Mark", "a137", "var1", "var2", "var3", + "var3"]; print "$NAME{Mark}[2] is $PERSON{a137}[2]\n"; $NAME{Mark}[2] = "New1"; print "$NAME{Mark}[2] is $PERSON{a137}[2]\n";

    Since you are going to the trouble of creating anonymous arrays save them in BOTH structures at once. Then updating either gets you the change in both, with less memory used than your method. Sexy fun...

    --
    $you = new YOU;
    honk() if $you->love(perl)