http://www.perlmonks.org?node_id=989068

jjap has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks,

I made this script to find where a value is along an array. This code is pretty explicit and works as expected, but I am looking for advice on improving its efficiency or elegance before implementing it further.

use strict; use warnings; use List::Util qw(max min); my @array = (11, 23, 40, 52, 67); my ($minarray, $maxarray); my $lookup = $ARGV[0]; my ($prev, $curr); $minarray = min(@array); $maxarray = max(@array); foreach my $i (@array){ if ($lookup < $minarray or $lookup > $maxarray){ print "$lookup is out of bounds\n"; last; } if ($i == $lookup) { print "Exact match $i\n"; last; } unless ($prev) { $prev = $i; next; } if ($i < $lookup) { $prev = $i; next; } else { $curr = $i; } if ($curr > $lookup) { print "Value $lookup, is between $prev and $curr\n"; last; } } # >perl lookupArray.pl 27 # Value 27, is between 23 and 40
Thanks and best regards

Update: Minor edit to correct as per BillKSmith's observation.
And many thanks to all for the nice variants and improvements offered. Cheers!

Replies are listed 'Best First'.
Re: Improve my lookup script
by GlitchMr (Sexton) on Aug 22, 2012 at 15:14 UTC

    You can use CPAN module, such as List::BinarySearch instead of writing code yourself.

    Also, instead of using min and max you could use $array[0] and $array[-1] - the list is sorted.

    Also, as far I can see, you check if value is out of bounds every time loop passes. You can move this check outside loop.

      Indeed all very good points!

      Thanks for pointing me to List::BinarySearch.

       bsearch_num_pos($lookup, @array) gives me a grip to handle further calculations.
Re: Improve my lookup script
by Athanasius (Archbishop) on Aug 22, 2012 at 16:07 UTC

    Hello jjap,

    There is nothing wrong with your approach, but here is an alternative which uses no modules:

    #! perl use strict; use warnings; @ARGV == 1 or die "\nUsage: perl lookupArray.pl <integer>\n"; my $lookup = $ARGV[0]; my @array = sort { $a <=> $b } (67, 40, 11, 23, 52); my $prev = ( grep { $_ <= $lookup } @array )[-1]; my $next = ( grep { $_ >= $lookup } @array )[ 0]; printf "\nValue %d is %s\n", $lookup, ($prev && $next) ? "between $prev and $next" : $prev ? "above $prev" : "below $next";

    Thanks to GlitchMr for the hint about getting min and max via array indices [0] and [-1]. I’ve really just taken this idea to its logical conclusion.

    I make no claims for the efficiency of this approach, but it does have the advantages of compactness and (I think) of clarity, as well as some minimum sanity checking.

    Maybe looking at the task from this different angle will be useful for you? Hope it helps.

    Update: Fixed sort as per Re^2: Improve my lookup script by ++GlitchMr, below.

    Athanasius <°(((><contra mundum

      Just a small note, sort is lexical sort. While it doesn't matter in this case (every number has this same length), you probably meant sort { $a <=> $b }.

      Very neat approach! Of both grep and conditional operator. Many thanks!
Re: Improve my lookup script
by Kenosis (Priest) on Aug 22, 2012 at 18:35 UTC

    I think you did a nice job on your script(!), and you've received great comments about it. I'm not too sure if the following addresses either efficiency or elegance, but it's another option:

    use Modern::Perl; my @array = ( 0, 11, 23, 40, 52, 67 ); my $find = 27; my ( $before, $after ) = findValAlongArray( $find, @array ); if ( defined $before and $before == $after ) { say "Exact match $find."; } elsif ( defined $before ) { say "$find is between $before and $after."; } else { say "$find was not found along the array."; } sub findValAlongArray { my ( $find, @array ) = @_; return $find, $find if $find ~~ @array; @array = sort { $a <=> $b } @array; for ( my $i = 0 ; $i < $#array ; $i++ ) { return $array[$i], $array[ $i + 1 ] if $array[$i] < $find and $array[ $i + 1 ] > $find; } undef, undef; }

    Output:

    27 is between 23 and 40.

    Hope this helps!

    Update: Added exact matching using smart match.

Re: Improve my lookup script
by BillKSmith (Monsignor) on Aug 22, 2012 at 20:05 UTC

    Print $lookup rather than $i for "out of bounds" condiltion.

    Bill