AlexAMC
AlexAMC

Reputation: 352

In Perl, how can find the smallest value in a list and keeping the index corresponding to that value?

I am looking to distinguish the difference between side_i and side_j to find the smallest value ... following on... to then multiply the smallest and largest by a given number.

That's pretty simple to do. However, I want to still be able to distinguish between i and j in such a way that I know which one is which after they have been multiplied.

This is what I have so far. Please note , I am a very new user of Perl and general coding:

use strict; 
use Getopt::Long;
use List::Util qw(max);
use File::Find;
use warnings 'all';



my $side_i = 24.56; 
my $side_j = 3.56; 
my $maxi = 10;
my $maxj = 10; 
my $threshold = 0.05; 
my $small_side;



(my $sside, my $bside) = smallestTest($side_i, $side_j);


sub smallestTest{

    my $sside;
    my $bside;
    print "$_[0] $_[1] /n";
    if ($_[0]<$_[1]){
        $sside = $_[0];
        $bside = $_[1];
    } else {
        $sside = $_[1];
        $bside = $_[0];
    }
return($sside, $bside);
}
print "Biggest side is $bside /n";

my $newSide_i = $.....*20;
my $newSide_j = $.....*21;

Upvotes: 0

Views: 873

Answers (2)

Sinan &#220;n&#252;r
Sinan &#220;n&#252;r

Reputation: 118148

In math, what you are referring to are called argmax and argmin. You can get their functionality in various ways.

First, given a list of values, you can find their maximum and minimum, and then select the indices which correspond to elements of the list with the maximum and minimum values, respectively. This is implemented in the using_only_max_min function below. Note that this method makes two passes over the array for each extremum: Once to find the extremum and another time to find the corresponding indices.

Or, given a list of values, you can find the indices which corresponding to the extrema in the list, and then select the corresponding value for any of them to get the value of the extremum. This is implemented in the using_by_functions routine below.

#!/usr/bin/env perl

use strict;
use warnings;

use Data::Dumper;
use List::AllUtils qw(max min max_by min_by);

my @n = (1, 3, 5, 8, 8, 3, 2, 4, 4, 6);

print Dumper using_only_max_min(\@n);
print Dumper using_by_functions(\@n);

sub using_only_max_min {
    my $n = shift;

    my $max = max @$n;
    my @argmax = grep $n->[$_] == $max, 0 .. $#$n;

    my $min = min @$n;
    my @argmin = grep $n->[$_] == $min, 0 .. $#$n;

    return {
        max => $max,
        argmax => \@argmax,
        min => $min,
        argmin => \@argmin,
    };
}

sub using_by_functions {
    my $n = shift;

    my @argmax = max_by { $n->[$_] } 0 .. $#$n;
    my $max = $n->[$argmax[0]];

    my @argmin = min_by { $n->[$_] } 0 .. $#$n;
    my $min = $n->[$argmin[0]];

    return {
        max => $max,
        argmax => \@argmax,
        min => $min,
        argmin => \@argmin,
    };
}

Output:

$VAR1 = {
          'argmin' => [
                        0
                      ],
          'max' => 8,
          'min' => 1,
          'argmax' => [
                        3,
                        4
                      ]
        };
$VAR1 = {
          'argmax' => [
                        3,
                        4
                      ],
          'min' => 1,
          'max' => 8,
          'argmin' => [
                        0
                      ]
        };

You can also do this without using any libraries, and find the two extrema in one go. The function below returns a hash reference which contains two entries, max and min. Each of those point to a list whose first value is the value of the extremum, and the second value is the list of indices corresponding to the extremum:

#!/usr/bin/env perl

use strict;
use warnings;

use Data::Dumper;

my @n = (1, 3, 5, 8, 8, 3, 2, 4, 4, 6);

print Dumper find_extrema(\@n, sub { $_[0] <=> $_[1]});

sub find_extrema {
    my ($n, $cmp) = @_;

    my ($max, $min) = ([$n->[0], [0]], [$n->[0], [0]]);

    for my $i (1 .. $#$n) {
        my $v = $n->[$i];

        my $r = $cmp->($v, $max->[0]);
        if ($r >= 0) {
            $r ? $max = [$v, [$i]] : push @{ $max->[-1] }, $i;
            next;
        }

        my $s = $cmp->($v, $min->[0]);
        if ($s <= 0) {
            $s ? $min = [$v, [$i]] : push @{ $min->[-1] }, $i;
        }
    }

    return {
        max => $max,
        min => $min,
    };
}

Output:

$VAR1 = {
          'min' => [
                     1,
                     [
                       0
                     ]
                   ],
          'max' => [
                     8,
                     [
                       3,
                       4
                     ]
                   ]
        };

Upvotes: 1

Borodin
Borodin

Reputation: 126742

I suggest that you use a hash %side to contain the values. Then the $sside and $bside variables can hold the identities i and j of the smallest and biggest values

You don't explain what you want to do after you've determined which is which, so my multiplications at the end are probably way off

use strict; 
use warnings 'all';

my %side = ( i => 24.56, j => 3.56 );

my ($sside, $bside) = $side{i} < $side{j} ? qw/ i j / : qw/ j i /;

$side{$sside} *= 20;
$side{$bside} *= 21;

If you prefer, you could use sort instead to order the hash keys. The result is identical

my ($sside, $bside) = sort { $side{$a} <=> $side{$b} } keys %side;

Upvotes: 4

Related Questions