Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chriss Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

array of hashes 3

Status
Not open for further replies.

Cagliostro

Programmer
Sep 13, 2000
4,226
GB
Hi,
I'm not a perl programmer, but I have to sole this.
I'm having problems with ararys of hashes and would know how to solve it:
Code:
my @all_users;
....
while(reading data from somewhere)
{
  my %single_user;
  my $user = $atts{"name"};
  $single_user{'group'} = $group;
  $single_user{'user'} = $user;
  #with the hash there everithing is fine:
  print $single_user{'group'}.':'.$single_user{'user'}."\n";
  push(@all_users, \%single_user);
}

.....
#reading the array of hashes elsewhere:
for(my $i = 0;  $i < scalar(@all_users); $i++)
{
...
  my %single_user = $all_users[$i];
...
  my $k;
  foreach $k(keys %single_user)
  {
    #a very strange outout
    printf "{" . $k . ":" . $single_user{$k} . "}";
  }
....
}
....
the second output is simething like this:
.....
{HASH(0x1f1d87c):}
{HASH(0x1f1d8ac):}
{HASH(0x1f1d8dc):}
{HASH(0x1f1d90c):}
{HASH(0x1f1d93c):}
{HASH(0x1f1d96c):}
{HASH(0x1f1d99c):}
....

Ion Filipski
1c.bmp
 
you have to "cast" the hash ref as a hash before assigning it to a plain hash. In other words:

my %single_user = $all_users[$i];

becomes

my %single_user = %{$all_users[$i]};

The first was just assigning a scalar reference to a hash, not the hash itself.

You should also note that this copies the hash. In most small cases, this doesn't make any tangible difference, but if the data set gets huge or the application runs many instances, the time and memory duplication can be costly and you can work directly with the references instead. The syntax changes a little, but it's not bad. Just yell if you're interrested in more details, or you can start reading perldsc and the like in perldoc.

________________________________________
Andrew
 
thanks a lot. One more small question. What would happen if I push hashes like this:
push(@all_users, %single_user); #without slash berore %
in other I would like to push in the array a copy of a variable instead of a reference, but I don't know how :(

Ion Filipski
1c.bmp
 
You could also just do the following to let perl know that $single_user is a hash reference and to get to the value for the given key:
Code:
    printf "{" . $k . ":" . $single_user[COLOR=red]->[/color]{$k} . "}";
(See for more info)


And regarding your second post, you won't be able to put the actual hashes into the array, you'll need to use references.
 
philote is right that you can't put the actual hashes into the array, at least not the way you'd like to. If you push an actual hash into an array, each "piece" of the hash becomes a separate array element. The hash won't retain its identity as a hash. Example:
Code:
#!perl
use strict;
use warnings;

my (@a1, @a2);
while (<DATA>) {
    my %temp = split;
    push @a1, %temp;  #push actual hash
    push @a2, \%temp;  #push hash ref
}

print "\@a1:\n";
for my $i (0..$#a1) {
    print "$i: $a1[$i]\n";
}

print "\n\@a2:\n";
for my $j (0..$#a2) {
    print "$j: ";
    for my $k (sort keys %{$a2[$j]}) {
        print "$k => $a2[$j]->{$k}, ";
    }
    print "\n";
}

__DATA__
a abc b bcd c cde d def
e efg f fgh g ghi h hij
Output:
Code:
@a1:
0: c
1: cde
2: a
3: abc
4: b
5: bcd
6: d
7: def
8: e
9: efg
10: h
11: hij
12: g
13: ghi
14: f
15: fgh

@a2:
0: a => abc, b => bcd, c => cde, d => def, 
1: e => efg, f => fgh, g => ghi, h => hij,

 
Yeah, without the \ it'll flatten the hash into an array and push on all the names and values as elements. Really, the best way to create the structure is how you're doing it now. The next time it goes through the loop, "my %single_user" is creating a new hash, so you get references to the new objects.

I guess you could say something like push(@all_users, {%single_user});
and it'd probably copy the hash, but I don't really think you'd want to. The { } create an anonymous hash and returns the reference to it. Putting %single_user inside should initialize this new anonymous hash with its values, making a copy.

And it takes a little more than just -> to make it work with references. The original code as posted is making a hash %single_user with a single key and no value. The key is the hash reference serialized into a string so it can act as a key, and I'm pretty sure they don't work as references anymore. Instead, you could say something like:
Code:
foreach my $k (keys %{$all_users[$i]})
{
    printf "{" . $k . ":" . $all_users[$i]{$k} . "}";
}

________________________________________
Andrew
 
Thanks a lot both.
icrf, in fact the problem comes from there:
Code:
{
  #see, there is declared outside while
  #for some reasons I need it there
  my %single_user;
  while(reading data from somewhere)
  {
    my $user = $atts{"name"};
    $single_user{'group'} = $group;
    $single_user{'user'} = $user;

    #pushing a reference to the same variable
    push(@all_users, \%single_user);
  }
}

#doing something
#it will print the last value of %single_user from above
#for each item of @all_users
......
{
...
  foreach $k(keys %single_user)
  {
    #a very strange outout
    printf "{" . $k . ":" . $single_user{$k} . "}";
  }
...
}
......


Ion Filipski
1c.bmp
 
Yeah, but if you just want the last value from above, you can index arrays in perl with negative numbers for an offset from the end. In other words, move "my %single_user" inside the while and make the bottom looks like what I had in my last post, except use -1 instead of $i.

Actually, there's another set of { } around the upper block that will create scope and %single_user won't be available below anyway.

perlreftut that philote linked above is a reasonable tutorial on references. Worth a read, as they're very useful.

________________________________________
Andrew
 
I don't want the last value. That's the trick. I need a set ov values, but I get only the last value if I do like is done in my last post

Ion Filipski
1c.bmp
 
set of values? like $single_user{'user'} contains an array of all users? You could say something like:

my @users = map { $_->{'user'} } @all_users;

________________________________________
Andrew
 
Ok, I'm a little confused. Maybe I'm mistaken, but I thought the code posted originally was correct except for the -> missing from $single_user{$k}. IonFilipski, humor me and try your original code again with the
Code:
->
put in like I showed in my last post.

 
I'm a bit out of sync with my Perl these days, but for that to work, %single_user would have to become $single_user. Run that under strict/warnings, and you'll get "Reference found where even-sized list expected" at "my %single_user = $all_users[$i];"

________________________________________
Andrew
 
you can still pack arrays into strings with rec seperators
you can still pack hashes into arrays with diffrent rec seperators

You just have to unpack at the other side, like a really bad holiday ;-)

HTH
--Paul
 
... pack .... into strings ... / ... have to unpack ... "
why? I need to pack it in a string only when I want to show it to some user, but inside the application it is two more useless steps, since I'm not constrainted to use strings.

Ion Filipski
1c.bmp
 
Sheesh - roll on Perl6 and a, hopefully, more intuitive way of dealing with compound data structures...

Mike

"Deliver me from the bane of civilised life; teddy bear envy."

Want to get great answers to your Tek-Tips questions? Have a look at faq219-2884

 
OK, I was confused, but I think I have it straight now. From the originally posted code, I believe this line:
Code:
my %single_user = $all_users[$i];
is trying to store a hash reference in a hash. And I think what happens is the hash then has one key, the hashref, and nothing else. As icrf noted in his last post, this causes perl to complain under strict/warnings. So, as Paul was hinting at, this can be fixed by changing the line to:
Code:
my %single_user = [COLOR=red]%{[/color]$all_users[$i][COLOR=red]}[/color];

OR
You could change the line to:
Code:
my [COLOR=red]$[/color]single_user = $all_users[$i]
then change the next lines to:
Code:
  foreach my $k (keys [COLOR=red]%$[/color]single_user) 
  {
    printf "{" . $k . ":" . $single_user[COLOR=red]->[/color]{$k} . "}";

Is that right now? From the testing I did both fixes seem to work. Sorry IonFilipski if I've confused you.

 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top