I am hacking a git-svn Perl script. I have a $paths variable which I think contains an array of individual paths, but I am having a hard time iterating over it. My end goal is to to add an additional attribute to one path.
Here is the dumper output.
{
"/dira" => {
action => "A",
copyfrom_path => undef,
copyfrom_rev => -1
},
"/dira/dirb" => {
action => "A",
copyfrom_path => undef,
copyfrom_rev => -1
},
"/dira/dirb/test.55mb.file" => {
action => "A",
copyfrom_path => undef,
copyfrom_rev => -1
},
}
According to that output, $paths is a reference to a hash of references to hashes.
If you know which path you want to extend, you don't need to iterate:
$paths->{'/foo/bar'}{'my_attribute'} = 42;
If you want to do this uniformly to all paths, you can do this:
for my $attrs (values %$paths) {
$attrs->{'my_attribute'} = 42;
}
See perldoc perldata for information about hashes and perldoc perlreftut for references and nested data structures.
Related
I have an array of hashrefs built from a database using fethrow_hashref(). The data structure is built like so:
while (my $ref = $sth->fetchrow_hashref()) {
push #lines, $ref;
}
I sort the data in the query by program name ascending, so all of the references in the array are still in alphabetical order. Then, I go through each hash and find the value that is numerically equal to a '1'. I then take the caolumn name, and store it to compare to the rest of the hashrefs with that program name to ensure they all have a '1' in the same column.
my $pgm = "";
my $met_lvl = "";
my #devs = ();
my %errors = ();
my $error = "";
foreach my $line_ref (#lines) {
if ($pgm ne $line_ref->{"PROGRAM"}) {
if (#devs && $error) {
# print " Different number metal layers for $pgm: #devs \n";
$error = "";
}
#devs = ();
$pgm = $line_ref->{"PROGRAM"};
($met_lvl) = grep { $line_ref->{$_} == 1 } keys(%$line_ref);
push #devs, $line_ref->{"DEVICE"};
} elsif ($pgm eq $line_ref->{"PROGRAM"}) {
push #devs, $line_ref->{"DEVICE"};
my ($met_chk ) = grep { $line_ref->{$_} == 1 } keys(%$line_ref);
if ($met_chk ne $met_lvl) {
$errors{$line_ref->{"PROGRAM"}} = $line_ref->{"PROGRAM"};
$error = "YUP";
}
}
}
I'd like to be able to access the hashrefs individually, based on matching column names from the database. How can I access the hashrefs with "TEST" values for "PROGRAM" keys? I used Data::Dumper to provide an example of a few of the hashrefs I'd like to access based on "PROGRAM" value:
'PLM' => undef,
'SLM' => undef,
'QLM' => undef,
'DEVICE' => 'DEV1',
'TLM' => '1',
'DLM' => undef,
'ROUTING' => 'NORMAL',
'PROGRAM' => 'TEST'
};
$VAR455 = {
'PLM' => undef,
'SLM' => undef,
'QLM' => undef,
'DEVICE' => 'DEV2',
'TLM' => '1',
'DLM' => undef,
'ROUTING' => 'NORMAL',
'PROGRAM' => 'TEST'
};
$VAR456 = {
'PLM' => undef,
'SLM' => undef,
'QLM' => undef,
'DEVICE' => 'DEV3',
'TLM' => '1',
'DLM' => undef,
'ROUTING' => 'NON_STANDARD',
'PROGRAM' => 'EXP'
};
$VAR457 = {
'PLM' => undef,
'SLM' => undef,
'QLM' => undef,
'DEVICE' => 'DEV4',
'TLM' => '1',
'DLM' => undef,
'ROUTING' => 'NORMAL',
'PROGRAM' => 'FINAL'
};
I'd like to be able to access key values for the hashrefs which contain the same program name. I cannot even begin to figure out what type of operation to use for this. I assume map is the correct way to do it, but dereferencing the "PROGAM" value for each element (hashref) in the array is beyond the scope of my understanding. I hope I was able to define the problem well enough for you guys to be able to help.
Edit: The impetus for wanting to access hashrefs with the same 'PROGRAM" value is to be able to provide an output of selected values to print to a logfile. So, after I compare and find differences between those hashrefs with the same "PROGRAM" value, I want to access them all again, and print out the desired column values to the lofgile.
Looks like you need to exrtact subsets of your data (hashrefs) with the same PROGRAM name.
Can preprocess your data to build a hash with those names as keys, and arrayrefs (with suitable hashrefs) as values. Then process those groups one at a time.
use warnings;
use strict;
use feature 'say';
use Data::Dumper; # to print complex data below
... populate #lines with hashrefs as in the question or copy-paste a sample
# Build hash: ( TEST => [ hashrefs w/ TEST ], EXP => [ hashrefs w/ EXP ], ... )
my %prog_subset;
for my $hr (#lines) {
push #{ $prog_subset{$hr->{PROGRAM}} }, $hr;
# Or, using "postfix dereferencing" (stable from v5.24)
# push $prog_subset{$hr->{PROGRAM}}->#*, $hr;
}
foreach my $prog (keys %prog_subset) {
say "\nProcess hashrefs with PROGRAM being $prog";
foreach my $hr (#{ $prog_subset{$prog} }) {
say Dumper $hr;
}
}
(See postfix dereference)
Now %prog_subset contains keys TEST, EXP, FINAL (and whatever other PROGRAM names are in data), each having for value an arrayref of all hashrefs which have that PROGRAM name.
There are other ways, and there are libraries that can be leveraged, but this should do it.
OK! I found an example of this with the google machine. I replaced #lines = (); with $lines = [];. This allowed me to change the grep statement to (#found) = grep { $pgm eq $_->{PROGRAM} } #$lines;. Now the returned array is a list of the hashrefs that share the program name I'm looking for. Thanks for the help #zdim!
I have to iterate over a nested hash in perl and carry out some operations. The structure I have is
$featureGroup = [
{
featureType => "widget",
name => "dpx-shadow-fleet",
parameterMap => { dpxContext => "shadowAtf", dpxEndPoint => "/art/dp/ppd?" },
},
{
featureType => "widget",
name => "dpx-shadow-fleet",
parameterMap => { dpxContext => "shadowBtf", dpxEndPoint => "/art/dp/btf?" },
},
{
features => [
{
featuredesc => [
{
critical => 1,
featureType => "widget",
name => "dpx-ppd",
parameterMap => { dpxContext => "atf", dpxEndPoint => "/art/dp/" },
},
{
featureType => "widget",
name => "error",
parameterMap => { errorMessageId => "error" },
},
],
featureType => "sequence",
},
{
critical => 1,
features => ["encode-landing-image", "image-encoding-error"],
featureType => "sequence",
},
],
handler => "/gp/product/features/embed-landing-image.mi",
name => "embed-landing-image",
pfMetrics => { "" => undef, "start" => sub { "DUMMY" }, "stop" => sub { "DUMMY" } },
type => "custom-grid",
},
];
I want to iterate over the featuredesc subarray and get the value name. I am trying out this.
for(my $i = 0; $i < #$featureGroup; $i++){
if(defined $featureGroup->[$i]->{'features'}){
for(my $j = 0; $j < #$featureGroup->[$i]->{'features'} ; $j++){
print "$featureGroup->[$i]->{'features'}->{'featuredesc}->{name}";
}
}
}
But this is not working. I am not understanding where am I going wrong. Any pointers in the right direction would be useful.
You have a very complex data object there and you have already encountered problems dealing with it. While I could help you address your direct problem, I think you would benefit more from learning how to reduce the complexity.
Perl supports Object Oriented programming. This allows you to take data structures and attach subroutines to them that operate on them. You can read about Perl OO here. I will show you quickly how you can turn the $featureGroup list into a list of objects, and how to access the features that a single object contains. You should apply this technique to every hash in your datastructure (you can tone it back if you are sure that certain inner hashes should not be objects, but it is probably better to start by overdoing it and then scale back rather than the other way around).
This is one of the feature group hashes:
{
'featureType' => 'widget',
'name' => 'dpx-shadow-fleet',
'parameterMap' => {
'dpxContext' => 'shadowAtf',
'dpxEndPoint' => '/art/dp/ppd?'
}
}
In this one you have a featureType, name, and parameterMap. These fields do not appear in every object in your list (in fact the last hash looks quite different to the first two). I will show you how to create an object which requires those three parameters:
package Feature;
use Moose; # You may have to install this
has 'featureType' => (
'is' => 'rw',
'isa' => 'Str'
);
has 'name' => (
'is' => 'rw',
'isa' => 'Str'
);
has 'parameterMap' => (
'is' => 'rw',
'isa' => 'HashRef'
# You could make this accept another object type
# if you convert this inner hash
);
You can then construct your object like so:
my $f = new Feature(
'featureType' => 'widget',
'name' => 'dpx-shadow-fleet',
'parameterMap' => {
'dpxContext' => 'shadowAtf',
'dpxEndPoint' => '/art/dp/ppd?'
}
);
You are then able to access those fields by using the named accessors:
print $f->name; # dpx-shadow-fleet
At the moment this just seems like a longer way to use a hash, right? Well the real benefit comes from being able to define arbitrary subroutines on the class which hide complexity from the caller. So you want to operate on the features array in your original question. Lets define that as a field:
has features => (
is => 'rw',
isa => 'ArrayRef[HashRef]'
# This is an array containing hashes
# You _really_ want to turn the inner hashes into an object here!
);
Then we can operate on them in another subroutine. Lets define one that returns every feature that is a sequence (has a featureType of sequence):
sub get_sequences {
my ($self) = #_;
return grep { $_->{featureType} eq 'sequence' } #{ $self->features };
}
Now when you use an object of this type to get the sequence features all you need to do is:
$f->get_sequences();
If you apply this to all levels of your hash you will find that your code becomes easier to manage. Good luck!
Try this:
for(my $i = 0; $i < #$featureGroup; $i++){
if(defined $featureGroup->[$i]->{'features'}){
for(my $j = 0; $j<scalar #{$featureGroup->[$i]->{'features'}} ; $j++){
for(my $k=0;$k<scalar #{$featureGroup->[$i]->{'features'}->[$j]->{'featuredesc'}};$k++) {
if (defined $featureGroup->[$i]->{'features'}->[$j]->{'featuredesc'}->[$k]->{'name'}) {
print $featureGroup->[$i]->{'features'}->[$j]->{'featuredesc'}->[$k]->{'name'}."\n";
}
}
last if !defined $featureGroup->[$i+1]->{'features'};
}
}
}
Instead of iterated by index, I'd advise that you iterate by element.
This enables one to easily filter each step using grep or next
for my $group (grep {$_->{features}} #$featureGroup) {
for my $feature (grep {$_->{featuredesc}} #{$group->{features}}) {
for my $desc (#{$feature->{featuredesc}}) {
print "$desc->{name}\n"
}
}
}
Outputs:
dpx-ppd
error
$hash = { 'Man' => 'Bill',
'Woman' => 'Mary,
'Dog' => 'Ben'
};
What exactly do Perl's “anonymous hashes” do?
It is a reference to a hash that can be stored in a scalar variable. It is exactly like a regular hash, except that the curly brackets {...} creates a reference to a hash.
Note the usage of different parentheses in these examples:
%hash = ( foo => "bar" ); # regular hash
$hash = { foo => "bar" }; # reference to anonymous (unnamed) hash
$href = \%hash; # reference to named hash %hash
This is useful to be able to do, if you for example want to pass a hash as an argument to a subroutine:
foo(\%hash, $arg1, $arg2);
sub foo {
my ($hash, #args) = #_;
...
}
And it is a way to create a multilevel hash:
my %hash = ( foo => { bar => "baz" } ); # $hash{foo}{bar} is now "baz"
You use an anonymous hash when you need reference to a hash and a named hash is inconvenient or unnecessary. For instance, if you wanted to pass a hash to a subroutine, you could write
my %hash = (a => 1, b => 2);
mysub(\%hash);
but if there is no need to access the hash through its name %hash you could equivalently write
mysub( {a => 1, b => 2} );
This comes in handy wherever you need a reference to a hash, and particularly when you are building nested data structures. Instead of
my %person1 = ( age => 34, position => 'captain' );
my %person2 = ( age => 28, position => 'boatswain' );
my %person3 = ( age => 18, position => 'cabin boy' );
my %crew = (
bill => \%person1,
ben => \%person2,
weed => \%person3,
);
you can write just
my %crew = (
bill => { age => 34, position => 'captain' },
ben => { age => 28, position => 'boatswain' },
weed => { age => 18, position => 'cabin boy' },
);
and to add a member,
$crew{jess} = { age => 4, position => "ship's cat" };
is a lot neater than
my %newperson = ( age => 4, position => "ship's cat" );
$crew{jess} = \%newperson;
and of course, even if a hash is created with a name, if its reference is passed elsewhere then there may be no way of using that original name, so it must be treated as anonymous. For instance in
my $crew_member = $crew{bill}
$crew_member is now effectively a reference to an anonymous hash, regardless of how the data was originally constructed. Even if the data is (in some scope) still accessible as %person1 there is no general way of knowing that, and the data can be accessed only by its reference.
It's quite simple. They allow you to write
push #hashes, { ... };
f(config => { ... });
instead of
my %hash = ( ... );
push #hashes, \%hash;
my %config = ( ... );
f(config => \%config);
(If you want to know the purpose of references, that's another story entirely.)
Anything "anonymous" is a data structure that used in a way where it does not get a name.
Your question has confused everyone else on this page, because your example shows you giving a name to the hash you created, thus it is no longer anonymous.
For example - if you have a subroutine and you want to return a hash, you could write this code:-
return {'hello'=>123};
since it has no name there - it is anonymous. Read on to unwind the extra confusion other people have added on this page by introducing references, which are not the same thing.
This is another anonymous hash (an empty one):
{}
This is an anonymous hash with something in it:
{'foo'=>123}
This is an anonymous (empty) array:
[]
This is an anonymous array with something in it:
['foo',123]
Most of the time when people use these things, they are really trying to magically put them inside of other data structures, without the bother of giving them a waste-of-time temporary name when they do this.
For example - you might want to have a hash in the middle of an array!
#array=(1,2,{foo=>3});
that array has 3 elements - the last element is a hash! ($array[2]->{foo} is 3)
perl -e '#array=(1,2,{foo=>1});use Data::Dumper;print Data::Dumper->Dump([\#array],["\#array"]);'
$#array = [
1,
2,
{
'foo' => 1
}
];
Sometimes you want to don't want to pass around an entire data structure, instead, you just want to use a pointer or reference to the data structure. In perl, you can do this by adding a "\" in front of a variable;
%hashcopy=%myhash; # this duplicates the hash
$myhash{test}=2; # does not affect %hashcopy
$hashpointer=\%myhash; # this gives us a different way to access the same hash
$hashpointer->{test}=2;# changes %myhash
$$hashpointer{test}=2; # identical to above (notice double $$)
If you're crazy, you can even have references to anonymous hashes:
perl -e 'print [],\[],{},\{}'
ARRAY(0x10eed48)REF(0x110b7a8)HASH(0x10eee38)REF(0x110b808)
and sometimes perl is clever enough to know you really meant reference, even when you didn't specifically say so, like my first "return" example:
perl -e 'sub tst{ return {foo=>bar}; }; $var=&tst();use Data::Dumper;print Data::Dumper->Dump([\$var],["\$var"]);'
$var = \{
'foo' => 'bar'
};
or:-
perl -e 'sub tst{ return {foo=>bar}; }; $var=&tst(); print "$$var{foo}\n$var->{foo}\n"'
bar
bar
I am new to Perl, and can't find the answer to the question in the Learning Perl book.
For example I have a array like:
my #loop=("op1_sel","op2_sel");
and two hash table as:
my %op1_sel=(
"bibuf","000",
"self","101"
);
my %op2_sel=(
"zero","1",
"temp","0"
);
Now I want to use variables in the loop to loop for the hash table for a particular key
for example:
foreach(#loop)
{
print ${$_}{"bibuf"} ;
}
But it seems not working, I know the ${$_} part is wrong, can anyone can tell me how
to fix this ?
Use nested hashes. Like this:
my %op;
# put a hash reference into hash, twice
$op{op1_sel} = \%op1_sel;
$op{op2_sel} = \%op2_sel;
# later ...
foreach (keys %op) {
print "bibuf of $_: $op{$_}->{bibuf}\n";
};
Or, long story short, just
my %op = (
op1_sel => {
foo => 1,
bar => 2,
# ...
},
op2_sel => {
# ...
},
};
The {} construct creates a reference to anonymous hash and is the standard way of handling nested data structures.
See also perldoc perldsc.
You can't refer to lexical (my) variables using the ${$foo} syntax. You could probably make it work if they were package variables, but this would not be the right way to go about it.
The right way to do it is using a nested data structure.
I can see two obvious ways of doing it. You could either make an array of op_sel containing the inner hashes directly, or create a hash of hashes, and then index into that.
So "array of hashes":
my #op_sels = (
{
bibuf => '000',
self => '101',
},
{
zero => '1',
temp => '0',
},
);
for my $op (#op_sels) {
print $$op{bibuf};
}
and "hash of hashes":
my %op_sels = (
1 => {
bibuf => '000',
self => '101',
},
2 => {
zero => '1',
temp => '0',
},
);
for my $op_key (sort keys %op_sels) {
print $op_sels{$op_key}{bibuf};
}
You can use eval for this.
foreach(#loop)
{
eval "\%var = \%$_";
print $var{"bibuf"} ;
}
I have a perl hash of hashes like the following:
$VAR1 = {
'ID_1' => {
'FILE_B' => '/path/to/file/file1',
'FILE_C' => '/path/to/file/file2',
'FILE_A' => '/path/to/file/file3'
},
'ID_2' => {
'FILE_B' => '/path/to/file/file4',
'FILE_A' => '/path/to/file/file5'
},
'ID_3' => {
'FILE_B' => '/path/to/file/file6',
'FILE_A' => '/path/to/file/file7'
}
...
}
I would like to get a list of all keys of members in the main hash that have FILE_C defined. In the example, this will return only ID_1.
I know how to do this in a cumbersome loop (iterating all keys, checking if FILE_C is defined, if so — pushing the key to an array, finally returning this array), but I have a feeling there's a single-liner or even a function for this …
Yep, perl has the grep function:
my #keys = grep { defined $hash{$_}{FILE_C} } keys %hash;