PERL – Корисні поради, 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";

Це зовсім інший підхід. Ми починає з того, що створюємо анонімні функції, кожна з яких повертає TRUE, коли її аргумент збігається з одним з шуканих слів. Посилання на функції зберігаються в масиві @ 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;

Насправді велика частина наведеного вище коду – це препроцесор, який готує дані для подальшої сортування Шварца.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*