m1ndgames
m1ndgames

Reputation: 37

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

Upvotes: 1

Views: 86

Answers (2)

Borodin
Borodin

Reputation: 126722

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

Upvotes: 1

zdim
zdim

Reputation: 66883

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.

Upvotes: 3

Related Questions