Как я могу сделать целочисленное деление в Perl ИЛИ Как заставить работать мой двоичный поиск?

Я пытаюсь реализовать бинарный поиск. Это мой код:

#!/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;
    }   
}

Но это не работает, хотя это прямая реализация (по крайней мере, это было бы на Java).
Кажется, что я получаю значения с плавающей запятой при делении и не могу искать. Если я предоставлю в качестве ввода 5, я получу мусор:

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  

Как я могу использовать целые числа, чтобы я мог индексировать массив?
Также, если я раскомментирую use strict, я получу следующие ошибки. Что они имеют в виду?

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 

person Cratylus    schedule 10.04.2013    source источник


Ответы (6)


Вы должны использовать функцию int.

Кроме того, вам нужно use strict; и использовать my для определения области ваших переменных. Это позволит выявить ошибки, которые вы можете пропустить. Просто объявите их так:

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

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

Вы также можете рассмотреть возможность использования chomp, чтобы удалить символы новой строки из ввода.

person squiguy    schedule 10.04.2013
comment
Итак, my отсутствует, это причина, по которой это не работает на strict, верно? - person Cratylus; 11.04.2013
comment
@Cratylus Да, именно поэтому он жалуется, что требует явного имени пакета. Однако научитесь любить это, поскольку strict действительно выявляет ошибки, которые вы могли пропустить. - person squiguy; 11.04.2013
comment
На самом деле int - это не оператор приведения, это функция. Поскольку в Perl нет строгих типов переменных, нет необходимости в операторе приведения. - person TrueY; 11.04.2013

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

Отпечатки 2.

person kjprice    schedule 10.04.2013
comment
Это приведение к целому числу? - person Cratylus; 11.04.2013
comment
И что такое my? Мне это нужно? Это причина ошибок при использовании strict? - person Cratylus; 11.04.2013
comment
perldoc.perl.org/strict.html и perldoc.perl.org/functions/my.html По сути, рекомендуется использовать строгие и правильно локализованные переменные с my. Тебе это нужно? Нет. Если вы его используете, да. - person kjprice; 11.04.2013
comment
@Cratylus - вам уже рассказали, что такое my сегодня, в другом сообщении - person chrsblck; 11.04.2013
comment
@chrsblck: Я читал про Perl последние пару дней, я еще не понял большей части, поэтому прошу еще раз, чтобы получить его - person Cratylus; 11.04.2013
comment
@Cratylus - тогда прочтите, на что вы ссылаетесь. Не нужно задавать один и тот же вопрос снова и снова. - person chrsblck; 11.04.2013

Два эзотерических решения. На самом деле не используйте их, если вам действительно не скучно или что-то в этом роде.

  1. use integer - прагма, которая заставляет Perl обрабатывать все ваши числовые значения как целые числа. Его можно ограничить локально, чтобы не испортить все данные в вашей программе.

    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 имеет несколько специальных переменных $=, _ 4_ и $%, которые будут содержать только целые значения, независимо от того, что вы им присваиваете. Используйте их напрямую

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

    или как промежуточные звенья

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

    Использование таких магических переменных удобно для кодового гольфа и раздражает ваших коллег, но не более того.

person mob    schedule 11.04.2013
comment
Почему Perl не обрабатывает числовое значение как целое число при использовании в качестве индекса массива? Я имею в виду, что в моем OP я предполагаю, что напечатанный 48.5 не используется как 48 для индексации массива, и по этой причине двоичный поиск не работает - person Cratylus; 11.04.2013
comment
Оно делает. $array[48.5] вернет то же значение, что и $array[48]. - person mob; 11.04.2013
comment
Так почему же код работает, только если я использую int() вместо mid? - person Cratylus; 11.04.2013
comment
Потому что в противном случае $high и $low могут содержать значения с плавающей запятой, и ваше условие остановки $high >= $low никогда не будет выполнено. - person mob; 11.04.2013

Используйте старый добрый >> 1 вместо /2.

Пример:

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

Вывод:

2.5 : 2

И используйте (оставьте вычитание):

my $mid = ($low + $high) >> 1;
person TrueY    schedule 10.04.2013
comment
Это знаковое деление? Есть ли символ беззнакового деления? (как >>> в Java) - person Cratylus; 11.04.2013
comment
@Cratylus: Нет. Вы можете использовать int -5/2 версию. - person TrueY; 11.04.2013

С вашим кодом довольно много проблем.

  • Вы отключили strict.

    Предположительно из-за очередной проблемы.

  • Вы не объявили ни одну из своих переменных с помощью _1 _ или our или даже _ 3_.

    our @array = 1..100;
    use vars qw'$mid $low $high';
    my $number = <STDIN>
    
  • Вы слишком беспокоитесь о переполнении. Это не C.

    Если ваше число будет переполнять целое число, оно просто превратится в число с плавающей запятой.

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

    Вероятно, просто должно быть:

    my $mid = ($high + $low)/2;
    
  • Вы ожидали целого числа от деления.

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

    Если вам действительно нужно целое число, просто используйте int.

    my $mid = int( ($high + $low)/2 );
    
  • Вы не удалили новую строку с конца $number

    chomp($number);
    
  • У вас вылет на одну ошибку.

    while($low < $high){
    

    Действительно должно быть

    while($low <= $high){
    

    Это действительно твоя главная проблема.

#! /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;
    }
}

Хотя это не совсем Perlish.

#!/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;

Или более причудливо

#! /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;

Это работает с числами вплоть до UV max или (2 ** 72) - 1.
Это 18 446 744 073 709 551 615 для 64-битных сборок и 4 294 967 295 для 32-битных сборок.

person Brad Gilbert    schedule 11.04.2013
comment
+1. Я не понял этого If your number would overflow an integer, it just becomes a float., и примеры Perl для меня совершенно непонятны (новичок в Perl) - person Cratylus; 11.04.2013
comment
@Cratylus Первый пример Perlish просто перебирает список, возвращая индекс, где он становится истинным. Второй странный пример Perl использует возможности Perl по обработке строк в стиле UTF8. (На самом деле это не очень Perlish) - person Brad Gilbert; 24.04.2013
comment
Я мог / должен был написать my $char = join '', map chr, @array; как _ 2_. - person Brad Gilbert; 24.04.2013
comment
first_index { $_ == $number } это встроенная функция? - person Cratylus; 24.04.2013
comment
@Cratylus first_index - это написано для использования аналогично grep. - person Brad Gilbert; 24.04.2013
comment
Вы имеете в виду, что first_index - это еще один существующий метод? - person Cratylus; 24.04.2013
comment
@Cratylus first_index - это подпрограмма, которую кто-то написал, она не является частью Perl. (перейди по ссылке) - person Brad Gilbert; 25.04.2013
comment
Ой, извините. Не понял, что есть ссылка! Спасибо! - person Cratylus; 25.04.2013

Прежде всего,

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

можно упростить до

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

Вы можете получить неотъемлемую часть, используя int

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

Или вы можете воспользоваться тем фактом, что битовый сдвиг - это целочисленное деление на 2.

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

См. Также: Гибкая функция двоичного поиска

person ikegami    schedule 10.04.2013
comment
$low+($high-$low)/2 - это идиома из C, которая обрабатывает случай, когда результат $low+$high больше, чем наибольшее значение int. Вероятно, это менее полезно в Perl. - person mob; 11.04.2013
comment
@mob, ага, это только проблема, если у вас может быть массив, у которого количество элементов больше половины адресного пространства. В Perl этого не произойдет, поскольку для каждого элемента требуется как минимум sizeof (void *) байтов. Даже если вы проигнорируете это, у вас никогда не возникнет проблем с 32-битной сборкой Perl, поскольку 2 53 или 2 64 (диапазон чисел) намного больше, чем 2 ** 33 (2 * диапазон индекса ). У вас могут быть проблемы в 64-битных сборках, если у вас есть массив с ~ 10 000 000 000 000 000 000 элементов или более (что, как я уже сказал, не может произойти в Perl). - person ikegami; 11.04.2013
comment
@ikegami: Я думаю, что этот ответ и ваш комментарий неверны. Я использовал low + (high - low)/2, чтобы избежать переполнения. Я считаю, что это должно быть возможно в Perl, поскольку мы можем индексировать массив только с помощью int индексов, которые подписаны и имеют 2^32 - 1 адресные элементы. Поэтому я не совсем понимаю, что вы имеете в виду про number of elements is more half of the addressable space etc в своем комментарии - person Cratylus; 11.04.2013
comment
@ikegami: Также по поводу ($high + $low) >> 1;, это разделение со знаком или без знака? - person Cratylus; 11.04.2013
comment
@Cratylus, В настоящее время без подписи. - person ikegami; 11.04.2013