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.
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 );
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 = {};
}
}
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...
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)
Related
I looked at the other two questions that seem to be about this, but they are a little obtuse and I can't relate them to what I want to do, which I think is simpler. I also think this will be a much clearer statement of a very common problem/task so I'm posting this for the benefit of others like me.
The Problem:
I have 3 files, each file a list of key=value pairs:
settings1.ini
key1=val1
key2=val2
key3=val3
settings2.ini
key1=val4
key2=val5
key3=val6
settings3.ini
key1=val7
key2=val8
key3=val9
No surprise, I want to read those key=value pairs into a hash to operate on them, so...
I have a hash of the filenames:
my %files = { file1 => 'settings1.ini'
, file2 => 'settings2.ini'
, file3 => 'settings3.ini'
};
I can iterate through the filenames like so:
foreach my $fkey (keys %files) {
say $files{$fkey};
}
Ok.
Now I want to add the list of key=value pairs from each file to the hash as a sub-hash under each respective 'top-level' filename key, such that I can iterate through them like so:
foreach my $fkey (keys %files) {
say "File: $files{$fkey}";
foreach my $vkey (keys $files{$fkey}) {
say " $vkey: $files{$fkey}{$vkey}";
}
}
In other words, I want to add a second level to the hash such that it goes from just being (in psuedo terms) a single layer list of values:
file1 => settings1.ini
file2 => settings2.ini
file3 => settings3.ini
to being a multi-layered list of values:
file1 => key1 => 'val1'
file1 => key2 => 'val2'
file1 => key3 => 'val3'
file2 => key1 => 'val4'
file2 => key2 => 'val5'
file2 => key3 => 'val6'
file3 => key1 => 'val7'
file3 => key2 => 'val8'
file3 => key3 => 'val9'
Where:
my $fkey = 'file2';
my $vkey = 'key3';
say $files{$fkey}{$vkey};
would print the value
'val6'
As a side note, I am trying to use File::Slurp to read in the key=value pairs. Doing this on a single level hash is fine:
my %new_hash = read_file($files{$fkey}) =~ m/^(\w+)=([^\r\n\*,]*)$/img;
but - to rephrase this whole problem - what I really want to do is 'graft' the new hash of key=value pairs onto the existing hash of filenames 'under' the top $file key as a 'child/branch' sub-hash.
Questions:
How do I do this, how do I build a multi-level hash one layer at a time like this?
Can I do this without having to pre-define the hash as multi-layered up front?
I use strict; and so I have seen the
Can't use string ("string") as a HASH ref while "strict refs" in use at script.pl line <lineno>.
which I don't fully understand...
Edit:
Thank you Timur Shtatland, Polar Bear and Dave Cross for your great answers. In mentally parsing your suggestions it occurred to me that I had slightly mislead you by being a little inconsistent in my original question. I apologize. I also think I see now why I saw the 'strict refs' error. I have made some changes.
Note that my first mention of the initial hash of filename is correct. The subsequent foreach examples looping through %files, however, were incorrect because I went from using file1 as the first file key to using settings1.ini as the first file key. I think this is why Perl threw the strict refs error - because I tried to change the key from the initial string to a hash_ref pointing to the sub-hash (or vice versa).
Have I understood that correctly?
There are several CPAN modules purposed for ini files. You should study what is available and choose what your heart desire.
Otherwise you can write your own code something in the spirit of following snippet
use strict;
use warnings;
use feature 'say';
use Data::Dumper;
my #files = qw(settings1.ini settings2.ini settings3.ini);
my %hash;
for my $file (#files) {
$hash{$file} = read_settings($file);
}
say Dumper(\%hash);
sub read_settings {
my $fname = shift;
my %hash;
open my $fh, '<', $fname
or die "Couldn't open $fname";
while( <$fh> ) {
chomp;
my($k,$v) = split '=';
$hash{$k} = $v
}
close $fh;
return \%hash;
}
Output
$VAR1 = {
'settings1.ini' => {
'key2' => 'val2',
'key1' => 'val1',
'key3' => 'val3'
},
'settings2.ini' => {
'key2' => 'val5',
'key1' => 'val4',
'key3' => 'val6'
},
'settings3.ini' => {
'key1' => 'val7',
'key2' => 'val8',
'key3' => 'val9'
}
};
To build the hash one layer at a time, use anonymous hashes. Each value of %files here is a reference to a hash, for example, for $files{'settings1.ini'}:
# read the data into %new_hash, then:
$files{'settings1.ini'} = { %new_hash }
You do not need to predefine the hash as multi-layered (as hash of hashes) upfront.
Also, avoid reinventing the wheel. Use Perl modules for common tasks, in this case consider something like Config::IniFiles for parsing *.ini files
SEE ALSO:
Anonymous hashes: perlreftut
Hashes of hashes: perldsc
Perl makes stuff like this ridiculously easy.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use Data::Dumper;
my %files;
# <> reads from the files given on the command line
# one line at a time.
while (<>) {
chomp;
my ($key, $val) = split /=/;
# $ARGV contains the name of the file that
# is currently being read.
$files{$ARGV}{$key} = $val;
}
say Dumper \%files;
Running this as:
$ perl readconf settings1.ini settings2.ini settings3.ini
Gives the following output:
$VAR1 = {
'settings3.ini' => {
'key2' => 'val8',
'key1' => 'val7',
'key3' => 'val9'
},
'settings2.ini' => {
'key3' => 'val6',
'key1' => 'val4',
'key2' => 'val5'
},
'settings1.ini' => {
'key3' => 'val3',
'key1' => 'val1',
'key2' => 'val2'
}
};
I know that one can easily split a string into a hash using map like this question How to do I split a string into hash keys with undef values?, or this Perl Monks thread. So, something like this very easily works:
my %table = map { chomp; split(/\t/) } <DATA>;
dd \%table;
__DATA__
#1245 banana
#3499 cherry
#5290 notebook
#2112 compact_disc
Of course, this would result in:
{
"#1245" => "banana",
"#2112" => "compact_disc",
"#3499" => "cherry",
"#5290" => "notebook",
}
If one had a more complicated table of data, though, and wanted to make a hash of arrays using the second column as the key, is this possible with map, or does one have to use the "longer" form:
my %table;
while(<DATA>) {
chomp(my #elems = split(/\t/));
$table{$elems[1]} = \#elems;
}
dd \%table;
__DATA__
shelf1 #1245 banana Dole
shelf1 #3499 cherry Acme
shelf2 #5290 notebook Staples
shelf3 #2112 compact_disc Mercury_Records
to make:
{
"#1245" => ["shelf1", "#1245", "banana", "Dole"],
"#2112" => ["shelf3", "#2112", "compact_disc", "Mercury_Records"],
"#3499" => ["shelf1", "#3499", "cherry", "Acme"],
"#5290" => ["shelf2", "#5290", "notebook", "Staples"],
}
I tried these two approaches, but neither seem to work, and I'm guessing it's not possible. But, just out of curiosity (and education) was wondering if one can do it a similar way.
my %table = map{ $_->[1] => #$_ } split(/\t/, <DATA>);
my %table = map{ split(/\t/); $_->[1], #$_ } <DATA>;
You can use map, but you need to move the split inside:
my %table = map { chomp; my #s = split /\t/; $s[1], \#s } <DATA>;
I am trying to define an inline hash in a while each loop, my program is not throwing any errors but also doesn't execute the print statement. Is it possible to define an inline hash like below:
while (my (key, value) = each %{ (apple => "red", orange => "orange", grape => "purple")}) {
print "something";
}
or alternatively I can't while each loop to work if I directly call a sub in the each statement that returns a hash like the following:
sub returnsHash {
my %fruits = (
apple => "red",
orange => "orange",
grape => "purple",
);
return %fruits;
}
while (my (key, value) = each %{ returnsHash() }) {
print "something";
}
A list/comma operator in scalar context evaluates to the result of the last item evaluated in scalar context. That means
each %{ apple => "red", orange => "orange", grape => "purple" }
is equivalent to
each %{ "purple" }
This is what both of your snippets are doing, but it's undesired, and it's a strict violation. (Always use use strict; use warnings qw( all );!!!)
You are using a hash dereference (%{ ... }), but you have no hash, much less a reference to a hash that you can dereference. To build a hash and return a reference to the hash, use { ... }.
each %{ { apple => "red", orange => "orange", grape => "purple" } }
While that solves a problem, but just reveals another problem: You get an endless loop.
The iterator used by each, keys and values is associated with the hash, not the operator. Since you are creating a new hash each time through the loop, you are creating a new iterator each time through the loop, so you will always get the first element of the newly created hash, and your loop will never end.
Since you have no need to look up items by key, I don't see why you're using a hash at all. You could use the following instead:
for (
[ apple => "red" ],
[ orange => "orange" ],
[ grape => "purple" ],
) {
my ($key, $val) = #$_;
...
}
The following is how you'd write the above if you got the list from a sub.
use List::Util qw( pairs );
for (pairs(f())) {
my ($key, $val) = #$_;
...
}
Both of those create many arrays, though. Since there's no issue with being destructive, I would use the following which avoids the issue:
{
my #kvs = f();
while ( my ($key, $val) = splice(#kvs, 0, 2) ) {
...
}
}
You could also use the following, but I think many would be confused by it:
for (
my #kvs = f();
my ($key, $val) = splice(#kvs, 0, 2);
) {
...
}
That can't be done since each only works as intended with an actual variable, a hash or an array. See the synopsis in docs. Same goes for keys and values.
It is the same with the second attempt, where the function is also called anew in every iteration.
Note that a sensible thing to try would be %{ {...} } (and not %{ (...) }) since the thing inside %{} must be a hash reference. This applies to both attempts as the function returns a hash, whereby you get back a list of scalars. (This still wouldn't help, per the first statement.)
I am not sure what the need is for this, as a hash can be defined before the loop. Also, I'd like to suggest to carefully look at each before using it since it comes with complexities.
I take it that you want to iterate over key-value pairs of a dynamically created list of such pairs. Here is a way to do that using a custom iterator (which wraps the hash iterator used by each)
use warnings;
use strict;
use feature 'say';
my $hit = get_each_it(a => 1, b => 2, c => 3);
while (my ($k, $v) = $hit->()) {
say "$k => $v";
}
my ($k, $v) = $hit->(); # restarts the iterator
say "$k --> $v";
($k, $v) = $hit->(); # next (keeps state)
say "$k --> $v";
sub get_each_it {
my %h = #_;
return sub { return each %h }
}
The repeated and continued iteration (after the hash is exhausted or for individual calls) is a basic property of the hash iterator that each uses, and in doing that
So long as a given hash is unmodified you may rely on keys, values and each to repeatedly return the same order as each other.
Please study carefully how this works.
See this article on Perl.com about iterators, with a number of examples. A detailed discussion of iterators is given in Iterator module, along with a tutorial. I don't know the module well but docs are worth reading; each and every warning and caveat applies to each.
In case you don't need (or want) the capability to reset the iterator for continued iterations once the hash is exhausted, here is an alternative iterator from ikegami's comment using splice
sub get_each_it {
my #kv = #_;
return sub { return splice #kv, 0, 2 }
}
This doesn't get entangled with each and it also iterates in the order of the submitted list.
Note that by properties of a closure each code reference returned by the generator still retains its own iterator, which maintains its state when invoked from various pieces of code. Use with care.
Also see an introductory note on closure in perlfaq7
A possibility to do what you apparently want to do, would be using a for loop. The (not so) anonymous hash goes in the initialisation expression. The test expression is the assignment of each and the iteration expression stays empty.
#!/usr/bin/perl
use strict;
use warnings;
for (my %h = (
apple => 'red',
orange => 'orange',
grape => 'purple',
);
my ($key, $value) = each(%h);
) {
print("$key: $value\n");
}
This can actually be seen as a sort of a while loop with an initialisation local to the loop. %h is only in scope in the loop. So it's not anonymous to the for loop and can be used with each but ceases to exist after the loop is done.
The while executes the expression on each loop, a ref to the hash works.
sub returnsHash {
my %fruits = (
apple => "red",
orange => "orange",
grape => "purple",
);
return \%fruits;
}
my $f = returnsHash() ;
while ( my ($key, $value) = each %{ $f } ) {
print "$key => $value\n";
}
what is needed is a foreach:
use v5.36 ;
no warnings qw(experimental::for_list);
sub returnsHash {
my %fruits = (
apple => "red",
orange => "orange",
grape => "purple",
);
return %fruits;
}
foreach my ($key, $value) ( returnsHash() ) {
print "$key => $value\n";
}
I have a string as input, say apple.mango.orange = 100
I also have a hash reference:
$inst = {
'banana' => 2,
'guava' => 3,
'apple' => {
'mango' => {
'orange' => 80
}
}
};
I want to modify the value of orange using the input string. Can someone please help me how I could do this?
I tried splitting the string into (key, value) pair. I then did the following on the key string:
my $key2 = "\$inst->{".$key."}";
$key2 =~ s/\./}->{/g;
$$key2 = $value;
This does not work as intended. Can someone help me out here? I have read the Perl FAQ about not using a variable value as variable but I am unable to think of an alternative.
You are building string that consists of (buggy) Perl code, but you never ask Perl to execute it. ...but that's not the right approach.
sub dive_val :lvalue {
my $p = \shift;
$p = \($$p->{$_}) for #_;
$$p
}
my #key = split /\./, "apple.mango.orange";
dive_val($inst, #key) = $value;
or
use Data::Diver qw( DiveVal );
my #key = split /\./, "apple.mango.orange";
DiveVal($inst, map \$_, #key) = $value;
Not only is a symbolic reference a very bad idea here, it doesn't even solve your problem. You're building an expression in $key2, and just jamming another dollar sign in front of its name won't make perl execute that code. For that you would need eval, which is another bad idea
You can install and use the Data::Diver module, which does exactly this sort of thing, or you can simply loop over the list of hash keys, picking up a new hash reference each time and assigning the value to the element with the last key
The biggest issue is actually parsing the incoming string into a list of keys and a value. This code implements a subroutine apply which applies the implied operation in the string to a nested hash. Unless you are confident of your data, it needs some error checking addingto make sure each of the keys in the list exists. The Data:;Dumper output is just to demonstrate the validity of the result
use strict;
use warnings 'all';
use Data::Dumper;
my $inst = { 'banana' => 2, 'guava' => 3, 'apple' => { 'mango' => { 'orange' => 80 } } };
my $s = 'apple.mango.orange = 100';
apply($s, $inst);
print Dumper $inst;
sub apply {
my ($operation, $data) = #_;
my ($keys, $val) = $operation =~ /([\w.]+)\s*=\s*(\d+)/;
my #keys = split /\./, $keys;
my $last = pop #keys;
my $hash = $data;
$hash = $hash->{$_} for #keys;
$hash->{$last} = $val;
}
output
$VAR1 = {
'banana' => 2,
'apple' => {
'mango' => {
'orange' => '100'
}
},
'guava' => 3
};
The issue is when I try to compare the input to the output file, i am unable to handle the nesting of the parenthesis, and the complexity needs to be very low. is there a parsing module for this? compatible to 5.8.4. I found modules but they needed at least 5.10.:(
Input
(K1=V1,K2=V2,K3=V3(K2=V2.K5=V5)K6=V6(K7=V7,K8=V8(K9=V9,K10=V10)K11=V11)K12=V12,K13=V13)
OUTPUT FILE
(K0=V0,K1=V1,K2=V2,K3=V3(K1=V1,K2=V2,K4=V4,K5=V5,K14=V14),K15=V15,K6=V6(K18=V18,K7=V7,K19=V19,K8=V8(K20=V20,K9=V9,K16=V16,K10=V10,K21=V21)K11=V11)K12=V12,K13=V13,K22=V22)
I need to pick up each key value pair from input and one by one verify from the output file that the value is the same. if not
I need to store the key with the existing value.( The issue is with the nesting )
INPUT
K3=V3(K2=V2,K5=V5)
OUTPUT
K3=V3(K1=V1,K2=V2,K4=V4,K5=V5,K14=V14)
The issue is that "K2=V2" inside the V3 value is to be checked inside the V3 value in the output file. So I cannot just use a regular expression to do that as K2=V2 may appear outside the V3 parenthesis too.
I was trying to create a hash of a hash of a hash but failed. could someone suggest a way I could achieve this?
The following code builds the hash of hashes. Note that values (V3) are lost if they contain an inner hash.
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
sub to_hash {
my $string = shift;
$string =~ s/^\( | \)$//gx; # Remove the outer parentheses.
my #stack = {};
my #keys;
while (length $string) {
$string =~ s/^([^,=()]+) = ([^(),]*)//x or die $string;
my ($key, $value) = ($1, $2);
$stack[-1]{$key} = $value;
next if $string =~ s/^,//;
if ($string =~ s/^\(//) {
push #stack, {};
push #keys, $key;
} elsif ($string =~ s/^\),?//) {
my $last = pop #stack;
$stack[-1]{ pop #keys } = $last;
}
}
return $stack[0]
}
my $input = '(K1=V1,K2=V2,K3=V3(K2=V2,K5=V5)K6=V6(K7=V7,K8=V8(K9=V9,K10=V10)K11=V11)K12=V12,K13=V13)';
print Dumper to_hash($input);
Output
$VAR1 = {
'K2' => 'V2',
'K13' => 'V13',
'K6' => {
'K7' => 'V7',
'K8' => {
'K9' => 'V9',
'K10' => 'V10'
},
'K11' => 'V11'
},
'K3' => {
'K2' => 'V2',
'K5' => 'V5'
},
'K12' => 'V12',
'K1' => 'V1'
};
Nested parens either suggests an application of Text::Balanced and its extract_bracketed function, or building yourself a little parser subclass on Parser::MGC. Using the latter to build a little "convert string into data structure" parser is usually pretty straightforward for simple examples like this.