How to iterate on a conditional array in Perl? - arrays

I iterate through files in Perl and I'd like to get the correct "Adresse" field in the file. "Adresse" is a hash. Either the file contains only one "Adresse", and I take it, or it contains several "Adresse" and "Adresse" is actually an Array containing several "Adresse", and I just need the one having "type" = "postale".
Here is my code:
my $ad;
my $adresse;
if(ref($doc->{'Organisme'}->{'Adresse'}) eq 'ARRAY') {
print "\nI'M AN ARRAY!\n";
foreach $ad ($doc->{'Organisme'}->{'Adresse'}) {
print Dumper $ad;
if ($ad->{'type'} == 'postale') {
my $adresse = $ad;
}
}
} else {
my $adresse = $doc->{'Organisme'}->{'Adresse'}
}
print $fd $adresse->{'Ligne'};
I get the error:
Not a HASH reference at ./scripts/actualiserDonnees.pl line 35
and line 35 is:
if ($ad->{'type'} == 'postale') {
Apparently the "foreach" doens't iterate through "$doc->{'Organisme'}->{'Adresse'}" when the latter is an array, because the Dumper gives me this:
$VAR1 = [
{
'Localisation' => {
"Pr\x{e9}cision" => '8',
'Longitude' => '1.9751304',
'Latitude' => '43.2279035'
},
'type' => 'physique',
'CodePostal' => '11270',
"Accessibilit\x{e9}" => {
'type' => 'ACC'
},
'NomCommune' => 'Laurac',
'Ligne' => 'Place Blanche-de-Laurac'
},
{
'Ligne' => '8 rue du Pont',
'CodePostal' => '11270',
'type' => 'postale',
'NomCommune' => 'Laurac'
}
];
If I didn't explain myself enough, feel free to ask questions.
Thanks in advance :)

my $adresse creates a new variable. Replace both instances of my $adresse = ... with $adresse = ...
$doc->{'Organisme'}->{'Adresse'} is a scalar (a reference to an array), so foreach $ad ($doc->{'Organisme'}->{'Adresse'}) only loops over one item (the reference to an array). You want foreach $ad (#{ $doc->{'Organisme'}->{'Adresse'} })

Related

Convert array to multidimensional hash

My task is convert array, containing hash with x keys to x-1 dimensional hash.
Example:
use Data::Dumper;
my $arr = [
{
'source' => 'source1',
'group' => 'group1',
'param' => 'prm1',
'value' => 1,
},
{
'source' => 'source1',
'group' => 'group1',
'param' => 'prm2',
'value' => 2,
},
];
my $res;
for my $i (#$arr) {
$res->{ $i->{source} } = {};
$res->{ $i->{source} }{ $i->{group} } = {};
$res->{ $i->{source} }{ $i->{group} }{ $i->{param} } = $i->{value};
}
warn Dumper $res;
my $res_expected = {
'source1' => {
'group1' => {
'prm1' => 1, # wasn't added, why ?
'prm2' => 2
}
}
};
However it doesn't work as expected, 'prm1' => 1 wasn't added. What is wrong and how to solve this task ?
The problem is that you are assigning to the source even if something was there, and you lose it. Just do a ||= instead of = and you'll be fine.
Or even easier, just use the fact that Perl autovivifies and leave that out.
my $res;
for my $i (#$arr) {
$res->{ $i->{source} }{ $i->{group} }{ $i->{param} } = $i->{value};
}
warn Dumper $res;
The first 2 lines in the for loop are what is causing your problem. They assign a new hash reference each iteration of the loop (and erase what was entered in the previous iteration). In perl, there is no need to set a reference as you did. Just eliminate the first 2 lines and your data structure will be as you wish.
The method you chose only shows 'prmt' => 2 because that was the last item entered.

Iterating over an array of hashes inside of a hash

I am new to Perl and I am having a hard time with iterating over an array of hashes inside of a hash. I tried this solutionHere is the data structure from Dumper:
1234 => {
'items' => [
#0 {
'k1' => { 'x1' => '123', 'x2' => '321' },
'k2' => 'v2',
'k3' => 'v3',
'k4' => 'v4'
},
#1 {
'k1' => { 'x1' => '123', 'x2' => '321' },
'k2' => 'v2',
'k3' => 'v3',
'k4' => 'v4'
},
#2 {
'k1' => { 'x1' => '123', 'x2' => '321' },
'k2' => 'v2',
'k3' => 'v3',
'k4' => 'v4'
},
],
}
Here is the code I have. I want to iterate over the hash '%myHash' while having access to both the keys and values. Then for each hash inside the "items" array I want to perform some operation.
In this case, my key is '1234' and its value is a hash that contains the key 'items'. This is the code I have (a ref to the hash is being passed to my sub):
sub iterateHash {
my %myHash = #_;
while (my ($key, $value) = each %myHash) {
my %newHash;
foreach my $item (#{ $value->{'items'} }) {
if( !(defined $newHash{'nk1'}) ) {
$newHash{'nk1'} = $item{'k1'}{'x2'};
}
}
}
}
and how it is called:
my $results = iterateHash(\%myHash);
I have uses for the key later on which is why I want access to both the key and value in a pair in the outer while loop, but for now I want to get this part working.
This is the error I get:
Global symbol "%item" requires explicit package name
You passed a reference but didn't treat it like a reference inside iterateHash. Also, once you get into nested structures, everything is a reference, so $item is a hash reference not a plain hash.
sub iterateHash {
my ($myHash) = #_;
while (my ($key, $value) = each %$myHash) {
my %newHash;
foreach my $item (#{ $value->{'items'} }) {
if( !(defined $newHash{'nk1'}) ) {
$newHash{'nk1'} = $item->{'k1'}->{'x2'};
}
}
}
}

How do I append a new hash to an array of hashes?

If I wanted to add a new hash to all the arrays in the mother_hash using a loop, what would be the syntax?
My hash:
my %mother_hash = (
'daughter_hash1' => [
{
'e' => '-4.3',
'seq' => 'AGGCACC',
'end' => '97',
'start' => '81'
}
],
'daughter_hash2' => [
{
'e' => '-4.4',
'seq' => 'CAGT',
'end' => '17',
'start' => '6'
},
{
'e' => '-4.1',
'seq' => 'GTT',
'end' => '51',
'start' => '26'
},
{
'e' => '-4.1',
'seq' => 'TTG',
'end' => '53',
'start' => '28'
}
],
#...
);
If you have a hash of arrays of hashes and want to add a new hash to
the end of each of the arrays, you can do:
push #{ $_ }, \%new_hash for (values %mother_hash);
This loop iterates over the values of %mother_hash (which are array refs in this case) and setting $_ for each iteration. Then in each iteration, we push the reference to the new hash %new_hash to the end of that array.
First I would point out the daughter hashes aren't hashes but arrays of anonymous hashes. To add another daughter hash:
$mother_hash{daughter_hash3} = [ { %daughter_hash3 } ];
This creates an anonymous array that contains an anonymous hash with the contents of %daughter_hash3.
For a loop:
$mother_hash{$daughter_hash_key} = [ { %daughter_hash } ];
where $daughter_hash_key is a string contain the key for the %mother_hash and %daughter_hash is the hash to add.
To add another hash to a daughter array with key $daughter_hash_key:
push #{ $mother_hash{$daughter_hash_key} }, { %daughter_hash };
I know ti's complicated but I suggest you use Data::Dumper to dump the contents of %mother_hash each time thru the loop to see if it grows correctly.
use Data::Dumper;
print Dumper \%mother_hash;
See perldoc Data::Dumper for details..
Data::Dumper is a standard module that comes with Perl. For a list of standard modules, see perldoc perlmodlib.
mother_hash is a hash of arrays of hashes.
To add another top-level array of hashes.
%mother_hash{$key} = [ { stuff }, { stuff } ];
To add another entry to an existing array
push #{%mother_hash{'key'}} { stuff };
To add another entry to the hash in the embedded array
%{#{%mother_hash{'top_key'}}[3]}{'new_inner_key'} = value;
When confused and attempting to match up the "types" of hash / array / scalar containing a hash reference / array reference, you can use the following technique
use Data::Dumper;
$Data::Dumper::Terse = 1;
printf("mother_hash reference = %s\n", Dumper(\%mother_hash));
printf("mother_hash of key 'top_key' = %s\n", Dumper(%mother_hash{top_key}));
and so on to find your way through a large data structure and validate that you are narrowing down to the region you want to access or alter.

Extracting an array of non=sibling hash values from a nested data structure in perl

This is my data structure created by Data::Dumper->Dumper:
$VAR1 = {
'name' => 'genomic',
'class' => [
{
'reference' => [
{
'name' => 'chromosome',
'referenced-type' => 'Chromosome'
},
{
'name' => 'chromosomeLocation',
'referenced-type' => 'Location'
},
{
'name' => 'sequence',
'referenced-type' => 'Sequence'
},
{
'name' => 'sequenceOntologyTerm',
'referenced-type' => 'SOTerm'
}
],
}
],
};
(trimmed for clarity)
I would like to return a reference to an array of each name value under reference in a single line.
Currently I have
$class->[0]{reference}[0..3]{name}
but no avail.
Also this example has four sibling-hashes with indexes 0..3, how can I represent the whole array independent of the number of elements?
There isn't an easy syntax to do that, unfortunately. You'll have to use map:
my $array_ref = [
map { $_->{name} } #{ $class->[0]{reference} }
];
Then, if you dump out $array_ref, you'll see it contains:
$array_ref = [
'chromosome',
'chromosomeLocation',
'sequence',
'sequenceOntologyTerm'
];
If you need references to the original strings (not copies), you just need a backslash before $_ (so it'd be \$_->{name} inside the map).
$class->[0]{reference} is an array reference, so you have to dereference it with #{}:
#{$class->[0]{reference}}
Is the 'whole array', you can then use slice syntax on the end to get a part of it:
#{$class->[0]{reference}}[0..3]
From there you're working with an array of hashrefs, so you'll have to iterate over it with for or map.

How do I reference a Perl hash in an array in a hash?

This is the code snippet I am working with:
my %photo_details = (
'black_cat' => (
('size' => '1600x1200', 'position' => -25),
('size' => '1280x1024', 'position' => 25),
('size' => '800x600', 'position' => 0),
),
'race_car' => (
('size' => '1600x1200', 'position' => 10),
('size' => '800x600', 'position' => 5),
),
);
my $photo = 'black_cat';
foreach my $photo_detail ($photo_details{$photo})
{
my $size = $photo_detail{'size'};
my $position = $photo_detail{'position'};
print ("size = $size, position = $position\n");
}
What I am expecting to get is:
size = 1600x1200, position = -25
size = 1280x1024, position = 25
size = 800x600, position = 0
What I do get is:
Use of uninitialized value $size in concatenation (.) or string at C:\Test.pl line 23.
Use of uninitialized value $position in concatenation (.) or string at C:\Test.pl line 23.
size = , position =
The foreach statement is clearly wrong as not only are there no values for $size and $position, it has only gone through the loop once instead of three times. I have tried all sorts of variants of variable prefixes and found none that work.
What am I doing wrong?
Here is some updated code, with an explanation below:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my %photo_details = (
'black_cat' => [
{'size' => '1600x1200', 'position' => -25},
{'size' => '1280x1024', 'position' => 25},
{'size' => '800x600', 'position' => 0},
],
'race_car' => [
{'size' => '1600x1200', 'position' => 10},
{'size' => '800x600', 'position' => 5},
],
);
print Dumper( %photo_details );
foreach my $name ( keys %photo_details ) {
foreach my $photo_detail ( #{ $photo_details{$name} } ) {
my $size = $photo_detail->{'size'};
my $position = $photo_detail->{'position'};
print Dumper( $photo_details{$photo} );
print ("size = $size, position = $position\n");
}
}
I've replaced some of your parentheses with square and curly brackets. In Perl, square brackets give you a reference to an anonymous array, and curly brackets denote a reference to an anonymous hash. These are called anonymous because there's no explicit variable name for the anonymous array or hash.
As Perl data structures make you store a reference to a hash rather than the actual hash, you need these to construct the references. You can do this in two steps like this:
my #array = ( 1, 2, 3 );
my $array_ref = \#array;
my %hash = ( 'one' => 1, 'two' => 2, 'three' => 3 );
my $hash_ref = \%hash_ref;
To get data out of $array_ref and $hash_ref, you need the -> operator:
print $array_ref->[0], "\n";
print $hash_ref->{one}, "\n";
You don't need the quotes inside of the {} when referencing a hash key, although some people consider quotes on a hash key to be good practice.
I added an example of iteration over the entire data structure as an example rather than just looking at one reference. Here's the first line:
foreach my $name ( keys %photo_details ) {
The keys method returns all of the keys in a hash, so that you can get them in order. The next line iterates over all of the photo_detail hashrefs in %photo_details:
foreach my $photo_detail ( #{ $photo_details{$photo} } ) {
The #{ $photo_details{$photo} } de-references the reference $photo_details{$photo} into an array, which you can iterate over it with foreach.
The last thing that I added is a call to Data::Dumper, a very useful module distributed with Perl that prints out data structures for you. This is very handy when building up data structures like this, as is its closely related cousin Data::Dumper::Simple. This module is unfortunately not distributed with Perl, but I prefer its output as it includes variable names.
For some further reading about how to build up complex data structures using references, check out perlreftut.
First of all, always start every script or module with:
use strict;
use warnings;
You will get more warning messages and sooner, which greatly helps debugging.
I cannot duplicate your error: when I put that code into a file and run it with no additional flags, I get: size = , position =. There is no $size variable in the code you printed, so the error message does not match.
Nevertheless, you are declaring your data structures incorrectly. Hashes and arrays can
only contain scalar values, not lists: so if you want to nest an array or
a hash, you need to make it a reference. See perldoc perldata, perldoc perldsc
and perldoc perlreftut for more about data structures and references.
my %photo_details = (
black_cat => [
{ size => '1600x1200', position => -25 },
{ size => '1280x1024', position => 25 },
{ size => '800x600', position => 0 },
],
race_car => [
{ size => '1600x1200', position => 10 },
{ size => '800x600', position => 5 },
],
);
foreach my $photo_detail (#{$photo_details{black_cat}})
{
my $size = $photo_detail->{size};
my $position = $photo_detail->{position};
print ("size = $size, position = $position\n");
}
There's really only one thing you have to worry about, and that's the top level of the data structure. After that, you just use the right indexing syntax for each level:
If you have a regular hash, you access the key that you want then line up the additional indices for each level after it:
%regular_hash = ...;
$regular_hash{$key}[$index]{$key2};
If you have a reference, you do almost the same thing, but you have to start off with the initial dereference with an arrow, ->, after the top-level reference. After that it's the same indexing sequence:
$hash_ref = ...;
$hash_ref->{$key}[$index]{$key2};
For all of the details, see Intermediate Perl where we explain reference syntax.

Resources