How to dig into an certain hash depth? - perl

I have a hash where I don't know its depth. I got it with DBI::selectall_hashref where the second parameter is given by the user.
So depending on the query I can have something like this for a 2-levels hash.
hash_ref = (
aphrodite => (
foo => (
name => aphrodite,
foobar => foo
a => 1,
b => 2,
)
bar => (
name => aphrodite,
foobar => bar
a => 1,
b => 2,
)
)
apollo => (
...
)
ares => (
...
)
)
As you can see the key columns are redundant into the hash. I would like to remove the redundant keys.
If I know that this is a 2-levels hash I can easily solve my problem with this:
for my $name (keys $hash_ref) {
for my $foobar (keys $hash_ref->{$name}) {
my $h = $hash_ref->{$name}{$foobar};
delete $h->{name};
delete $h->{foobar};
}
}
However with a 3-levels hash I will need 3 cascaded for-loop and so on.
How can I dynamically remove the redundant keys from $hash_ref i.e. name and foobar?
My initial idea was to recursively iterate trough my hash:
iterate($hash_ref, scalar #keys);
sub iterate {
my ($ref, $depth) = #_;
for(keys $ref) {
if ($depth > 0) {
iterate($ref->{$_}, $depth - 1);
}
else {
delete $ref->{$_} for(#keys);
}
}
}
It works but It's ugly, very ugly... Before going any further I would like to know if I missed something. Perhaps the solution could be much simpler that I think.
Any ideas?
More details?
I am writing a database fetcher that takes a user configuration that contains the SQL query $sql and the hash keys #keys. So I get the values from the database with:
$dbh->selecthall_hashref($sql, \#keys, {}, #bind);
I also have to clean fetched data according to additional. Do apply these rules, I have to iterate into the deepest level of $hash_ref to access the keys/values.

I think this does what you need. Essentially it recurses through the hash until it finds a layer where the hash values aren't references. Then it removes the elements from that layer with the keys in #keys
use strict;
use warnings;
use 5.010;
use Data::Dump;
use List::Util 'any';
my $hash_ref = {
aphrodite => {
bar => { name => "aphrodite", foobar => "bar", a => 3, b => 4, },
foo => { name => "aphrodite", foobar => "foo", a => 1, b => 2, },
},
apollo => {
bar => { name => "apollo", foobar => "bar", a => 7, b => 8, },
foo => { name => "apollo", foobar => "foo", a => 5, b => 6, },
},
ares => {
bar => { name => "ares", foobar => "bar", a => 11, b => 12, },
foo => { name => "ares", foobar => "foo", a => 9, b => 10, },
},
};
my #keys = qw/ name foobar /;
remove_dups($hash_ref, \#keys);
dd $hash_ref;
sub remove_dups {
my ($href, $keys) = #_;
if ( any { ref } values %$href ) {
remove_dups($_, $keys) for values %$href;
}
else {
delete #{$href}{#$keys};
}
}
output
{
aphrodite => { bar => { a => 3, b => 4 }, foo => { a => 1, b => 2 } },
apollo => { bar => { a => 7, b => 8 }, foo => { a => 5, b => 6 } },
ares => { bar => { a => 11, b => 12 }, foo => { a => 9, b => 10 } },
}

Related

how to create reference to a value of a subhash member in Perl?

I would like to use reference to make the code shorter
I make it simple, only one level of depth here :
my %cx = ( 'a' => ( "A" => 7, "B" => 8), 'b' => ( "Z" => 20 ));
# I want a ref to the B's value for testing, with a possible increment action :
my $ref = \$cx{a}{B}; # so I just put a \ before
if ($$ref and $$ref < 10) { $$ref ++; } # will give $cx{a}{B} = 9
# I have the same need for inner references, for example :
my $ref = \$stock{$stockName}->{places}->{$otherHashRef->{andItsKey}}
But this doesn't work
Parens don't contruct anything; they just change precedence. So,
my %cx = ( 'a' => ( "A" => 7, "B" => 8), 'b' => ( "Z" => 20 ));
is just a weird way of writing
my %cx = ( 'a' => "A", 7 => "B", 8 => 'b', "Z" => 20 );
Curlies create a hash and returns a reference to a hash, so you want
my %cx = ( 'a' => { "A" => 7, "B" => 8 }, 'b' => { "Z" => 20 });
For example,
$ perl -MData::Dumper -e'
{
my %cx = ( 'a' => { "A" => 7, "B" => 8 }, 'b' => { "Z" => 20 });
my $ref = \$cx{a}{B};
++$$ref;
print(Dumper(\%cx));
}
{
my %stock;
my $stockName = "abc";
my $otherHashRef = { andItsKey => 'def' };
my $ref = \$stock{$stockName}->{places}->{$otherHashRef->{andItsKey}};
++$$ref;
print(Dumper(\%stock));
}
'
$VAR1 = {
'a' => {
'A' => 7,
'B' => 9
},
'b' => {
'Z' => 20
}
};
$VAR1 = {
'abc' => {
'places' => {
'def' => 1
}
}
};

Declare hash variable in loop

I need to use a hash and loop in my code. Please see the sample code it's not working. i wanted to print the variable wafer, site and res side by side so it will look like this
1, 1, 63
1, 2, -53
1, 3, 9.47
1, 4, 9.55
1, 5, -8.32
my #wafer = ("1","1","1","1","1");
my #site = ("1", "2", "3", "4", "5");
my #res = ("63","-53","9.47","9.55","-8.32");
my %hash;
foreach my $result(#res) {
$hash{$wafer[0]}{$site[0]} = $result;
last;
}
print "$wafer{$wafer[0]}{$site[0]} \n";
When you want to iterate several arrays synchronously, iterate over the indices:
for my $index (0 .. $#wafer) {
print "$wafer[$index] $site[$index] $res[$index]\n";
}
You also might want to build a hash keyed by the site (as it's the only unique value):
for my $index (0 .. $#wafer) {
$hash{ $site[$index] } = { wafer => $wafer[$index],
res => $res[$index] };
}
This will create a hash like this:
%hash = (
'4' => {
'res' => '9.55',
'wafer' => '1'
},
'3' => {
'wafer' => '1',
'res' => '9.47'
},
'1' => {
'res' => '63',
'wafer' => '1'
},
'2' => {
'res' => '-53',
'wafer' => '1'
},
'5' => {
'res' => '-8.32',
'wafer' => '1'
}
);

Can't use string as a HASH reference

Here's the structure that I'm trying to access
Dumper $resourceAudit
$VAR1 = '{
\'rh6\' => {
\'h\' => 1,
\'n\' => 1
},
\'win2k8\' => {
\'h\' => 1,
\'n\' => 1
},
\'win2k12\' => {
\'h\' => 3,
\'n\' => 3
},
\'win2k3\' => {
\'h\' => 0,
\'n\' => 1
},
\'usim\' => {
\'h\' => 4,
\'n\' => 4
}
}';
So, I know that $resourceAudit is actually a string and so, %$resourceAudit is sure to give me the Can't use string as a HASH reference error.
Is there any way I can get around this and access the 'rh6' key?
$resourceAudit doesn't contain a reference to a hash; it contains a string. That string is Perl code that would return a reference to a hash when executed. You can use eval EXPR to run Perl code.
my $data = eval($serialized_data)
or die("Error executing audit code: $#");
... %$data ...

perl populating hash of hashes recursively

I have a hash like this:
{ ABC => [1, 2],
1 => [11, 12,13,14],
13 => [17,20] }
I want to generate a hash of hashes like this:
(ABC => { 1 => {11 => {},
12 => {},
13 => { 17 => {}
20 = {} },
14 => {}
},
2 => {}
}
)
The above hash is nothing but a tree with a root node and further child nodes.
I understand we have to use recursion to check child nodes for every parent node. I have looked at the question previously asked here. I am unable to understand how during recursion specific node's data is stored under its particular parent key. In other words how can hash of hashes be populated recursively ?
Appreciate any pointers or explanation.
Thanks for your time
The real problem is that you don't really know what you want.
{ABC => 1 => 11 => {}
=> 1 => 12 => {}
=> 1 => 13 => 17 => {}
=> 20 = {}
=> 1 => 14 => {}
}
is just a really weird way of writing
{
ABC => "1",
11 => {},
1 => "12",
{} => "1",
13 => "17",
{} => "20",
{} => "1",
14 => {},
}
That makes no sense. I think you actually want
{
ABC => {
1 => {
11 => {},
12 => {},
13 => {
17 => {},
20 => {},
},
14 => {},
},
},
}
Now that you know what you want, you should take a stab at implementing it.
You can use the code I defined here: How can I merge several hashes into one hash in Perl?
having defined #hash_list so:
my #hash_list
= { map { ref() eq 'ARRAY' ? { map {; $_ => {} } #$_ } : $_ }
%{{ ABC => [1, 2]
, 1 => [11, 12,13,14]
, 13 => [17,20]
}}
};
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
my %data = (
ABC => [1, 2],
1 => [11, 12, 13, 14],
13 => [17, 20]
);
my %hash;
sub modify_hash {
my ($base, $ref) = #_;
for my $k (keys %$ref) {
if (exists $base->{$k}) {
$ref->{$k} = $base->{$k};
delete $base->{$k};
}
modify_hash($base, $ref->{$k});
}
}
map { %{$hash{$_}} = map { $_ => {}; } #{$data{$_}}; } keys %data;
map { modify_hash(\%hash, $hash{$_}); } keys %hash;
print Dumper(\%hash);
output:
$VAR1 = {
'ABC' => {
'1' => {
'11' => {},
'13' => {
'17' => {},
'20' => {}
},
'12' => {},
'14' => {}
},
'2' => {}
}
};

Is this the cleanest way to extract an AoH subset in Perl?

Out of curiosity, is there another way to extract a subset of my AoH structure? The AoH is 'rectangular' (i.e. guaranteed to have the same keys across all hashrefs).
The use of a temp var and nested maps seems a bit too much for what is essentially a fancy hash slice:
use strict;
use warnings;
use Data::Dump 'dump';
my $AoH = [ # There are many more keys in the real structure
{ a => "0.08", b => "0.10", c => "0.25" },
{ a => "0.67", b => "0.85", c => "0.47" },
{ a => "0.06", b => "0.57", c => "0.84" },
{ a => "0.15", b => "0.67", c => "0.90" },
{ a => "1.00", b => "0.36", c => "0.85" },
{ a => "0.61", b => "0.19", c => "0.70" },
{ a => "0.50", b => "0.27", c => "0.33" },
{ a => "0.06", b => "0.69", c => "0.12" },
{ a => "0.83", b => "0.27", c => "0.15" },
{ a => "0.74", b => "0.25", c => "0.36" },
];
# I just want the 'a's and 'b's
my #wantedKeys = qw/ a b /; # Could have multiple unwanted keys in reality
my $a_b_only = [
map { my $row = $_;
+{
map { $_ => $row->{$_} } #wantedKeys
}
}
#$AoH
];
dump $a_b_only; # No 'c's here
This does it with one map and an arbitrary list of keys:
my #wantedKeys = qw/a b/;
my $wanted = [
map { my %h; #h{#wantedKeys} = #{ $_ }{#wantedKeys}; \%h } #$AoH
];
(With a little help from this post)
If you do not need $AoH anymore, you can use the destructive way:
delete $_->{c} for #$AoH;
You want delete.
my $foo = [ map { delete $_->{c}; $_ } #$AoH ];
If you want to preserve the original data, then you would need to dereference the hashes first.
my $foo = [ map { my %hash = %$_; delete $hash{c}; \%hash; } #$AoH ];
This is my solution (let me introduce the nice Data::Printer module):
use Modern::Perl;
use Data::Printer { colored => 1 };
my $AoH = [
{ a => "0.08", b => "0.10", c => "0.25" },
{ a => "0.67", b => "0.85", c => "0.47" },
{ a => "0.06", b => "0.57", c => "0.84" },
{ a => "0.15", b => "0.67", c => "0.90" },
{ a => "1.00", b => "0.36", c => "0.85" },
{ a => "0.61", b => "0.19", c => "0.70" },
{ a => "0.50", b => "0.27", c => "0.33" },
{ a => "0.06", b => "0.69", c => "0.12" },
{ a => "0.83", b => "0.27", c => "0.15" },
{ a => "0.74", b => "0.25", c => "0.36" },
];
# I just want the 'a's and 'b's, so I build a new hash with the keys I want
my #ab = map { {a=>$_->{a}, b=>$_->{b}} } #$AoH;
p #ab;
# If you don't mind remove the "c" key in your original structure:
# map { delete $_->{c} } #$AoH;
# and $AoH is an array of hashes without the "c" key.