Глава 15В этой главе:
Извлечение и замена подстроки Форматирование данных с помощью функции sprintf() Сортировка по заданным критериям Транслитерация Упражнения Другие операции преобразования данныхПоиск подстроки Успех поиска подстроки зависит от того, где вы ее потеряли. Если вы потеряли ее в большей строке вам повезло, потому что в такам случае может помочь операция index. Вот как ею можно воспользоваться:$х = index($сгрока,$подстрока)Perl находит первый зкземпляр указанной подстроки в заданной строке и возвращает целочисленный индекс первого символа. Возвращаемый ин-декс отсчитывается от нуля, т.е. если подстрока найдена в начале указанной строки, вы получаете 0. Если она найдена на символ дальше, вы получаете 1 и т.д. Если в указанной строке нет подстроки, вы получаете -1.Вот несколько примеров: $where = index("hello","e") ; $person = "barney"; $where = index("fred barney",$person); $rockers = ("fred","barney") ; $where = index(join(" ",Orockers),$person); # то же самоеОтметим, что и строка, в которой производится поиск, и строка, которая ищется, может быть литеральной строкой, скалярной переменной, содержа-щей строку, и даже выражением, которое имеет строковое значение. Вот еще несколько примеров: $which index("a very long string","long"); # $which получает 7 $which as index("a very long string","lame"); # $which получает -1Если строка содержит искомую подстроку в нескольких местах, то функция index возвращает первый относительно начала строки индекс. Чтобы найти другие зкземпляры подстроки, можно указать для зтой функ-ции третий параметр минимальное значение позиции, которое она будет возвращать при поиске зкземпляра подстроки. Выглядит зто так:$х = index($большая_строка,$маленькая_строка,$пропуск}Вот несколько примеров того, как работает зтот третий параметр: $where = index("hello world","!"); # возвращает 2 (первая буква 1) $where = index("hello world","I",0); # то же самое $where = index("hello world","I",1); # опять то же самое $where = indexC'hello world", "I", 3) ; # теперь возвращает З# (3 - первая позиция, которая больше или равна 3) $where = indexC'hello world", "о", 5) ; # возвращает 7 (вторая о) $where indexC'hello world", "о", 8) ; # возвращает -1 (ни одной после 8)Вы можете пойти другим путем и просматривать строку справа налево с помощью функции rindex, чтобы получить правый крайний зкземпляр. Зта функция тоже возвращает количество символов между левым концом строки и началом подстроки, как и раньше, но вы получите крайний правый зкземпляр, а не первый слева, если их несколько. Функция rindex тоже принимает третий параметр, как и функция index, чтобы вы могли получить целочисленный индекс первого символа зкземпляра подстроки, который меньше или равен адресу выбранной позиции. Вот несколько примеров того, что можно таким образом получить:$w = rindex("hello world","he"); # $w принимает значение О$w = rindex("hello world","!"); # $w принимает значение 9 (крайняя правая 1)$w = rindex("hello world", "о"); # $w принимает значение 7$w = rindex("hello world","o "); # теперь $w принимает значение 4$w = rindex("hello world","xx"); t $w принимает значение -1 (не найдена)$w = rindex("hello world", "о",6); # $w принимает значение 4 (первая до 6)$w = rindex ("hello world", "o",3) ; # $w принимает значение -1 (не найдена до 3)Извлечение и замена подстроки Извлечь фрагмент строки можно путем осторожного применения регулярних виражений, но если зтот фрагмент всегда находится на известной позиции, такой метод незффективен. В зтом случае удобнее использовать функцию substr. Зта функция принимает три аргумента: строковое значение, начальную позицию (определяемую так же, как в функции index) и длину, т.е.$s = substr ($строка, $нач<зло, $длина) ;Начальная позиция определяется так же, как в функции index: первый символ нуль, второй символ единица и т.д. Длина зто число символов, которые необходимо извлечь, начиная от данной позиции: нулевая длина означает, что символы не извлекаются, единица означает получение первого символа, двойка двух символов и т.д. (Больше символов, чем имеется в строке, извлечь нельзя, позтому если вы запросите слишком много, ничего страшного не произойдет.) Выглядит зто так:$hello = "hello, world!"; $grab substr($hello, 3, 2); t $grab получает "lo" $grab = substr($hello, 7, 100); # 7 до конца, или "world!"Можно даже вьшолнять подобным образом операцию "десять в степени п" для небольших целочисленньк степеней, например: $big = substr("10000000000",0,$power+l); # 10 ** $power Если количество символов равно нулю, то возвращается пустая строка. Если либо начальная, либо конечная позиция меньше нуля, то такая позиция отсчитывается на соответствующее число символов, начиная с конца строки. Так, начальная позиция -©й длина 1 (или более) дает последний символ. Аналогичным образом начальная позиция -2 отсчитывается от второго символа относительно конца строки:$stuff = substr("a very long string",-3,3); # последние три символа $stuff = substr("a very long string",-3,1); # буква ЁЕсли начальная позиция указана так, что находится "левее" начала строки (например, задана большим отрицательным числом, превышающим длину строки), то в качестве начальной позиции берется начало строки (как если бы вы указали начальную позицию 0). Если начальная позиция большое положительное число, то всегда возвращается пустая строка. Другими словами, зта функция всегда возвращает нечто, отличное от сообщения об ошибке.Отсутствие аргумента "длина" зквивалентно взятию в качестве зтого аргумента большого числа в зтом случае извлекается все от выбранной позиции до конца строки*.Если первый аргумент функции substr скалярная переменная (другими словами, она может стоять в левой части операции присваивания), то сама зта функция может стоять в левой части операции присваивания. Если вы перешли к программированию на Perl из С, вам зто может показаться странным, но для тех, кто когда-нибудь имел дело с некоторыми диалектами Basic, зто вполне нормально.* В очень старых версиях Perl пропуск третього аргумента не допускался, позтому первые Perl-программистн использовали в качестве зтого аргумента большие числа. Вы, возможно, столкнетесь с зтим в своих археологических исследованиях программ, написанньк Perl.В результате такого присваивания изменяется та часть строки, которая была бы возвращена, будь substr использована не в левой, а в правой части выражения. Нопример, substr ($var, 3,2) возвращает четвертьш и пятый символы (начиная с 3 в количестве 2), позтому присваивание изменяет указанные два символа в $var подобно тому, как зто приведено ниже:$hw = "hello world!"; substr($hw, 0, 5) = "howdy"; # $hw теперь равна "howdy world!"Длина заменяющего текста (который присваивается функции substr) не обязательно должна быть равна длине заменяемого текста, как в зтом примере. Строка автоматически увеличивается или уменьшается в соответ-ствии с длиной текста. Вот пример, в котором строка укорачивается:substr($hw, 0, 5) = "hi"; # $hw теперь равна "hi world!"В следующем примере зта строка удлиняется: substr($hw, -б, 5) = "nationwide news"; # заменяет "world"Процедуры укорачивания и удлинения заменяемой строки выполняются д остаточно быстро, позтому не бойтесь их использовать хотя лучше все же заменять строку строкой той же длины.Форматирование данных с помощью функции sprintf()Функция printf оказывается удобной, когда нужно взять список значений и создать выходную строку, в которой зти значення отображались бы в заданном виде. Функция sprint f использует такие же аргументы, как и функция printf, но возвращает то, что выдала бы printf, в виде одной строки. (Можете считать ее "строковой функцией printf".) Например, чтобы создать строку, состоящую из буквы х и значення переменной $у, дополненного нулями до пяти разрядов, нужно записать:$result = sprintf("X%05d",$y); Описание аргументов функции sprintf вы найдете в разделе sprintf главы 3 книги Programming Perl и на man-странице printf(3) (если она у вас сть).Сортировка по заданным критериям Вы уже зна те, что с помощью встроенной функции sort можно получить какой-либо список и отсортировать его по возрастанию кодов ASCII. Что, если вы хотите отсортировать список не по возрастанию кодов ASCII, а, скажем, с учетом числових значений? В Perl сть инструменты, которые позволят вам решить и зту задачу. Вы увидите, что Perl-функция sort может выполнять сортировку в любом четко установленном порядке.Чтобы задать порядок сортировки, следует определить программу срав-нения, которая задает способ сравнения двух злементов. Для чего она нужна? Нетрудно понять, что сортировка зто размещение множества злементов в определенном порядке путем их сравнения между собой. Поскольку сравнить сразу все злементы нельзя, нужно сравнивать их по два й, используя результати зтих попарных сравнений, расставить все их по местам.Программа сравнения определяется как обычная подпрограмма. Она будет вызываться многократно, и каждый раз ей будут передаваться два аргумента сортируемого списка. Данная подпрограмма должна определить, как первое значение соотносится со вторым (меньше, равно или больше), и возвратить закодированное значение (которое мы опишем чуть ниже). Зтот процесе повторяется до тех пор, пока не будет рассортирован весь список. Чтобы повысить скорость вьшолнения, зти два значення передаются в подпрограмму не в массиве, а как значення глобальных переменных $а и $Ь. (Не волнуйтесь: исходные значення $ а и $Ь належно защищены.) Зта подпрограмма должна возвратить любое отрицательное число, если $а меньше $Ь, нуль, если $а равно $Ь, и любое положительное число, если $а больше $Ь. Теперь учтите, что "меньше чем" соответствует вашому пониманию зтого результата в данном конкретном случае; зто может бьггь сравнение чисел, сравнение по третьому символу строки, наконец, сравнение по значенням какого-то хеша с использованием передаваемьгх значений как ключей в общем, зто очень гибкий механизм.Вот пример подпрограммы сортировки в числовом порядке: sub by_number ( if ($a < $b) ( return -1; } elsif ($a == $b) ( return 0; } elsif ($a > $b) ( return 1; ) > Обратите внимание на имя by_number. На первый взгляд, в имени зтой подпрограммы нет ничего особенного, но скоро вы поймете, почему нам нравятся имена, которые начинаются с префикса Ьу_.Давайте разберем зту подпрограмму. Если значение $а меньше (в данном случае в числовом смысле), чем значение $Ь, мы возвращаем значение -1.Если значення численно равнн, мы возвращаем нуль, а в противном случае возвращаем 1. Таким образом, в соответствии с нашей спецификацией программы сравнения для сортировки зтот код должен работать.Как использоватьданную программу? Давайте попробу м рассортировать такой список: Bsomelist = (1,2,4,8,16,32,64,128,256); Если использовать с зтим списком обычную функцию sort без всяких "украшений", числа будут рассортированы так, как будто зто строки, причем сортировка будет выполнена с учетом кодов ASCII, т.е.:Swronglist = sort Osomelist; # Owronglist теперь содержит (1,128,16,2,256,32,4,64,8)Конечно, зто не совсем числовой порядок. Давайте используем в функ-ции sort нашу только что определенную программу сортировки. Имя зтой программы ставится сразу после ключового слова sort:@rightlist = sort by_number Swronglist; * @rightlist Задача решена. Обратите внимание: функцию sort можно прочитать вместе с ее спутницей, программой сортировки, на человеческом языке, т.е. "рассортировать по числовым значенням". Бот почому мы использовали в имени подпрограммы префикс Ьу_ ("по").Такое тройное значение (-1, 0, +1), отражающее результаты сравнения числовьк значений, встречается в программах сортировки достаточно часто, позтому в Perl сть специальная операция, которая позволяет сделать все зто за один раз. Зту операцию часто называют "челноком" (или "космическим кораблем", как следует из дословного перевода английского spaceship), потому что ее знак <=>.Используя "космический корабль", можно заменить предыдущую под-программу сортировки следующим кодом: sub by_number ( $а <=> $b;} Обратите внимание на знак операции между двумя переменными. Да, он действительно состоит из трех символов. Зта операция возвращает те же значення, что и цепочка if/elsif из предыдущего определения зтой программы. Теперь все записано очень кратко, но зтот вызов можно сокра-тить и дальше, заменив имя подпрограммы сортировки самой подпрограм-мой, записанной в той же строке:@rightlist " sort ( $а <=> $b } @wronglist;Некоторые считают, что такая запись снижает удобочитаемость. Мы с ними не согласны. Некоторые говорят, что благодаря зтому в программе исчезает необходимость выполнять переход к определению подпрограммы. Но языку Perl все равно. Наше собственное правило гласит: если код не умещается в одной строке или должен использоваться более чем однаждн, он оформляется как подпрограмма. Для операции сравнения числових значений "челнок" сть соответст-вующая строковая операция стр*. Зта операция возвращает одно из трех значений в зависимости от результата сравнения двух аргументов по строковим значенням. Вот как можно по-другому записать порядок сортировки, который установлен по умолчанию:@result = sort ( $а cmp $b } Osomelist;Вам, вероятно, никогда не придется писать именно такую подпрограмму (имитирующую встроенную функцию стандартной сортировки) если только вы не пишете книгу о Perl. Тем не менее, операция стр все же находит применение в каскадних схемах упорядочивания. Например, вам необходи-мо расставить злементы по численним значенням, если они численно не равны; при равенстве они должны идти быть упорядочены по строковим значенням. (По умолчанию приведенная выше подпрограмма by_number просто ставит нечисловые строки в случайном порядке, потому что при сравнении двух нулевих значений числовое упорядочение провести нельзя.) Вот как можно сказать "числовые, если они численно не равны, иначе строковие":sub by mostly_numeric ( ($a <=> $b) I I ($a cmp $b) ; ) Зтот код работает следующим образом. Если результат работы "челнока" равен -1 или 1, то остальная часть вираження пропускается и возвращается -1 или 1. Если "челнок" дает нуль, то вьшолняется операция cmp, которая возвращает соответствующее значение, сравнивая сортируемие значення как строки.Сравниваются не обязательно те значення, которне передаются в про-грамму. Пусть, например, у вас сть хеш, ключи которого регистрацион-ние имена, а значення реальнне имена пользователей. Предположим, ви хотите напечатать таблицу, в которой регистрационнне и реальные имена будут рассортированн по порядку реальних имен.Сделать зто довольно легко. Давайте Предположим, что значення находятся в массиве % names. Регистрационнне имена, таким образом, представляют собой список keys (%names). Нам нужно получить список регистрационных имен, рассортированных по соответствующим значенням, позтому для любого конкретного ключа $а мы должны проверить значение $ names ($а} и провести* Не вполне соответствующая. Встроенная функция sort отбрасывает злементы undef, а зта функция нет.сортировку относительно данного значення. Если следовать зтой логике, то программа практически напишется сама: @sortedkeys = sort_by_name keys (%names); sub by_names ( return $names{$a} cmp $names($b}; ) foreach (@sortedkeys) { print "$_ has a real name of $names($_)\n"; } K зтому нужно еще добавить "аварийное" сравнение. Предположим, что реальные имена двух пользователей совпадают. Из-за капризной натуры программы sort мы в перами раз можем получить зти значення в одном порядке, а во второй раз в другом. Зто плохо, если данный результат придется, например, вводить в программу сравнения для формирования отчета, позтому следует избегать таких вещей. Задача легко решается с помощью операции cmp:sub by_names { ($names($a} cmp $names($b}) || ($a cmp $b); Если реальные имена совпадают, то еортировка здесь производится на оснований регистрационного имени. Поскольку регистрационные имена уникальны (ведь, помимо всего прочего, они являются ключами хеша, а ключи совпадать не могут), мы можем добиться нужного нам результата. Если вы не хотите, чтобы поздно вечором раздался звонок от системного администратора, удивленно спрашивающего, почему включается аварийная сигнализация лишите хорошие программы днем!Транслитерация Если вам необходимо взять строку и заменить все зкземпляры какого-нибудь символа другим символом или удалить их, зто можно сделать, как вы уже зна те, с помощью тщательно подобранных команд s///. Предположим, однако, вам нужно превратить все буквы а в буквы Ь, а все буквы b в буквы а. Зто нельзя сделать посредством двух команд s///, потому что вторая команда отменит все изменения, сделанные первой.Такое преобразование данных очень просто выполняется в shell с помощью стандартной команды tr(l):tr ab ba <indata >outdata (Если вы ничего не зна те о командо tr, загляните на man-страницу tr(l);зто полезный инструмент.) В Perl тоже применяется операция tr, которая работает в основном так же:tr/ab/ba; Операция tr принимает два аргумента: старая_строка и новая_строка. Они используются так же, как аргументы команды s///; другими словами, имеется некий разделитель, который стоит сразу же за ключевым словом tr и разделяет и завершает аргументы (в данном случае зто косая черта, но в зтой роли могут виступать почти все символы).Аргументи операции tr похожи на аргументи команды tr(l). Операция tr изменяет содержимое переменной $_ (совсем как si/I}, отыскивая в ней символы старой строки и заменяя найденные символы соответствующи-ми символами новой строки. Вот несколько примеров:$_ = "fred and barney"; tr/fb/bf; # $ теперь содержит "bred and farney" tr/abcde/ABCDE/; t $_ теперь содержит "BrED AnD fArnEy" tr/a-z/A-Z/; # $_ теперь содержит "BRED AND FARNEY"Обратите внимание на то, что диапазон символов можно обозначить двумя символами, разделенными дефисом. Если вам нужен в строке дефис как таковой, поставьте перед ним обратную косую. Если новая строка короче старой, то последний символ новой строки повторяется столько раз, сколько нужно для того, чтобы строки имели одинаковую длину, например: $_ = "fred and barney"; tr/a-z/x/; # $_ теперь содержит "хххх xxx xxxxxx"Чтобы такое не происходило, поставьте в конце операции tr/// букву d, которая означает delete ("удалить"). В данном случае последний символ не повторяется. Все символы старой строки, для которых нет соответствую-щих символов в новой строке, просто удаляются:$ = "fred and barney"; tr/a-z/ABCDE/d; # $_ теперь содержит "ED AD BAE"Обратите внимание на то, что все буквы, стоящие после буквы е, исчезают, потому что в новом списке соответствующей буквы нет, и на то, что на пробелы зто не влияет, потому что их нет в старом списке. По принципу работы зто зквивалентно команде tr c опцией -d.Если новый список пуст и опция d не используется, то новый список будет совпадать со старым. Зто может показаться глупым зачем заменять на и 2 на 2? но на самом деле в зтом сть довольно глубокий смысл. Операция tr/// возвращает количество символов, совпавших со старой строкой, и путем замены символов на самих себя вы можете получить число таких символов, содержащихся в новой строке*. Например:* Зто справедливо только для одиночних символов. Для подсчета строк в операции сопоставления с образцом используйте флаг /д:while (/образец/д) { $count++;> $_ = "fred and barney"; $count = tr/a-z//; # $_ не изменилась, но $count = 13 $count2 = tr/a-z/A-Z/; # $_ переведена в верхний регистр, и $count2 = 13Если в конце операции добавить букву с (как мы добавляли букву d), символы старой строки будут рассматриваться как исключение из набора всех 256 символов. Каждый символ, указаними в старой строке, удаляется из совокупности всех возможных символов; оставшиеся символы, взятые по порядку от младшего к старшему, образуют новую строку-результат. Напри-мер, подсчитать или изменить в нашей строке все символы, не являющиеся буквами, можно так:$_ = "fred and barney"; $count == tr/a-z//c; # $_ не изменилась, но $count = 2tr/a-z/_/c; # $_ теперь содержит "fred_and_barney" (символы-небуквы => _)tr/a-z//cd; t $_ теперь содержит "fredandbarney" (символы-небуквыудалены) Отметим, что зти опции можно обьединять, как показано в последнем примере, где мы сначала меняем совокупность символов на "дополняющую" (список букв становится списком всех символов-небукв), а затем с помощью опции d удаляем все символы зтой совокупности.Последняя опция операции tr/// s, которая заменяет множество зкземпляров одной преобразованной буквы одним. Например:$_ = "aaabbbcccdefghi"; tr/defghi/abcddd/s; # $_ теперь содержит "aaabbbcccabcd"Обратите внимание: буквы def заменены на abc, a ghi (которые без опции s превратились би в ddd) стали одной буквой d. Отметим также, что стоящие друг за другом буквы в первой части строки "не сжимаются", потому что для них не задано преобразование. Вот еще несколько примеров:$_ = "fred and barney, wilma and betty"; tr/a-z/X/s; $_ теперь содержит "X X X, X X X" $_ = "fred and barney, wilma and betty";tr/a-z/_/cs; # $_ теперь содержит "fred_and_barney_wilma and betty"В первом из зтих примеров каждое слово (стоящие друг за другом буквы) было заменено одной буквой х. Во втором примере все группы стоящихдруг за другом символов-небукв стали одиночними знаками подчеркивания. Как и команда s///, операция tr может бьггь проведена над другой строкой, а не только над строкой, хранящейся в переменной $_. Зто достигается с помощью операции =~:$names = "fred and barney"; $names =~ tr/aeiou/X/; # $names теперь содержит "frXd Xnd bXrnXy"Упражнения Ответы к упражнениям см. в приложении А. 1. Напишите программу, которая читает список имен файлов и разбивает каждое имя на начальний и конечний компоненти. (Все, что стоит в имени файла до последней косой черты начальный компонент, а все, что за ней конечний компонент. Если косой нет, то все имя является конечним компонентом.) Попробуйте вьшолнить зту программу с име-нами вроде /fred, bamey, fred/barney. Имеют ли результати смисл?2. Напишите программу, которая читает список чисел, стоящих в отдельних строках, и сортирует их по числовим значенням, виводя список-результат в столбец с выравниванием справа. (Совет: для вивода столбца с выравниванием справа нужно использовать формат наподобие %20д.)3. Напишите программу вивода реальних и регистрационннх имен поль-зователей из файла /etc/passwd с сортировкой по фамилиям пользовате-лей. Работоспособно ли ваше решение в случае, если удвух пользователей одинаковне фамилии?4. Создайте файл, состоящий из предложений, каждое из которих стоит в отдельной строке. Напишите программу, которая переводит первий символ каждого предложения в верхний регистр, а остальную часть предложения в нижний. (Работает ли зта программа в случае, если первый символ небуква? Как решить зту задачу, если предложения не стоят в отдельних строках?) |