Все задачи

Буквы к буквам, числа к числам

26 Jul 2013

Прораммист написал скрипт для сортировки строк в соответствии с разными правилами:

  • sort.pl -i должен упорядочивать строки без учета регистра,
  • sort.pl -n рассматривает строки как числа (т.е. 23 окажется раньше, чем 123),
  • sort.pl -r переставляет строки в случайном порядке.

sort.pl (имя файла кликабельно, можно скачать исходник):

#!/usr/bin/perl

use SortRules;

my $rule = \&SortRules::default_cmp;
my $opt = $ARGV[0] && $ARGV[0] =~ /^-/ ? shift @ARGV : '';
if ($opt eq '-i'){
    $rule = \&SortRules::case_insensitive_cmp;
} elsif ($opt eq '-n'){
    $rule = \&SortRules::numerical_cmp;
} elsif ($opt eq '-r'){
    $rule = \&SortRules::random_cmp;
}

my @lines = <>;
print sort $rule @lines;

SortRules.pm (имя файла кликабельно, можно скачать исходник):

package SortRules;

sub default_cmp
{
    return $a cmp $b;
}

sub case_insensitive_cmp
{
    return lc($a) cmp lc($b);
}

sub numerical_cmp
{
    return $a <=> $b;
}

sub random_cmp
{
    return int(rand(3)) - 1;
}

1;

Однако сортировка как-то не работает.

Пример данных:

zzzz
aaaa
vvvv
1111
67
333
BBBB
QQQQ
LLLL

Подсказка

Показать

Скрипт не использует прагму warnings, ай-яй-яй!

Подсказка-2

Показать

А если бы программист ее включил, то увидел бы множество сообщений:

Use of uninitialized value $SortRules::a in string comparison (cmp) at SortRules.pm line 5, <> line ...

Подсказка-3

Показать

$SortRules::a и $main::a – это совсем разные переменные.

Разоблачение

Показать

Дело в том, что sort устанавливает для функции сравнеия глобальные пакетные переменные $a и $b. В нашем случае это $main::a и $main::b.

В то же время функции сравнения из пакета SortRules обращаются к глобальным переменным своего пакета, то есть к $SortRules::a и $SortRules::b. Неудивительно, что функция сравнения выдавала предупреждения о неопределенных значениях!

Использовать в sort сравнивающую функцию из другого пакета возможно, просто для этого функцию надо объявить с прототипом ($$).

Пакет с функциями сравнения мог бы выглядеть так:

SortRules2.pm

package SortRules2;

sub default_cmp($$)
{
    my ($a, $b) = @_;
    return $a cmp $b;
}

sub case_insensitive_cmp($$)
{
    my ($a, $b) = @_;
    return lc($a) cmp lc($b);
}

sub numerical_cmp($$)
{
    my ($a, $b) = @_;
    return $a <=> $b;
}

sub random_cmp
{
    return int(rand(3)) - 1;
}

1;

Цитата из perldoc -f sort:

If the subroutine’s prototype is ($$), the elements to be compared are passed by reference in @_, as for a normal subroutine. This is slower than unprototyped subroutines, where the elements to be compared are passed into the subroutine as the package global variables $a and $b.

Перевод:

Если у функции объявлен прототип ($$), то подлежащие сравнению элементы передаются по ссылке в массиве @_, как для обычной функции. Это медленнее, чем фукнция без прототипа, в которую элемены для сравнения передаются в глобальных пакетных переменных $a и $b.

Мораль: всегда используйте прагмы strict и warnings, обращайте внимание на предупреждения и читайте документацию (хотя бы иногда) ^_^

И еще: не пишите такой скрипт для сортировки. Все сортировки, перечисленные в условии, отлично выполняет утилита sort с ключами -f, -n, -r или вовсе без них.