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

Greetings, Monks.

After witnessing the power of array and hash slices in responses to a previous question I had posed, I became determined that my next programming task would involve both understanding and utilizing them. What I needed was a simple challenge that would enable me to familiarize myself with them. I decided that coding a simple searching algorithm using array slices would suffice. With that goal in mind, I chose to implement a binary search algorithm, as its specification seemed the easiest to code. Here is my first novice attempt:

#!/usr/bin/perl -w use strict; my @list = 1..10; print binary_search(\@list, 3); sub binary_search { my ($aref, $find) = @_; my $mid; $mid = int $#$aref / 2; return 0 if $find == $aref->[$mid]; return 1 if !$#$aref; if ($find > $aref->[$mid]) { @$aref = @$aref[++$mid..$#$aref]; binary_search($aref, $find); } else { @$aref = @$aref[0..--$mid]; binary_search($aref, $find); } }

This works as it should, though I'm positive the verbosity of the code can be greatly decreased. My question is this: why is it that when I alter this line,
@$aref = @$aref[++$mid..$#$aref];
to this:
@$aref = @$aref[++$mid..-1];

The script displays this error message: Use of uninitialized value in numeric qt (>) at BinarySort.pl line 21? I proceeded to run the script through the perl debugger and noticed that the array slice @$aref[++$mid..-1] returns nothing. Why is that? I was under the impression that -1 is the same as $#$aref in this context. Any help is appreciated.

Matt

Replies are listed 'Best First'.
Re: Binary search algorithm
by BrowserUk (Patriarch) on Apr 21, 2003 at 06:13 UTC

    -1 only becomes an alias for $#array

    at the point where its value is being used as an index into an array. So

    print $array[-1]; is ok, as is

    print @array[-1,0,3];.

    In this latter case, the -1 is part of a list of indices in the array slice.

    However, in print @array[0..-1];,

    it isn't yet part of a list, it is an operand to the .. operator that is intended to generate the list of indices, and the .. operator will only generate positive indices, so a null list is generated. Even if .. did generate negative running lists, the result wouldn't be what you wanted betcause

    print 0..-1; would result in the list (0,-1) which isn't what is intended at all.

    In order for print @array[0..-1];

    to DWIM, the complier would have to make a special case of the .. operators context within the subscript of an array which in itself would create another non-orthoganality in that

    print @array[0..-1]; would now do something different to

    @indices = 0..-1; print @array[@indices];.

    It's my guess that this is the main reason why this is done.

    Updated from here on.

    BTW. One caveat of your implementation of the binary search is that it destroys the input array as a result of the lines

    @$aref = @$aref[++$mid..$#$aref]; ... @$aref = @$aref[0..--$mid];

    Where you are overwriting the original array (via the reference) with the subsets needed by the binary chop--in this case, literally:).

    You can avoid this by creating an anonymous array from the subset when you recurse. This could look something like

    sub binary_search { my ($aref, $find) = @_; my $mid; $mid = int $#$aref / 2; return 0 if $find == $aref->[$mid]; return 1 if !$#$aref; if ($find > $aref->[$mid]) { binary_search([ @$aref[++$mid..$#$aref] ], $find); } else { binary_search([ @$aref[0..--$mid] ], $find); } }

    Or in a slightly more compact form

    sub binary_search { my ($aref, $find) = @_; my $mid = int $#$aref / 2; return 0 unless $#$aref; return 1 if $find == $aref->[$mid]; binary_search( $find < $aref->[$mid] ? [ @$aref[ 0 .. --$mid ] ] : [ @$aref[++$mid .. $#$aref] ] , $find ); }

    I also reversed the logic of the return as I would prefer to say

    if (binary_search(\@array, $needl) ) { #found it }

    to if (binary_search(\@array, $needle)){ #Didn't find it }

    but that's just a personal preference.


    Examine what is said, not who speaks.
    1) When a distinguished but elderly scientist states that something is possible, he is almost certainly right. When he states that something is impossible, he is very probably wrong.
    2) The only way of discovering the limits of the possible is to venture a little way past them into the impossible
    3) Any sufficiently advanced technology is indistinguishable from magic.
    Arthur C. Clarke.
      That makes sense. It never occured to me to iterate through the array after performing the binary search on it. I didn't realize I was destroying it. I like your solution involving an anonymous array. Very neat. Thank you for your input.

      Matt
Re: Binary search algorithm
by chromatic (Archbishop) on Apr 21, 2003 at 04:58 UTC

    -1 is a shortcut index for the final element of an array. $# is the index of the final element of the array. The difference is subtle and important.

    Besides that, ranges always increase. You can't go from 0 to -1 by adding one -- at least, not anytime soon. It's a meaningless construct.

Re: Binary search algorithm
by demerphq (Chancellor) on Apr 21, 2003 at 14:10 UTC

    Others have dealt with your direct questions, id just like to point out that binary searching an array is almost never done recursively. Theres absolutely no need. Nor do I see why array slices would be useful here at all.

    use strict; use warnings; sub bin_search { my $array = shift; my $find = shift; return unless @$array; my ($l,$r)=(0,$#$array); while ($l<=$r) { my $m=int(($l+$r)/2); if ($find<$array->[$m]) { $r=$m-1; } elsif ($find>$array->[$m]) { $l=$m+1; } else { return $m; } } return undef; } my @list=map { $_*2 } (1..5); for (0..11) { my $pos=bin_search(\@list,$_); print "Search:$_ Position:",defined $pos ? $pos : "undef","\n"; }

    HTH


    ---
    demerphq

    <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...
Re: Binary search algorithm (slicing demonstration)
by Aristotle (Chancellor) on Apr 21, 2003 at 12:18 UTC
Re: Binary search algorithm
by Anonymous Monk on Apr 21, 2003 at 06:12 UTC
    First of all note that your recopying array slices makes this algorithm take as much or more work than just walking the whole array (which doesn't need to assume sorted first). If you want a binary search to be efficient, it is much better to pass an array reference and 2 indexes.

    Secondly any time you hear the word "search", you should think to yourself, "hash lookup". Building a hash takes the same amount of work as scanning the array a fairly small number of times, and is a simple one-liner to code. Even when it isn't the most efficient approach, it is generally only off by a constant factor from the best, and doing better takes a lot of work. Become friends with the hash. Using it takes less work, is more efficient, and is more reliable.

Re: Binary search algorithm
by tall_man (Parson) on Apr 21, 2003 at 14:29 UTC
    I know you did this as a learning exercise, but there is already a module Search::Binary that does this.