Define array when making hash from arrayref - perl

I am not sure if I am using the correct terminology here but I am trying to loop through an array and create a hash of arrays and values.
Currently my code looks like this:
my $endResult;
my $list = $arrayRef;
my $hash;
foreach my $hash_ref ( #$list ) {
if ( substr($hash_ref->{ID_NUMBER}, 0, 3) eq 'ABC' ) {
$hash->{'ABC'}{$hash_ref->{ID_NUMBER}}->{'VEHICLES'} = $arrayRef1;
push(#$endResult, $hash);
}
... #more ID number if statementss with different id numbers
and I get an output like this:
[
{
ABC => {
ABC1234 => {
VEHICLES => [
{ X => 11, Y => 0, Z => 12 },
{ X => 2001, Y => 100000, Z => 300 },
],
},
ABC56778 => {
VEHICLES => [
{ X => 1324, Y => 0, Z => 234 },
{ X => 666, Y => 7777, Z => 555 },
],
},
...
But what I want is for ABC to point to array of hashes (ABC1234, ABC46778) instead of how it is. Let me know if I need to clarify better but I cant figure out the right syntax to make this happen when building my hash.

To get your long keys into individual array refs, you need to change the assignment of your data structure. I've added a bunch of example data.
# input data
my $vehicles = [
{
'Z' => '12',
'X' => '11',
'Y' => '0',
},
{
'Z' => '300',
'X' => '2001',
'Y' => '100000',
}
];
my $list = [ { ID_NUMBER => 'ABC1234' }, { ID_NUMBER => 'ABC56778' } ];
# output data
my $endResult;
foreach my $hash_ref (#$list) {
my $hash; # needs to be inside of the loop!
if ( substr( $hash_ref->{ID_NUMBER}, 0, 3 ) eq 'ABC' ) {
push #{ $hash->{'ABC'} }, { # this becomes the array
$hash_ref->{ID_NUMBER} => { # and everything below needs
VEHICLES => $vehicles, # to be constructed directly
}
};
push #$endResult, $hash;
}
}
This will yield the following data structure (output with Data::Printer):
\ [
[0] {
ABC [
[0] {
ABC1234 {
VEHICLES [
[0] {
X 11,
Y 0,
Z 12
},
[1] {
X 2001,
Y 100000,
Z 300
}
]
}
}
]
},
[1] {
ABC [
[0] {
ABC56778 {
VEHICLES var[0]{ABC}[0]{ABC1234}{VEHICLES}
}
}
]
}
]
Please note that your choice of variable names makes this very complicated. It's hard to read and will be hell to maintain later. Always pick variable names based on what they represent. Name them something like $vehicle, $ids or $stuff_we_need_to_take_care_off_later, but not $hash1).
1: Unless you work with a hashing algorithm and we're talking about the result. :)

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

Perl: Get minimum distance value from multi hash using List::Util

i would like to get the smallest distance to a "snaffle" from the following hash:
$VAR1 = {
'0' => {
'y' => '7012',
'snaffle' => {
'5' => {
'y' => '3856',
'x' => '875',
'id' => '5',
'distance' => 9734
},
'6' => {
'x' => '10517',
'id' => '6',
'distance' => 510,
'y' => '6741'
},
'4' => {
'y' => '5291',
'id' => '4',
'x' => '11331',
'target' => 'true',
'distance' => 2125
},
'8' => {
'x' => '11709',
'id' => '8',
'distance' => 2236,
'y' => '5475'
},
'7' => {
'distance' => 8485,
'x' => '4591',
'id' => '7',
'y' => '544'
}
},
'x' => '10084',
'distance2mybase' => 10598,
'distance2enemybase' => 6755,
'type' => 'WIZARD',
'id' => '0',
'state' => 0
},
It is filled early:
# game loop
while (1) {
chomp(my $entities = <STDIN>); # number of entities still in game
for my $i (0..$entities-1) {
chomp($tokens=<STDIN>);
my ($entity_id, $entity_type, $x, $y, $vx, $vy, $state) = split(/ /,$tokens);
my $type;
if ($entity_type eq "WIZARD") {
$type = "wizard";
}
if ($entity_type eq "OPPONENT_WIZARD") {
$type = "enemy";
}
if ($entity_type eq "SNAFFLE") {
$type = "snaffle";
}
if ($entity_type eq "BLUDGER") {
$type = "bludger";
}
$entity{$type}{$entity_id}{x} = $x;
$entity{$type}{$entity_id}{y} = $y;
$entity{$type}{$entity_id}{state} = $state;
$entity{$type}{$entity_id}{id} = $entity_id;
$entity{$type}{$entity_id}{type} = $entity_type;
$entity{$type}{$entity_id}{distance2mybase} = &getdistance($entity{$type}{$entity_id}{x},$entity{$type}{$entity_id}{y},$mybase_x,$mybase_y);
$entity{$type}{$entity_id}{distance2enemybase} = &getdistance($entity{$type}{$entity_id}{x},$entity{$type}{$entity_id}{y},$enemybase_x,$enemybase_y);
}
foreach my $wizard_id (sort keys %{ $entity{'wizard'} }) {
foreach my $snaffle_id (sort keys %{ $entity{'snaffle'} }) {
$entity{'wizard'}{$wizard_id}{snaffle}{$snaffle_id}{id} = $snaffle_id;
$entity{'wizard'}{$wizard_id}{snaffle}{$snaffle_id}{x} = $entity{'snaffle'}{$snaffle_id}{x};
$entity{'wizard'}{$wizard_id}{snaffle}{$snaffle_id}{y} = $entity{'snaffle'}{$snaffle_id}{y};
$entity{'wizard'}{$wizard_id}{snaffle}{$snaffle_id}{distance} = &getdistance($entity{'wizard'}{$wizard_id}{x},$entity{'wizard'}{$wizard_id}{y},$entity{'snaffle'}{$snaffle_id}{x},$entity{'snaffle'}{$snaffle_id}{y});
}
&action($wizard_id,"sweep","up");
}
I tried List::Util::min, but i think im searching too deep, because as you can see in the output, it targets the wrong snaffle. (6 distance is lower then 4, which is the current target)
How can i find the overall minimum distance from all snaffles? (in case you wonder, its a codingame(.com))
sub snafflecheck {
my $wizard_id = shift;
my $wizard_x = shift;
my $wizard_y = shift;
if ($entity{'snaffle'}) {
foreach my $snaffle_id (sort keys %{ $entity{'snaffle'} }) {
my $snaffle_x = $entity{'snaffle'}{$snaffle_id}{x};
my $snaffle_y = $entity{'snaffle'}{$snaffle_id}{y};
my $distance2snaffle = &getdistance($wizard_x,$wizard_y,$snaffle_x,$snaffle_y);
my $nearest = min $entity{'wizard'}{$wizard_id}{snaffle}{$snaffle_id}{distance};
if ($distance2snaffle) {
if ($distance2snaffle == $nearest) {
$entity{'wizard'}{$wizard_id}{snaffle}{$snaffle_id}{target} = "true";
return("true",$entity{'wizard'}{$wizard_id}{snaffle}{$snaffle_id}{id},$entity{'wizard'}{$wizard_id}{snaffle}{$snaffle_id}{x},$entity{'wizard'}{$wizard_id}{snaffle}{$snaffle_id}{y},$distance2snaffle);
}
}
}
}
Given the shown data, this is the list of all values for key distance
my #dist = map { $_->{distance} } values %{$entity->{0}{snaffle}};
However, getting the minimum value doesn't reveal its key.
One way of finding the key for which the value of distance is smallest
use List::Util 'reduce';
my $snaff = $entity->{0}{snaffle};
my $min_dist = reduce {
$snaff->{$a}{distance} < $snaff->{$b}{distance} ? $a : $b
} keys %$snaff;
print "Minimal distance: $snaff->{$min_dist}{distance} for key $min_dist\n";
To have more control you can instead iterate over %$snaff using each.
You can also sort the extracted $snaff by distance value, if you'd like to have them all.
You should first extract the reference to the snaffle hash to make things tidier. Then you can just use map to extract the distance field of each hash element and min to find the smallest of them.
If you want to know the snaffle with the smallest distance then
I suggest that you install List::UtilsBy and use its min_by operator
This code shows both operations
The hash is identical to your own, but expressed more compactly using Data::Dump instead
use strict;
use warnings 'all';
use feature 'say';
use List::Util 'min';
use List::UtilsBy 'min_by';
my %data = (
"0" => {
distance2enemybase => 6755,
distance2mybase => 10598,
id => 0,
snaffle => {
4 => { distance => 2125, id => 4, target => "true", x => 11331, y => 5291 },
5 => { distance => 9734, id => 5, x => 875, y => 3856 },
6 => { distance => 510, id => 6, x => 10517, y => 6741 },
7 => { distance => 8485, id => 7, x => 4591, y => 544 },
8 => { distance => 2236, id => 8, x => 11709, y => 5475 },
},
state => 0,
type => "WIZARD",
x => 10084,
y => 7012,
},
);
my $snaffles = $data{0}{snaffle};
my $min_distance = min map { $snaffles->{$_}{distance} } keys %$snaffles;
# OR
my $min_distance = min map { $_->{distance} } values %$snaffles;
my $closest_snaffle = min_by { $snaffles->{$_}{distance} } keys %$snaffles;
say "\$min_distance = $min_distance";
say "\$closest_snaffle = $closest_snaffle";
output
$min_distance = 510
$closest_snaffle = 6

How to dig into an certain hash depth?

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 } },
}

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' => {}
}
};