Convert array to multidimensional hash - arrays

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.

Related

Why an array of array preserves my initial array?

So I have this fragment of code:
push #{$savedcallouts[-1]}, {
$funcnm => {
matches => {%$captures},
flags => [eval { #flags}]
}};
print Dumper \#{$savedcallouts[-1]};
Which gives the following result:
$VAR1 = [
[
{
'normalexpr' => undef
},
{
'normalexpr' => undef
},
{
'ternaryexpr' => undef
}
]
];
But if I remove the square brackets of flags => [eval { #flags}] (i.e. have flags => eval { #flags} - I get this:
$VAR1 = {
'begin_binary' => {
'HASH(0x1038301c0)' => {
'ternaryexpr' => undef
},
'flags' => {
'normalexpr' => undef
},
'matches' => {}
}
};
Any ideas why is this happening and how can I potentially avoid - i.e. have the array as hash field directly without artifacts or being an nested array.
First of all, eval { } is useless here. #flags isn't going to throw any exceptions.[1]
So
flags => eval { #flags }
is a weird way of writing
flags => #flags
flags => #flags
is a shorthand for
"flags", #flags
which is
"flags", $flags[0], $flags[1], $flags[2], ...
which is
"flags" => $flags[0],
$flags[1] => $flags[2],
...
have the array as hash field directly
The values of hash elements are scalars.
You can store a reference to an array in a scalar, but you can't store an array in a scalar.
Well, it's possible to add magic to #flags that throws an exception when accessed. But would you really want to ignore this exception? I can't fathom why you used eval here.

How to iterate on a conditional array in Perl?

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'} })

Compare two CSV Files with Perl

I have two CSV files that I want to compare with Perl.
I have the code to get the files into Perl using Text::CSV::Slurp and it gives me a nice array of hash references for the files.
Using Data::Dumper::Concise shows all my data imports correctly.
use strict;
use warnings;
use Text::CSV::Slurp;
use Data::Dumper::Concise;
my $file1_src = "IPB-CSV.csv";
my $file2_src = "SRM-CSV.csv";
my $IPB = Text::CSV::Slurp->load(file => $file1_src);
my $SRM = Text::CSV::Slurp->load(file => $file2_src);
print Dumper($IPB);
print Dumper($SRM);
The results of the dump look something like this
$IPB
[
{
Drawing => "1001"
},
{
Drawing => "1002"
},
{
Drawing => "1003"
}
]
$SRM
[
{
Drawing => "1001",
Figure => "Figure 2-8",
Index => 2,
Nomenclature => "Some Part"
},
{
Drawing => "1002",
Figure => "Figure 2-8",
Index => 2,
Nomenclature => "Some Part"
},
{
Drawing => "2001",
Figure => "Figure 2-8",
Index => 2,
Nomenclature => "Some Part"
},
{
Drawing => "2002",
Figure => "Figure 2-8",
Index => 2,
Nomenclature => "Some Part"
}
]
I want to compare the two arrays based on each hash's Drawing key, and create two CSV files as follows
One containing the items that are in $IPB but not $SRM, containing only the data in the `Drawing column.
Another where the item is in the $SRM but not the $IPB, containing all the fields that are related to the Drawing column.
I have found lots of information to compare files to see if they match, or to compare hashes or arrays for single pieces of data, but I can't find something specific to what I need.
This short program uses your example values for $ipb and $srm and creates the output that I think you want. (Please don't use capital letters for anything but global identifiers like package names.)
There are a couple of problems
Using Text::CSV::Slurp leaves you with two arrays of hashes that are no use for this task without further indexing. You would be much better off creating appropriate data structures from scratch by processing the file line-by-line
You say that your second file must contain all of the information related to each Drawing key, but, because Perl hashes are inherently unordered, Text::CSV::Slurp has lost the order of the field names. The best that can be done is to print the data in whatever order it is found, but preceding it by a header line showing the field names. This is another reason for avoiding Text::CSV::Slurp
use strict;
use warnings;
use autodie;
# The original data
my $ipb = [{ Drawing => 1001 }, { Drawing => 1002 }, { Drawing => 1003 }];
my $srm = [
{
Drawing => "1001",
Figure => "Figure 2-8",
Index => 2,
Nomenclature => "Some Part"
},
{
Drawing => "1002",
Figure => "Figure 2-8",
Index => 2,
Nomenclature => "Some Part"
},
{
Drawing => "2001",
Figure => "Figure 2-8",
Index => 2,
Nomenclature => "Some Part"
},
{
Drawing => "2002",
Figure => "Figure 2-8",
Index => 2,
Nomenclature => "Some Part"
}
];
# Index the data
my %srm;
for my $item (#$srm) {
my $drawing = $item->{Drawing};
$srm{$drawing} = $item;
}
my %ipb;
for my $item (#$ipb) {
my $drawing = $item->{Drawing};
$ipb{$drawing} = 1;
}
# Create the output files
open my $csv1, '>', 'file1.csv';
for my $id (sort keys %ipb) {
next if $srm{$id};
print $csv1 $id, "\n";
}
close $csv1;
open my $csv2, '>', 'file2.csv';
my #keys = keys %{ $srm->[0] };
print $csv2 join(',', #keys), "\n";
for my $id (sort keys %srm) {
next if $ipb{$id};
print $csv2 join(',', #{$srm{$id}}{#keys}), "\n";
}
close $csv2;
output
file1.csv
1003
file2.csv
Drawing,Nomenclature,Index,Figure
2001,Some Part,2,Figure 2-8
2002,Some Part,2,Figure 2-8
Since drawing is a criterion of sorts, why not "index" the data into something a little more convenient where the drawing index is the key and the corresponding data is a corresponding value?
my %ipb;
for my $record ( #$IPB ) {
my $index = $record->{Drawing};
push #{ $ipb{$index} }, $record;
}
my %srm;
for my $record ( #$SRM ) {
my $index = $record->{Drawing};
push #{ $srm{$index} }, $record;
}
Now it should be a breeze to figure out the indexes unique to $IPB and $SRM:
use List::MoreUtils 'uniq';
my #unique_ipb = uniq( grep { $ipb{$_} and not $srm{$_} } keys( %ipb ), keys( %srm ) );
my #unique_srm = uniq( grep { $srm{$_} and not $ipb{$_} } keys( %ipb ), keys( %srm ) );
What's common to both?
my #intersect = uniq( grep { $srm{$_} and $ipb{$_} } keys( %ipb ), keys( %srm ) );
What are all the figure number(s) for Drawing index 1002?
print $_->{Figure}, "\n" for #{ $ipb{1002} // [] }, #{ $srm{1002} // [] };
This is a bit complicated, because your data structures are less than ideal for comparing. You have references to arrays of hash references, and you care about the data in one of the keys of the hashref. My first step would be to flatten IPB to an array (since there is no data under this), and convert SRM to a single hashref.
my #ipbarray = map { ${$_}{Drawing} } $IPB; # Creates an array from IPB.
my $srmhash = {};
for my $hash ($SRM) {
${$srmhash}{${$hash}{Drawing}} = $hash unless defined ${$srmhash}{${$hash}{Drawing}}; # Don't overwrite if it exists
}
Now we have 2 more workable data structures.
Next step is to contrast these values:
my #ipbonly = ();
my #srmonly = ();
for my $ipbitem (#ipbarray) {
push #ipbonly, ( Drawing => $ipbitem } unless defined ${$srmhash}{$ipbtem};
}
for my $srmitem (keys $srmhash) {
push #srmonly, ${$srmhash}{$srmitem} unless grep { $_ == $srmitem } #ipbarray;
}
At this point, #ipbonly and #srmonly will contain the data you want.

How to declare AoHoAoH?

I have this output from Dumper
'group' => {
'1104' => {
'a' => 1
},
'52202' => {
'b' => 1,
'c' => 1
},
'52201' => {
'c' => 1
},
'52200' => {
'c' => 1
}
},
which I assume is an Array of Hashes of Arrays of Hashes?
I would like to declare this structure my self.
Is there a way to do this, so next time I see such a complex structure, I can do that in no time? =)
Your output is a hash of hashes of hashes, with the first hash only containing a single element. The {} mark a hash reference, so you'd repeat your data structure thus, where the resulting $hohoh is a refrence to a HoHoH.
my $hohoh = {
'group' => {
'1104' => {
'a' => 1
},
'52202' => {
'b' => 1,
'c' => 1
},
'52201' => {
'c' => 1
},
'52200' => {
'c' => 1
}
},
};
print $hohoh->{group}{1104}{a}; # -> 1
I recommend reading the Perl Datastructures Cookbook.
Since the types of variables, and of hash values, can change in Perl, there isn't any way to "declare" a three-level hash the way you're probably thinking. You can instantiate an empty hashref into each key as it's created, which is a similar idea:
# First pass
my $data = {};
# Later...
$data->{group} = {};
# Still later...
$data->{group}->{1104} = {};
# Finally...
$data->{group}->{1104}->{a} = 1;
But you could just as easily simply fill in the data as you obtain it, allowing autovivification to do its thing:
my $data;
# Fill one piece of data... Perl creates all three hash levels now.
$data->{group}->{1104}->{a} = 1;
# Fill another piece of data, this one has two values in the "bottom" hash.
$data->{group}->{52202} = { b => 1, c => 2};
But there is no way (in plain Perl) to "enforce" that the values for any particular key contain hashes rather than strings or subroutine references, which is usually what is intended by declaration in languages with C-like type systems.

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