Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

Binary Search - revisited

by GrandFather (Saint)
on Nov 15, 2006 at 21:48 UTC ( [id://584306]=CUFP: print w/replies, xml ) Need Help??

In Binary search I posted a binary search snippet. Having recently revisited the code I've rolled in the cmp suggestion zby made, handle empty lists and generally pimped the code.

This is the updated code. Note that undef is returned for an empty list.

use strict; use warnings; my @array = (1, 3, 5, 8, 10); print "\n", join ", ", @array, "\n"; print "Found 0 at " . BinSearch (0, \&cmpFunc, \@array) . "\n"; print "Found 1 at " . BinSearch (1, \&cmpFunc, \@array) . "\n"; print "Found 2 at " . BinSearch (2, \&cmpFunc, \@array) . "\n"; print "Found 5 at " . BinSearch (5, \&cmpFunc, \@array) . "\n"; print "Found 8 at " . BinSearch (8, \&cmpFunc, \@array) . "\n"; print "Found 10 at " . BinSearch (10, \&cmpFunc, \@array) . "\n"; print "Found 11 at " . BinSearch (11, \&cmpFunc, \@array) . "\n"; @array = (1, 3, 5, 8,); print "\n", join ", ", @array, "\n"; print "Found 0 at " . BinSearch (0, \&cmpFunc, \@array) . "\n"; print "Found 1 at " . BinSearch (1, \&cmpFunc, \@array) . "\n"; print "Found 2 at " . BinSearch (2, \&cmpFunc, \@array) . "\n"; print "Found 5 at " . BinSearch (5, \&cmpFunc, \@array) . "\n"; print "Found 8 at " . BinSearch (8, \&cmpFunc, \@array) . "\n"; print "Found 9 at " . BinSearch (9, \&cmpFunc, \@array) . "\n"; @array = (1,); print "\n", join ", ", @array, "\n"; print "Found 0 at " . BinSearch (0, \&cmpFunc, \@array) . "\n"; print "Found 1 at " . BinSearch (1, \&cmpFunc, \@array) . "\n"; print "Found 2 at " . BinSearch (2, \&cmpFunc, \@array) . "\n"; @array = (); print "\n", join ", ", @array, "\n"; print "Found 0 at " . BinSearch (0, \&cmpFunc, \@array) . "\n"; sub cmpFunc { $_[0] <=> $_[1]; } -------------- 8< -------------- 1, 3, 5, 8, 10, Use of uninitialized value in concatenation (.) or string at C:\Docume +nts and Settings\Peter.WINDOMAIN\My Documents\PerlMonks\junk\noname.p +l line 31. Found 0 at -0.5 Found 1 at 0 Found 2 at 0.5 Found 5 at 2 Found 8 at 3 Found 10 at 4 Found 11 at 4.5 1, 3, 5, 8, Found 0 at -0.5 Found 1 at 0 Found 2 at 0.5 Found 5 at 2 Found 8 at 3 Found 9 at 3.5 1, Found 0 at -0.5 Found 1 at 0 Found 2 at 0.5 Found 0 at
sub BinSearch { my ($target, $cmp, $array) = @_; my $posmin = 0; my $posmax = $#$array; return undef if ! @array; return -0.5 if $cmp->($array->[0], $target) > 0; return $#$array + 0.5 if $cmp->($array->[-1], $target) < 0; while (1) { my $mid = int (($posmin + $posmax) / 2); my $result = $cmp->($array->[$mid], $target); if ($result < 0) { $posmin = $posmax, next if $mid == $posmin && $posmax != $ +posmin; return $mid + 0.5 if $mid == $posmin; $posmin = $mid; } elsif ($result > 0) { $posmax = $posmin, next if $mid == $posmax && $posmax != $ +posmin; return $mid - 0.5 if $mid == $posmax; $posmax = $mid; } else { return $mid; } } }

Replies are listed 'Best First'.
Re: Binary Search - revisited
by grinder (Bishop) on Nov 15, 2006 at 22:35 UTC

    This contains a bug that was mentioned on a Google programmer's blog some time back. You add posmin and posmax together, possibly inducing overflow or promotion to floating point values.

    You should of course take the difference of posmin from posmax, divide it by 2, and add the result to posmin.

    This came up on perl5-porters mailing list some time ago: the core was also vulnerable to this error. See the summary on use.perl for the background on this issue.

    • another intruder with the mooring in the heart of the Perl

      If an SV is more than 2 bytes long (which it is), this is not actually a bug. You'll run out of addressable memory before falling off your array. Really, this "every binary search ever has a bug" meme is silly.
Re: Binary Search - revisited
by ikegami (Patriarch) on Nov 15, 2006 at 23:06 UTC

    I use a version that returns the 1s complement of where the element should be inserted when the element is not found.

    Furthermore, the elements to compare are in $a and $b, just like they are in sort compare functions.

    Finally, the prototype I specified allows the function to be called using the sort's block syntax.

    Example:

    # Add $value to sorted @array, if it's not already there. $idx = binsearch { $a <=> $b } $value, @array; splice(@array, ~$idx, 0, $value) if ($idx < 0);

    Code:

    sub _unsigned_to_signed { return unpack('i', pack('I', $_[0])); } sub binsearch(&$\@) { my ($compare, $value, $array) = @_; # # If the $value is not found in @array, # the 1s complement of where the $value # should be inserted is returned. So, # $value is found if the return value >= 0. # # When compare is called, $a is an alias for $value, # and $b is an alias for the element of the array # against which $a which be compared. # # Example: # # Add $value to sorted @array, if it's not already there. # $idx = binsearch { $a <=> $b } $value, @array; # splice(@array, ~$idx, 0, $value) if ($idx < 0); # my $i = 0; my $j = $#$array; return $j if ($j == -1); my $ap = do { no strict 'refs'; \*{caller().'::a'} }; my $bp = do { no strict 'refs'; \*{caller().'::b'} }; local *$ap; local *$bp; *$ap = \$value; for (;;) { my $k = int(($j-$i)/2) + $i; *$bp = \($array->[$k]); my $cmp = &$compare(); return $k if ($cmp == 0); if ($cmp < 0) { $j = $k-1; return _unsigned_to_signed(~$k) if ($i > $j); } else { $i = $k+1; return _unsigned_to_signed(~$i) if ($i > $j); } } }

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: CUFP [id://584306]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (5)
As of 2024-04-23 17:56 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found