0

I have searched and searched and I can't get any of the code I've found to work. I'm sorry if this is repeating old ground, but I've now spent 2 days trying to get these 10 lines to work and I am at my wits' end with no hair left :-(

I am running Perl 5.8.8.

I want to populate an array of hashes in Perl such that it contains multiple copies of a single hash variable I am updating. My code is here:

use strict;
use warnings;

my @array;

my %tempHash = (state => "apple", symbol => "54", memberId => "12345");
push(@array, \%tempHash);

%tempHash = (state => "tiger", symbol => "22", memberId => "12345");
push(@array, \%tempHash);

%tempHash = (state => "table", symbol => "37", memberId => "12345");
push(@array, \%tempHash);

printf("%p %p %p\n", $array[0], $array[1], $array[2]);

foreach my $entry (@array){
    printf("state: %s\n", $entry->{state});
    printf("memberId: %s\n", $entry->{memberId});
    printf("symbol: %s\n\n", $entry->{symbol});
}

This produces the following output:

1868954 18688d0 18688c4
state: table
memberId: 12345
symbol: 37

state: table
memberId: 12345
symbol: 37

state: table
memberId: 12345
symbol: 37

So it looks to me like the scalar values in the array are different. Yet the values in the hashes these scalars point to are all the same.

Thanks in advance for your help.

Alan Haggai Alavi
  • 72,802
  • 19
  • 102
  • 127
Stephan
  • 99
  • 1
  • 9
  • I would expect it to do this. You're pushing a *reference* of the hash onto the array, but you are only changing the contents. Last content assignment wins. So you keep on resetting the contents of this hash you have stored *3 times*. – Axeman Aug 02 '12 at 12:11

4 Answers4

6

1) The code you posted doesn't work under use strict;, did you mean %tempHash and %hash are really the same variable?

2) If you use %s instead of %p, you'll get 3 identical HASH(0x1234abcd) strings, which means the contents of the array are indeed references to the same hash.

3) I would suggest creating a new anonymous hash each time:

#!/usr/bin/perl -w
use strict;
use Data::Dumper;

my @array;
my %tempHash = (state => "apple", symbol => "54",memberId => "12345");
push(@array, { %tempHash });

%tempHash = (state => "tiger", symbol => "22", memberId => "12345");
push(@array, { %tempHash });

%tempHash = (state => "table", symbol => "37", memberId => "12345");
push(@array, { %tempHash });

print Dumper( \@array );
Dallaylaen
  • 5,268
  • 20
  • 34
  • but it doesn't help, cause it is not the same references, really. add a line `$tempHash{'state'}='any';` and i will not see differences (checked on perl 5.8.8) – gaussblurinc Aug 02 '12 at 11:46
  • Ahaaaaa. That works. My last few strands of hair remain eternally grateful. Thanks. – Stephan Aug 02 '12 at 11:53
  • @loldop: I understood this question as "make `@array` a track of `%tempHash`'s content at different times". However, my guess may be wrong. (But then what, a bunch of references to *the same* hash? Why?) – Dallaylaen Aug 02 '12 at 12:03
  • @Dallaylaen yeah, sorry, i can't understand question at all. and your answer really help OP. – gaussblurinc Aug 02 '12 at 12:04
  • @Dallaylen - '@array' tracking %tempHash is exactly it. I am parsing a CSV file a line at a time and want to store processed results in '@array'. My thanks to all once more. – Stephan Aug 02 '12 at 12:09
  • @Stephan: Are you using `Text::CSV` and calling `my $hr = $csv->getline_hr($fh)`? In that case you can keep the hash as a reference instead of dereferencing it, and just `push, @array, $hr`. The module will return a reference to a *different* hash each time and you are making your own problems by copying the data to the same hash each time. – Borodin Aug 02 '12 at 12:21
  • @Borodin: I am indeed using `Text::CSV`. However, I don't want the hash of the entire line (it's a huge file) - just a hash of some results I fill in as I scan. Thanks though. – Stephan Aug 02 '12 at 12:24
  • 1
    @Stephan: It would help a lot to see your real code, but please check out my answer which would be the best way to solve things if my assumptions are correct – Borodin Aug 02 '12 at 12:30
2

It sounds like you are fetching data a line at a time from a CSV file using Text::CSV.

Suppose your code is like this

my %tempHash;
my @array;

while (my $line = $csv->getline($fh)) {
  # Add values to %tempHash;
  push @array, \%tempHash;
}

then you could solve your problem very simply by declaring %tempHash insode the while loop

my @array;

while (my $line = $csv->getline($fh)) {
  my %tempHash;
  # Add values to %tempHash;
  push @array, \%tempHash;
}

because Perl creates a new lexical hash each time the block is entered


Update

If the data isn't necessarily complete after each input record, then write

my @array;
my $data = {};

while ( my $line = $csv->getline($fh) ) {
  # use information from $line to supplement $data
  if ($data is complete) {
    push @array, $data;
    $data = {};
  }
}
Borodin
  • 126,100
  • 9
  • 70
  • 144
  • +1, *if* the hash is populated within a loop, this is the most natural solution. – Dallaylaen Aug 02 '12 at 12:39
  • @Borodin - thank you. Some points though: 1. I can't post my code yet because I haven't written it - spent the last two days on this issue. 2. There are specific reasons why I can't do everything within the while loop (I am parsing consecutive strings in columns of the CSV, and the only way I know a string is terminated is by reading the next line. 3. The internet is littered with this answer, which I actually found unhelpful because the while loop really masks what is actually happening through the fact that the lexical hash is recreated each time. – Stephan Aug 02 '12 at 13:08
  • @Stephan: It is difficult to offer a proper solution when we know very little of the problem. I have updated my answer to offer an alternative that I think answers your situation. It is better than pushing `{ %tempHash }` every time as it avoids repeatedly copying the hash data – Borodin Aug 02 '12 at 13:21
  • @Borodin - I'm all good for a solution, thanks. Dallaylaen's code works a treat and makes perfect sense, so we can consider this one closed. Thanks for all your help - I'd +1 you both if I had enough experience points... – Stephan Aug 02 '12 at 13:25
  • @Stephan: It's your call, but it's *wrong* to keep copying hashes like that when it isn't necessary – Borodin Aug 02 '12 at 14:21
1

If you'd added use strict and use warnings to tor script, it would have told you what's wrong:

1st, your filling the hash temphash and store a reference to it. Next you create a new has, hash which you fill BUT NEVER USE! Instead, you add new references to temphash...

pavel
  • 3,488
  • 1
  • 20
  • 20
0

My bet is the push function does just not care about references. See perldoc:

Starting with Perl 5.14, push can take a scalar EXPR, which must hold a reference to an unblessed array. The argument will be dereferenced automatically. This aspect of push is considered highly experimental. The exact behaviour may change in a future version of Perl.

EDIT

You could try to not use push:

my @array;
my %tempHash = (state => "apple", symbol => "54", memberId => "12345");
$#array=4;
$array[0]=\%tempHash;
$array[1]=\%tempHash;
$array[2]=\%tempHash;
printf("%p %p %p\n", $array[0], $array[1], $array[2]);
foreach my $entry (@array){
    printf("state: %s\n", $entry->{state});
    printf("memberId: %s\n", $entry->{memberId});
    printf("symbol: %s\n\n", $entry->{symbol});
}

with same result as my perl interpreter just told me :-( (perl 5.14.2)

Matthias
  • 3,458
  • 4
  • 27
  • 46
  • I think that doesn't work because you add a reference to the same variable inside @array. I'm also a little dubious on whether you can go off and access $array[0] before you've pushed something onto the array. – Stephan Aug 02 '12 at 12:05
  • That documentation refers to using a reference as the *first* parameter of `push`, so you can write `push $arrayref, $value`. It is fine to push any scalar - including a reference - onto a simple array as the OP is doing – Borodin Aug 02 '12 at 12:12