CGI/Perl Guide | Learning Center | Forums | Advertise | Login
Site Search: in

  Main Index MAIN
INDEX
Search Posts SEARCH
POSTS
Who's Online WHO'S
ONLINE
Log in LOG
IN

Home: Perl Programming Help: Intermediate:
Extracting an Array of Hashes from an Array of Hashes

 



PapaGeek
User

Apr 4, 2014, 2:00 PM

Post #1 of 7 (5569 views)
Extracting an Array of Hashes from an Array of Hashes Can't Post

On Edit: see my second post. It contains a solution to the problem, but the code takes a lot of steps. Please review the solution and tell me if there is a more “Perlish” way to do this!

This is a very simplified example of the complex structure I’m trying to work with in my Perl application.

In this example I am creating an array of hash references for each family which contains a couple of scalars plus an array of hash references to each family members.


Code
use Modern::Perl '2013'; 

my @Families;

main();
ShowFamilyMember(0,1);
ShowFamilyMember(1,2);
AddFamilyMember(1,"Timmy",2);
exit;

sub main
{
my %SmithFamily;
$SmithFamily{"City"} = "Denver";
$SmithFamily{"LastName"} = "Smith";

my @SmithMembers;

my %JohnSmith;
$JohnSmith{"FirstName"} = "John";
$JohnSmith{"Age"} = "22";
push (@SmithMembers, \%JohnSmith);

my %MarySmith;
$MarySmith{"FirstName"} = "Mary";
$MarySmith{"Age"} = "20";
push (@SmithMembers, \%MarySmith);

$SmithFamily{"Members"} = \@SmithMembers;

push (@Families, \%SmithFamily);


my %ClarkFamily;
$ClarkFamily{"City"} = "Denver";
$ClarkFamily{"LastName"} = "Clark";
my @ClarkMembers;

my %TonyClark;
$TonyClark{"FirstName"} = "Tony";
$TonyClark{"Age"} = "42";
push (@ClarkMembers, \%TonyClark);

my %JoanClark;
$JoanClark{"FirstName"} = "Joan";
$JoanClark{"Age"} = "41";
push (@ClarkMembers, \%JoanClark);

my %LisaClark;
$LisaClark{"FirstName"} = "Lisa";
$LisaClark{"Age"} = "11";
push (@ClarkMembers, \%LisaClark);

my %TedClark;
$TedClark{"FirstName"} = "Ted";
$TedClark{"Age"} = "9";
push (@ClarkMembers, \%TedClark);

my $size = scalar(@ClarkMembers);
say "Clark family has $size members";
$ClarkFamily{"Members"} = \@ClarkMembers;

push (@Families, \%ClarkFamily);
}

sub ShowFamilyMember
{
my($Family,$Member) = @_;

my $family = $Families[$Family];
my $LastName = ${$family}{'LastName'};

my $member = ${$family}{'Members'}[$Member];
my $FirstName = ${$member}{'FirstName'};

my $fn = $Families[$Family]{'LastName'};
my $ln = $Families[$Family]{'Members'}[$Member]{'FirstName'};

say "name: $Families[$Family]{'Members'}[$Member]{'FirstName'} $Families[$Family]{'LastName'}";
}

sub AddFamilyMember
{
my ($Family, $name, $age) = @_;
my @members = $Families[$Family]{"Members"};
my $familySize = scalar(@members);
say "family has $familySize members";
}


output is:

Code
Clark family has 4 members 
name: Mary Smith
name: Lisa Clark
family has 1 members


I have the syntax for extracting (ShowFamilyMember) data from the outer array of hash references and also the inner array of hash references.

I can’t figure out the proper syntax for adding another family member to the inner array of hash references. The first output line shows that the Clark family has 4 members as the reference to the array of hashes is stored in the family hash.

The last line or output shows that that same array has only one member when I try to retrieve it from the hash.

The inner array of family member hashes has to be properly stored in the outer array of family hashes or the Show Family Member sub would not get the proper first names.
What is the proper syntax for second line of AddFamilyMember so the last line tell the correct family size?


(This post was edited by PapaGeek on Apr 5, 2014, 2:21 AM)


PapaGeek
User

Apr 5, 2014, 2:28 AM

Post #2 of 7 (5559 views)
Re: [PapaGeek] Extracting an Array of Hashes from an Array of Hashes [In reply to] Can't Post

This code now works, but it takes a lot of steps. Is there a simpler Perlish way to do this?

Code
use Modern::Perl '2013'; 

my @Families;

main();
ShowFamilyMember(0,1);
ShowFamilyMember(1,2);
AddFamilyMember(1,"Timmy",2);
ShowFamilyMember(1,4);
exit;

sub main: same as first example
sub AddFamilyMember: same as first example

sub AddFamilyMember
{
my ($Family, $name, $age) = @_;
my @members = @{$Families[$Family]{"Members"}}; # extract dereferenced array
my $familySize = scalar(@members); # trace
say "family has $familySize members";

my %NewMember; # Create hash for new family member
$NewMember{"FirstName"} = $name; # and update the data
$NewMember{"Age"} = $age;
push (@members, \%NewMember); # add new member to dereferenced array
$Families[$Family]{'Members'} = \@members; # replace array reference in outer hash
}


Output

Code
Clark family has 4 members 
name: Mary Smith
name: Lisa Clark
family has 4 members
name: Timmy Clark

Pardon my “C” talk here, but is there a way of extracting a “pointer” to the array in the second line of Add Family Member so that the last line which replaces the array in the family’s hash is not necessary?

The main and Show Family subs are the same as in my first post.

Main creates an array of two hash references [0] and [1]. The initial code creates array of 4 hash references for the members of the second family [0] through [3].

The new Add Family Member sub now extracts the family members array from the family hash and pushes the new member onto that stack as a fifth member [4].

Show Family Member ( 1, 4 ) can now display the new member of that family.


Laurent_R
Veteran / Moderator

Apr 5, 2014, 2:55 AM

Post #3 of 7 (5557 views)
Re: [PapaGeek] Extracting an Array of Hashes from an Array of Hashes [In reply to] Can't Post

It is probably a good idea to do it stepwise as you did, expecially if you are not yet fully fluent with nested data structures, as it may be argued that this is clearer. But if you really want to reduce the number of steps, you could use directly anonymous hash refs. This should probably work (not tested):


Code
sub AddFamilyMember  
{
my ($Family, $name, $age) = @_;
push @{$Families[$Family]{"Members"}}, {"FirstName" => $name, "Age" => $age};
}

Or even changing the main code line to:

Code
    push @{$Families[$Family]{"Members"}}, {FirstName => $name, Age =>  $age};



Laurent_R
Veteran / Moderator

Apr 5, 2014, 4:50 AM

Post #4 of 7 (5552 views)
Re: [PapaGeek] Extracting an Array of Hashes from an Array of Hashes [In reply to] Can't Post

When I answered with the piece of code above, I forgot to address some of your questions.

In Reply To
Pardon my “C” talk here, but is there a way of extracting a “pointer” to the array in the second line of Add Family Member so that the last line which replaces the array in the family’s hash is not necessary?


It is probably not a very good idea to think in C terms, Perl is much more expressive than C, thinking in C terms will reduce your own expressivity. But the answer is yes, it is possible and quite easy, with the references that you already have at hand.

This line:

Code
my @members = @{$Families[$Family]{"Members"}};

is actually dereferencing the

Code
$Families[$Family]{"Members"}

reference to copy its content into the @members array. If you work directly on the reference, rather than on a copy of its content, you obtain the same effect that what you decribe in terms of C pointers. So, besides the very compact solution I posted above, this is another solutionb that is closer to your original code:

Code
sub AddFamilyMember  
{
my ($Family, $name, $age) = @_;
my $members_ref = $Families[$Family]{"Members"}; # now a reference to the inner array
my $familySize = scalar @$members_ref;
say "family has $familySize members";
my %NewMember = (FirstName => $name, Age => $age);
push @$members_ref, \%NewMember;
}

Because we now work on a reference which is a copy of the original array ref (rather than on a copy of the array content), we no longer need to do the data copying of the last line of your code: any change we do is done on the original data, on the content of the Families array.

Pushing it one step further, you don't even need the $members_ref array reference but can work directly on $Families[$Family]{"Members"} which is itself an array ref:

Code
sub AddFamilyMember  
{
my ($Family, $name, $age) = @_;
say "family has ", scalar @{$Families[$Family]{"Members"}}, " members";
my $NewMember = {FirstName => $name, Age => $age}; # $NewMember is now a hash ref
# (no longer the %NewMember hash we had before)
push @{$Families[$Family]{"Members"}}, $NewMember;
}

Pushing this idea yet further would essentially lead to the code I presented in my previous post.


PapaGeek
User

Apr 5, 2014, 6:00 AM

Post #5 of 7 (5544 views)
Re: [Laurent_R] Extracting an Array of Hashes from an Array of Hashes [In reply to] Can't Post

Thanks for that last post, @$ is the syntax I was missing.
When I originally used:

Code
 	my @members = $Families[$Family]{"Members"};  
my $familySize = scalar(@members);
say "family has $familySize members";

It said my members array was only 1 element long.

The key I got from your last post was to collect the members reference as a $ reference, and again in “C” terms, recast it as an array with the @$ syntax.

The new code works great and is exactly what I was looking for. Thanks again!

I’m not sure if I will use your first solution or the step further solution, but they both work.

My actual code is managing a list of stock market symbols and the amount invested in each. The amount is displayed in a Tk::Entry widget which requires a unique location for the input / edited output for each widget. That is primary reason I needed an array of Hashes.


(This post was edited by PapaGeek on Apr 5, 2014, 6:04 AM)


Laurent_R
Veteran / Moderator

Apr 5, 2014, 7:05 AM

Post #6 of 7 (5538 views)
Re: [PapaGeek] Extracting an Array of Hashes from an Array of Hashes [In reply to] Can't Post

Hi PapaGeek,
Pedagogically, it was a mistake on my part to first present the most concise solution, and then to come back with intermediate steps. Sorry about that, I originally wanted to give you a solution, and then only figured out that it might be useful to give you the idea in intermediate steps. Use whichever solution you feel comfortable with right now, but I tend to think that, in a year from now, facing ther same or a similar issue, you'll go for the first solution I posted. Once you understand it, it is really simpler.

But, asides from the solutions I provided, as useful as they may be, I really hope that I helped you understand what is going on with these complex data structure and the underlying references. It also took me a few weeks, or possibly even a couple of months to really understand these things when I first came across them, but once you really grasp them, you can write really efficient code (efficient in terms of coding conciseness, that is). To me, the solution in my first post is the clearest one, because there is essentially only one line of code to look at, I do not need to memorize the content of intermediate variables. But I certainly agree that people beginning to use these concepts might need the intermediary steps to be comfortable with the solution.


2teez
Novice

Apr 7, 2014, 5:40 PM

Post #7 of 7 (4995 views)
Re: [PapaGeek] Extracting an Array of Hashes from an Array of Hashes [In reply to] Can't Post

Hi PapaGeek,
If I may add my voice to the solutions already given, I will say:

1. You might have to read perldsc documentation as you take on complex data structure. It will help you on the long run. You can get this on your system thus

Code
perldoc perldsc


2. I think it might be a lot easily if you start thinking on doing what you intended in here using perl5 OO! Of course Moose and several of it's cousins comes to mind which is the way to go doing OO perl5 in modern times. However, I will show how your script might be if you are using the boilerplate of perl OO.

Code
use warnings; 
use strict;

package Families {

sub new {
my $class = shift;
my ( $city, $lastname ) = _initialized(@_);
my $family = {
city => $city,
lastname => $lastname,
member => [],
};
return bless $family, $class;
}

sub _initialized {
my $parameter = shift;

die "Please specify the family lastname and the city of residence \n"
if ref $parameter ne 'HASH';

return map { ucfirst lc $_ } $parameter->{city}, $parameter->{lastname};
}

sub add_family_member {
my $self = shift;
my ( $firstname, $age ) = @_;
$firstname = ucfirst lc $firstname;
push @{ $self->{member} }, [ $firstname, $age ];
}

sub show_family_members {
my $self = shift;
my $surname = $self->{lastname};
print $surname, " Family has ", scalar @{ $self->{member} },
" members\n";
print map { join( ' ' => $_->[0], $surname ), $/ } @{ $self->{member} };
print $/;
}

sub get_a_family_member_name {
my $self = shift;
my $level = shift;
return @{ $self->{member}[$level] }[0], ' ', $self->{lastname};
}

sub get_a_family_member_age {
my $self = shift;
my $level = shift;
return @{ $self->{member}[$level] }[1];
}

}

my $family = Families->new( { city => 'denver', lastname => 'smith' } );

$family->add_family_member( 'john', 22 );
$family->add_family_member( 'mary', 20 );

my $family1 = Families->new( { city => 'denver', lastname => 'Clark' } );

$family1->add_family_member( 'Tony', 42 );
$family1->add_family_member( 'Joan', 41 );
$family1->add_family_member( 'Lisa', 11 );
$family1->add_family_member( 'Ted', 9 );

$family1->show_family_members();
$family->show_family_members();

print 'name: ', $family->get_a_family_member_name(1), $/;
print 'name: ', $family1->get_a_family_member_name(2), $/;
print 'age: ', $family1->get_a_family_member_age(2), $/;

$family1->add_family_member( 'timmy', 2 );
$family1->show_family_members();

Note that, you don't have to physically declare an hash for each member of a family, monitoring each person. You can simply add and get family member's name and age if you want. Since which family member has it's identity in his/her class, even if they have the same and live in the place. As long as they are in different class you are covered.

You can take the Families package to a different file named after it's name with an extension of

Code
 .pm

then use stating

Code
use Families; 

my $family = Families->new( {...} );
...


Like I mentioned above a better way to go is use Moose or Moo and their different cousins! The code above is to illustrate ( though works ) that OO will be better, cleaner and easier to maintain later.

 
 


Search for (options) Powered by Gossamer Forum v.1.2.0

Web Applications & Managed Hosting Powered by Gossamer Threads
Visit our Mailing List Archives