1

In this code, I'm checking if a certain key is present or not. Here I am checking if key "Uri" present. I am getting output as "3".

use strict; 
use warnings;

my %Names = ( 
    Martha  =>2, 
    Vivek   =>9, 
    Jason   =>6, 
    Socrates=>7, 
    Uri     =>3, 
    Nitin   =>1, 
    Plato   =>0, 
); 

if (exists $Names{Uri} ) {
    print "$Names{Uri}\n";
}
foreach my $name (sort {$Names{$a} cmp $Names{$b}} keys %Names) 
{ 
    print $name, $Names{$name}."\n";
} 

Output

3
Plato    0
Nitin    1
Martha   2
Uri      3
Jason    6
Socrates 7
Vivek    9

But, I want the previous key value present before that key. For example:

  1. If I search for key "Uri" Output should be "2"
  2. If I search for key "Vivek" Output should be "7"
  3. If I search for key "Plato" Output should be "0"

Does anyone know how to do it?

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
Alexx
  • 475
  • 2
  • 8
  • 1
    Can you define "previous value"? Do you mean the greatest value in the hash smaller than the one you're looking for? (in that case, see @toolic's answer) or something else? – joanis Feb 16 '21 at 21:39

2 Answers2

2

Create a sorted array of the hash values, then search through the array to get the value just lower than the value of your search key.

use strict; 
use warnings;

my %Names = ( 
    Martha  =>2, 
    Vivek   =>9, 
    Jason   =>6, 
    Socrates=>7, 
    Uri     =>3, 
    Nitin   =>1, 
    Plato   =>0, 
); 

my @vals = sort {$a <=> $b} values %Names;

get_prev('Uri');
get_prev('Vivek');
get_prev('Plato');

sub get_prev {
    my $k = shift;
    if (exists $Names{$k}) {
        for (@vals) {
            if ($Names{$k} == $vals[$_]) {
                my $idx = ($_ == 0) ? 0 : $_ - 1;
                print $vals[$idx], "\n";
                last;
            }
        }
    }
}

Prints:

2
7
0
toolic
  • 57,801
  • 17
  • 75
  • 117
1

If you want to print them all:

my $prev;
for my $name (
   sort { $Names{$a} <=> $Names{$b} }    # Note to use of <=> for numerical comparisons.
      keys(%Names)
) {
   say "$name $Names{$prev}" if $prev;
   $prev = $name;
} 

Similarly, to print just one

my $find = 'Uri';

my $prev;
for my $name (
   sort { $Names{$a} <=> $Names{$b} }
      keys(%Names)
) {
   if ($name eq $find) {
      say "$name $Names{$prev}" if $prev;
      last;
   }

   $prev = $name;
}

The above hat would be an expensive way to perform multiple lookups. For that, we'd build a mapping from names to the previous names.

my %prev_name_lkup;
my $prev;
for my $name (
   sort { $Names{$a} <=> $Names{$b} }
      keys(%Names)
) {
   $prev_name_lkup{$name} = $prev if $prev;
   $prev = $name;
} 

This could also be done as follows:

my @sorted_names =
   sort { $Names{$a} <=> $Names{$b} }
      keys(%Names);

my %prev_name_lkup =
   map { $sorted_names[$_-1] => $sorted_names[$_] }
      1..$#sorted_names;

Either way, the lookups would look like this:

say "Uri $Names{$prev_name_lkup{Uri}}";
ikegami
  • 367,544
  • 15
  • 269
  • 518