How to declare AoHoAoH? - arrays

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.

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.

How To Optimize Method Merging Two Hashes [RUBY]

I have constructed a method that merges two hashes where one of their keys values are the same. The method can be seen below:
def self.hash_merge_on_location(base, additions_arr, location)
base.each_with_index { |b_hash,b_index|
additions_arr.each do |addition|
addition.each_with_index { |a_hash,a_index|
if b_hash[location] == a_hash[location]
base[b_index] = b_hash.merge(a_hash)
end
}
end
}
base
end
So my question is how can I make this method better? Right now I have base (array of hashes), additions_arr (array of array of hashes), and location (key to compare) as my parameters and I iterate over over hash in my base array and then compare this against every array of hashes in my additions_arr. I finally check to see if the current hash value in my array in my additions_arr is equal to the hash value in my base array.
An example would be as follows:
Base Array
[{"loan_number" => 10808, "spec_type" => "New"},
{"loan_number" => 10809, "spec_type" => "Old"}]
Additions Array
[
[{"loan_number" => 10808, "new_field" => 10},
{"loan_number" => 12383, "new_field" => 19}],
[{"loan_number" => 10809, "new_field" => 11}]
]
So right now with my current code with a location input of "loan_number" the output of hash_merge_on_location would be:
Output
[{"loan_number" => 10808, "spec_type" => "New", "new_field" => 10},
{"loan_number" => 10809, "spec_type" => "Old", "new_field" => 11}]
Is there anyway in which I can optimize my method as it stands currently? Or anything else I could do to make it better?

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.

How do I store an array as a value in a Perl hash?

I'm trying to create a hash in Perl, whose values are arrays. Something like:
my #array = split(/;/, '1;2');
my $hash = {'a' => #array};
Surprisingly, this reports (using Data::Dumper):
$VAR1 = {
'a' => '1',
'2' => undef
};
This page gives an example of storing an array in a hash by defining the array use square brackets, like:
my $hash = {'a' => ['1', '2']};
That works, but I'm getting my array from a call to the split method. What's magic about square brackets versus parentheses for defining an array, and how can I turn a "parentheses-array" into a 'square-brackets' array?
The values of hash (and array) elements are scalars, so you can't store an array into a hash.
The following are all equivalent:
my $hash = { a => #array };
my $hash = { 'a', #array };
my $hash = { 'a', $array[0], $array[1] };
my $hash = { a => $array[0], $array[1] => () };
A common solution is to store a reference to the array.
my #array = split(/;/, '1;2');
my $hash = { a => \#array }; # my $hash = { a => [ '1', '2' ] };
[ LIST ] similarly create an array, assigns LIST to it, then returns a reference to the array.

Perl: Get parallel value in hash array

I have this:
my(%arr) = (
monsters => ["Test","Test2"],
kills => [-1, -2 ]);
Then later I search for Test2:
if ( grep { $_ eq "Test2"} #{ $arr{monsters} } )
{
#Get parallel value of Test2 (-2)
next;
}
How can I get the parallel value without knowing the index (an actual variable is used when searching and not a string literal)?
Rather than using a grep, just loop over the array and keep a count variable:
for my $idx( 0 .. $#{ $arr{monsters} } ) {
if ( $arr{monsters}[$idx] eq 'Test2' ) {
print "Kills = $arr{kills}[$idx]\n";
last;
}
}
A better way to handle this, however, might be to rethink your data structure. Instead of parallel arrays, consider an array of hashes:
my #monsters = ( { name => 'Test', kills => -1 }, { name => 'Test2', kills => -2 } );
Now, to find a specific monster:
my ( $monst ) = grep { $_->{name} eq 'Test2' } #monsters;
print $monst->{kills};
This would allow you to search by name and kills equally easily. If you are going to always search by name, then making a hash keyed on name and pointing to the number of kills (as #dmah suggests) might be better.
An even better way to handle this would be to wrap up your monsters in a class, and have each object keep track of its own kills, but I'll leave that as an exercise for the OP.
Try a hash of hashes:
my %arr = (
'Test' => {
'kills' => -1,
},
'Test2' => {
'kills' => -2,
},
);
print $arr{'Test2'}{'kills'}, "\n";

Resources