InfoCity
InfoCity - виртуальный город компьютерной документации
Реклама на сайте







Размещение сквозной ссылки

 

PERL - Полезные советы


Содержание




Иногда бывает необходимо преобразовать массив чего-либо в хэш-массив. Это можно сделать так:


        %hash = map { $_, 1 } @array;

Например:


        %hash = map { $_, 1 } qw(a b c);

Этот оператор преобразует массив ('a', 'b', 'c') в хэш-массив ('a', 1', 'b', 1, 'c', 1).

Предположим, мы имеем http log примерно следующего содержания


        gwa.fr.bosch.de - - [08/Jan/1998:01:50:42 -0700] "GET /ack.html HTTP/1.0" 200 6798
        fwigka.admin.ch - - [08/Jan/1998:01:53:21 -0700] "GET /toc.html HTTP/1.0" 200 10002
и нам хочется сделать хэш-массив, состоящий из первых слова каждой строки, а значением элемента массива - количество повторений этого слова

        @host{/^(\S+)/}++ while <>;
Этот же фрагмент кода можно переписать и в более пространном виде

        while (<>) {          # Считываем одну строку в переменную $_
                my ($addr) = /^(\S+)/;  # Получаем первое слово строки
                $host{$addr}++;         # Увеличиваем счетчик повторений слова
        }
Вот еще подобный фрагмент кода

        %host = map {/^(\S+)/, 1} <>;

Но в этом случае содержимое всего файла считывается в память (<> в контексте списка) перед тем, как продолжить вычисления. Это, конечно, неудобно в случае файла большого размера.

Инверсия хэш-массива один-к-одному
Предположим, мы имеем следующий хэш-массив, содержащий информацию о соответствии IP адресов и символьных имен


        %num_to_host = (
                '123.234.1.1' => 'george',
                '123.234.1.2' => 'jane',
                '123.234.1.3' => 'judy'
        );

Таким образом, используя IP адрес в качестве индекса мы можем получить его символьное имя. А как сделать наоборот?


        %host_to_num = reverse %num_to_host;
В случае хэш-массива с отношением один-к-одному мы получим хэш-массив с обратным соответствием, т.е. сможем определять IP адрес имея его символьное имя.

        ('george' => '123.123.1.1',
        'judy' => '123.234.1.3',
        'jane' => '123.234.1.2')

Проверка: если ли в файле обе искомые строки.

Нам необходимо определить, если ли в файле обе строки текста 'george' and 'judy'?


        my ($s1, $s2);
        while (<>) {
                exit 0 if ($s1 ||= /george/) & ($s2 ||= /judy/);
        }
        die "not found\n";

Соль этого фрагмента в использовании операторов ||= (ИЛИ-присвоение) и & (побитовый И). Программа считывает файл по строкам в переменную $_ оператором <>. Как только встретится строка 'george', переменной $s1 будет присвоено значение 1 (истина). Не забывайте, что оператор $s1 ||= /george/ означает то же, что и $s1 = $s1 || /george/ -- как только $s1 примет значение ИСТИНА, программа больше не будет делать проверку на строку /george/. $s2 ||= /judy/ работает подобным образом. Программа закончит свое выполнение как только обе переменные $s1 и $s2 получат значение ИСТИНА.

Интересный момент заключается в использовании оператора побитовое И (&) вместо логического И (&&).
Левый и правый аргументы оператора & всегда выполняются в отличие от &&. Этот код просто не будет работать, если использовать оператор && и файл содержит строку 'judy' перед строкой 'george'. Конечно, нельзя всегда заменять оператор && на &, но в данном случае это необходимо.

Проверка: если ли в файле все необходимые нам слова.


        die "usage: multi string1,string2,string3 [file1 file2 ...]\n"
                unless @ARGV;
         
        my @match;
        for (split /,/, shift) {
                my $regex = "\Q$_\E"; # escape regex chars
                push @match, eval 'sub { $_[0] =~ /$regex/o }';
        }
        my $line;
        while (defined($line = <>)) {
                my @left_to_match;
                for (@match) {
                        push @left_to_match, $_ unless $_->($line);
                }
                exit 0 unless @left_to_match;
                @match = @left_to_match;
        }
        die "not found\n";

Это совершенно другой подход. Мы начинает с того, что создаем анонимные функции, каждая из которых возвращает ИСТИНА, когда ее аргумент совпадает с одним из искомых слов. Ссылки на функции хранятся в массиве @match. Затем, для каждой строки входного файла, мы запускаем по циклу все функции. Если функция не нашла совпадения (искомое слово остутствует) мы сохраняем ссылку на эту функцию в другом массиве @left_to_match - они будут работать над следующими строками исходного файла. Когда все функции сработают, в массиве @left_to_match ничего не останется и программа завершится. В противном случае будет выдано диагностическое сообщение.

Попробуем отсортировать по возрастанию числа от 1 до 10. sort 1..10 дает нам результ ('1', '10', '2', '3', '4', '5', '6', '7', '8', '9'). Немного не то... Сортировка сработала как расстановка по алфавиту.
Проблему можно решить с помощью оператора <=>.


        @sorted_num = sort { $a <=> $b } 1..10;  # То, что мы ожидали
         
        # другой вариант этого же кода
        sub numerically { $a <=> $b }
        @sorted_num = sort numerically 1..10; 

По умолчанию функция сортировки sort выполняет расстановку по алфавиту (сортировка в контексте символьных строк). Таким образом '10' и '100' появятся перед '2' и '3'. Чтобы изменить способ сортировки в данном случае мы применили собственный оператор сравнения двух переменных (блок сортировки).

Сортировка одного массива в соответсвии с содержимым другого массива.
Нам надо отсортировать два "параллельных" массива (списка). Например массив @page состоит из номеров страниц, а @note состоит из примечаний к этим страницам, т.е. $note[$i] - это примечание к странице $page[$i]. Нам хочется напечатать оба массива, отсортировав их по номерам страниц.


        @page = qw(24 75 41 9);
        @note = qw(p.24-text p.75-text p.41-text p.9-text);
        for (sort { $note[$a] <=> $note[$b] } 0..$#note) {
                print "$page[$_]: $note[$_]\n";
        }
         
        # другой вариант
        @note_sorted = @note[sort { $page[$a] <=> $page[$b] } 0..$#page];

Сортировка по убыванию.
Надо просто поменять местами переменные $a и $b в блоке сравнения.


        print "descending: ",
                join(" ", sort { $b <=> $a } 3,4,1,5,9,7),
                "\n";

Сортировка ключей хэш-массива в порядке возрастаний их значений.
Имеем хэш-массив, состоящий из слов книги и количеством повторений этих слов. Нам надо напечатать этот массив в порядке убывания частоты слова.


        # Считываем слова
        while (<>) {
                for (split) {
                        $count{$_}++;
                }
        }
         
        # Теперь выводим список слов и количество повторений
        for (sort { $count{$b} <=> $count{$a} } keys %count) {
                print "$_: $count{$_}\n";
        }

Сортировка имен файлов по дате/времени изменения.


        @newest_first = sort { -M $a <=> -M $b } <*>;

Оператор -М возвращает дату/время изменения файла в виде числа с плавающей точкой. Проблема состоит в том, что оператор -М выполняется очень медленно и на больших списках файлов операция сортировки может занять очень много времени. Для сортировки n файлов блок сравнения будет вызван примерно n log n раз. Для решения этой проблемы смотрите пример сортировки Шварца.

Сортировка величин, время сравнения которых сравнительно велико.
Например, нам надо отсортировать файлы по времени последнего изменения, но оператор -М (время последнего изменения файла) работает очень медленно.

Решить проблему можно с помощью сортировка Шварца (по имени Рандала Шварца Randal Schwartz).


        # Сортировка имен файлов по времени их последнего изменения
        @newest_first = 
          map { $_->[0] }
          sort { $a->[1] <=> $b->[1] }
          map { [ $_, -M ] }
          <*>;
                 
        # Общая форма. Сортировка по одному ключу
        @sorted = 
          map { $_ ->[0] }
          sort { $a->[1] %%compare-op%% $b->[1] }
          map { [ $_, %%transform-func%% ] }
          @input;
                 
        # Общая форма. Сортировка по двум ключам
        @sorted = 
          map { $_ ->[0] }
          sort { $a->[1] %%compare-op1%% $b->[1] or
                 $a->[2] %%compare-op2%% $b->[2] }
          map { [ $_, %%transform-func1%%, %%transform-func2%% ] }
          @input;

Суть метода заключается в том, что медленные вычисления производятся только один раз, а их результат сохраняется во временном массиве. Дальнейшая сортировка производится над значениями временного массива.

Сортировка строк по полям, разделенным символом.
Например, хочу отсортировать строки, разделенные на поля запятой, сначала по второму полю по числам, затем по первому полю по алфавиту в порядке убывания.


        sub fieldsort {
            my ($sep, $cols);
            if (ref $_[0]) {
                $sep = '\\s+'
            } else {
                $sep = shift;
            }
            unless (ref($cols = shift) eq 'ARRAY') {
                die "fieldsort columns must be in anon array";
            }
            my (@sortcode, @col);
            my $col = 1;
            for (@$cols) {
                my ($a, $b) = /^-/ ? qw(b a) : qw(a b);
                my $op = /n$/ ? '<=>' : 'cmp';
                push @col, (/(\d+)/)[0] - 1;
                push @sortcode, "\$${a}->[$col] $op \$${b}->[$col]";
                $col++;
            }
            my $sortfunc = eval "sub { " . join (" or ", @sortcode) . " } ";
            my $splitfunc = eval 'sub { (split /$sep/o, $_)[@col] } ';
            return
                map $_->[0],
                sort { $sortfunc->() }
                map [$_, $splitfunc->($_)],
                @_;
        }
                 
        #Примеры:
                 
        # Как сказано выше
        @sorted = fieldsort ':', ['2n', -1], @data;
                 
        # по 2-му затем по 1-му полю, по алфавиту, разделены пробелами
        @sorted = fieldsort [2, 1], @data;
                 
        # по 1-му полю по числам в порядке убывания, затем по 3-му полю
        # по алфавиту и по 2-му по числам, поля разделены '+'
        @sorted = fieldsort '+', ['-1n', 3, 2], @data;

На самом деле большая часть приведенного выше кода - это препроцессор, который готовит данные для дальнейшей сортировки Шварца.


Реклама на InfoCity

Яндекс цитирования



Финансы: форекс для тебя








1999-2009 © InfoCity.kiev.ua