1

I'm trying to implement binary search. This is my code:

#!/usr/bin/perl
#use strict;
use warnings;

@array = (1..100);
$number = <STDIN>;
$low = 0;
$high = $#array;

while($low < $high){
    print "Searcing $low ---- $high \n";
    $mid = $low + ($high - $low)/2;
    if($array[$mid] == $number){
        print "Found in index:" . $mid;
        last;
    }
    elsif($array[$mid] < $number){
        $low = $mid + 1;
    }
    else{
        $high = $mid - 1;
    }   
}

But it does not work although it is a straight forward implementation (at least it would be in Java).
It seems that I get float values when dividing and can not search. If I provide as input 5 I get garbage:

5  
Searcing 0 ---- 99  
Searcing 0 ---- 48.5  
Searcing 0 ---- 23.25  
Searcing 0 ---- 10.625  
Searcing 0 ---- 4.3125  
Searcing 3.15625 ---- 4.3125  

How can I make it use integer numbers so I can index the array?
Also if I uncomment use strict I get the following errors. What do they mean?

Global symbol "@array" requires explicit package name at D:\Development\Perl\chapter3\binarySearch.pl line 6.  
Global symbol "$number" requires explicit package name at D:\Development\Perl\chapter3\binarySearch.pl line 9.  
Global symbol "$low" requires explicit package name at 
chrsblck
  • 3,948
  • 2
  • 17
  • 20
Cratylus
  • 52,998
  • 69
  • 209
  • 339

6 Answers6

6
my $int = int 5 / 2;
print $int;

Prints 2.

kjprice
  • 1,316
  • 10
  • 26
  • Is that a cast to integer? – Cratylus Apr 10 '13 at 20:39
  • Also what is `my`?Do I need it? Is this the reason for the errors on using `strict`? – Cratylus Apr 10 '13 at 20:41
  • http://perldoc.perl.org/strict.html and http://perldoc.perl.org/functions/my.html Basically, it's good practice to use strict and properly localize variables with my. Do you need it? No. Should you use it, yes. – kjprice Apr 10 '13 at 20:41
  • @Cratylus - you've already been told what `my` is today in another [post](http://stackoverflow.com/questions/15933254/how-do-you-reference-a-list-in-perl) – chrsblck Apr 10 '13 at 20:43
  • @chrsblck:I been reading about `Perl` for the last couple of days.I don't get most of it yet, so I ask again to get it – Cratylus Apr 10 '13 at 20:45
  • @Cratylus - read what you're linked to then. No need to ask the same question over and over. – chrsblck Apr 10 '13 at 20:46
3

Use the good old >> 1 instead of /2.

Example:

perl -e 'BEGIN { print 5/2, " : ", 5>>1}'

Output:

2.5 : 2

And use (spare an substraction):

my $mid = ($low + $high) >> 1;
TrueY
  • 7,360
  • 1
  • 41
  • 46
3

Two esoteric solutions. Don't actually use these unless you are really bored or something.

  1. use integer -- a pragma that forces Perl to treat all your numerical values as integers. It can be locally scoped so it won't ruin all the data in your program

    while($low < $high){
        print "Searcing $low ---- $high \n";
        {
            use integer;
            $mid = $low + ($high - $low)/2;
        }
        if($array[$mid] == $number){
            print "Found in index:" . $mid;
            last;
        }
        elsif($array[$mid] < $number){
            $low = $mid + 1;
        }
        else{
            $high = $mid - 1;
        }   
    }
    
  2. Perl has some special variables $=, $-, and $% that will only hold integer values, no matter what you assign to them. Use them directly

    $- = $low + ($high - $low) / 2;
    if ($array[$-] == $number) { ...
    

    or as intermediates

    my $mid = $- = $low + ($high - $low) / 2;
    if ($array[$mid] == $number) { ...
    

    Using the magic variables like this is handy for code golf and irritating your co-workers, but not much else.

Community
  • 1
  • 1
mob
  • 117,087
  • 18
  • 149
  • 283
  • Why doesn't `Perl` treat a numerical value as an integer when used as an array index?I mean in my OP I assume that the `48.5` printed is not used as `48` to index the array and that is the reason the binary search does not work – Cratylus Apr 11 '13 at 07:48
  • 1
    It does. `$array[48.5]` will return the same value as `$array[48]`. – mob Apr 11 '13 at 13:53
  • So why does the code works only if I use `int()` for the `mid`? – Cratylus Apr 11 '13 at 19:34
  • 1
    Because otherwise `$high` and `$low` might contain floating point values, and your stopping condition `$high >= $low` will never be satisfied. – mob Apr 11 '13 at 19:41
2

You should be using the function int.

Aside from that, you need to use strict; and use my to scope your variables. This will catch errors that you might miss. Just declare them like this:

my @array = (1..100);
chomp(my $number = <STDIN>);
my $low = 0;
my $high = $#array;

my $mid = int ($low + ($high - $low)/2);

You might consider using chomp too, to remove newlines from your input.

squiguy
  • 32,370
  • 6
  • 56
  • 63
  • So `my` missing is the reason this does not run on `strict` right? – Cratylus Apr 10 '13 at 20:45
  • 1
    @Cratylus Yes, this is why it complains about it requiring an explicit package name. Learn to love it though, as `strict` really catches errors that you might overlook. – squiguy Apr 10 '13 at 20:46
  • 1
    Actually int is not a casting operator, it is a function. As in perl there is no strict types of a variable, there is no need for a cast operator. – TrueY Apr 11 '13 at 08:11
2

There are quite a few problems with your code.

  • You disabled strict.

    Presumably because of the next problem.

  • You didn't declare any of your variables, with my or our or even use vars.

    our @array = 1..100;
    use vars qw'$mid $low $high';
    my $number = <STDIN>
    
  • You worry too much about overflows. This isn't C.

    If your number would overflow an integer, it just becomes a float.

    my $mid = $low + ($high - $low)/2;
    

    Should probably just be:

    my $mid = ($high + $low)/2;
    
  • You expected an integer out of a division.

    my $mid = ($high + $low)/2;
    

    If you really want an integer just use int.

    my $mid = int( ($high + $low)/2 );
    
  • You didn't remove the newline from the end of $number

    chomp($number);
    
  • You have an off by one error.

    while($low < $high){
    

    Should really be

    while($low <= $high){
    

    This is really your main problem.

#! /usr/bin/env perl
use strict;
use warnings;

my @array = 1..100;
my $number = <STDIN>;
chomp $number;
my $low = 0;
my $high = $#array;

while($low <= $high){
    my $mid = int( ($high + $low)/2 );
    printf "Searching %2d .. (%2d) .. %2d\n", $low, $mid, $high;
    if($array[$mid] == $number){
        print "Found $number at index mid $mid\n";
        last;
    }elsif($array[$mid] < $number){
        $low = $mid + 1;
    }else{
        $high = $mid - 1;
    }
}

That's not really Perlish though.

#!/usr/bin/perl
use strict;
use warnings;
use List::MoreUtils qw'first_index';

my @array = 1..100;
my $number = <STDIN>;
chomp $number;

my $index = first_index { $_ == $number } @array;
print "Found $number at index ", $index if $index != -1;

Or more bizarrely

#! /usr/bin/env perl
use strict;
use warnings;

my @array = 1..100;
my $number = <STDIN>;
chomp $number;

my $char = join '', map chr, @array;

my $index = index $char, chr $number;
print "Found $number at index $index\n" if $index != -1;

This works with numbers up-to the lesser of UV max or (2 ** 72) - 1.
That is 18,446,744,073,709,551,615 on 64-bit builds, and 4,294,967,295 on 32-bit builds.

Brad Gilbert
  • 33,846
  • 11
  • 78
  • 129
  • +1.I didn't get this `If your number would overflow an integer, it just becomes a float.` and the perlish examples are completely incomprehensible to me (a Perl newbie) – Cratylus Apr 11 '13 at 19:35
  • @Cratylus The first Perlish example just iterates over the list returning the index where it becomes true. The second bizarre Perl example takes advantage of the UTF8-style string handling capabilities of Perl. ( It's not actually very Perlish ) – Brad Gilbert Apr 24 '13 at 16:12
  • I could/should have written [`my $char = join '', map chr, @array;`](http://perldoc.perl.org/functions/chr.html "perldoc -f chr") as [`my $char = pack 'U*', @array;`](http://perldoc.perl.org/functions/pack.html "perldoc -f pack"). – Brad Gilbert Apr 24 '13 at 16:18
  • `first_index { $_ == $number }` is this an inline function? – Cratylus Apr 24 '13 at 19:45
  • @Cratylus [`first_index`](https://metacpan.org/module/List::MoreUtils#first_index-BLOCK-LIST "perldoc List::MoreUtils") is written to be used similar to [`grep`](http://perldoc.perl.org/functions/grep.html "perldoc -f grep"). – Brad Gilbert Apr 24 '13 at 19:57
  • You mean that `first_index` is another existing method? – Cratylus Apr 24 '13 at 19:58
  • @Cratylus [`first_index`](https://metacpan.org/module/List::MoreUtils#first_index-BLOCK-LIST "perldoc List::MoreUtils") is a subroutine that someone wrote, it is **not** part of Perl. (follow the link) – Brad Gilbert Apr 24 '13 at 20:09
  • Oh sorry.Did not realize there was a link! Thank you! – Cratylus Apr 24 '13 at 20:10
1

First of all,

my $mid = $low + ($high - $low)/2

can be simplified to

my $mid = ($high + $low)/2;

You can get the integral part using int

my $mid = int( ($high + $low)/2 );

Or you could take advantage of the fact that a bit shift is an integer division by 2.

my $mid = ($high + $low) >> 1;

See also: Flexible Binary Search Function

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • 1
    `$low+($high-$low)/2` is an idiom from C that handles the case where the result of `$low+$high` is larger than the largest int value. It's probably less useful in Perl. – mob Apr 11 '13 at 04:40
  • @mob, Yeah, it's only a problem if you can have an array whose number of elements is more half of the addressable space. That's not going to happen in Perl since at each element requires at least sizeof(void*) bytes. Even if you ignore that, you'd never have a problem with a 32-bit Perl build since 2**53 or 2**64 (number range) is far more than 2**33 (2 * index range). You could possibly have a problem in 64-bit builds if you have an array with ~10,000,000,000,000,000,000 elements or more (which, like I said, can't happen in Perl). – ikegami Apr 11 '13 at 05:01
  • @ikegami:I think this answer and your comment is incorrect.I used the `low + (high - low)/2` to avoid overflow.I believe this should be possible to happen in `Perl` as we can index an array via `int` indexes only which are signed and have `2^32 - 1` addressable elements. So I am not sure what you mean about `number of elements is more half of the addressable space etc` in your comment – Cratylus Apr 11 '13 at 07:53
  • @ikegami:Also concerning `($high + $low) >> 1;` is this signed or unsigned division? – Cratylus Apr 11 '13 at 07:53
  • @Cratylus, Currently, unsigned. – ikegami Apr 11 '13 at 14:21