m1ndgames m1ndgames - 2 days ago 5
Perl Question

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

Answer

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;

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
Comments