|
|
|
урок: предыдущий следующий
Успех поиска подстроки зависит от того, где вы ее потеряли. Если вы потеряли ее в большей строке — вам повезло, потому что в таком случае может помочь операция index. Вот как ею можно воспользоваться:
|
$x = index($строка,$подстрока);
|
Perl находит первый экземпляр указанной подстроки в заданной строке и возвращает целочисленный индекс первого символа. Возвращаемый индекс отсчитывается от нуля, т.е. если подстрока найдена в начале указанной строки, вы получаете 0. Если она найдена на символ дальше, вы получаете 1 и т.д. Если в указанной строке нет подстроки, вы получаете -1.
Вот несколько примеров:
$where = index("hello","e") ;
$person = "barney";
$where = index("fred barney",$person);
@rockers = ("fred","barney") ;
$where = index(join(" ",@rockers),$person); # то же самое
|
Отметим, что и строка, в которой производится поиск, и строка, которая ищется, может быть литеральной строкой, скалярной переменной, содержащей строку, и даже выражением, которое имеет строковое значение. Вот еще несколько примеров:
$which = index("a very long string","long"); # $which получает 7
$which = index("a very long string","lame"); # $which получает -1
|
Если строка содержит искомую подстроку в нескольких местах, то функция index возвращает первый относительно начала строки индекс. Чтобы найти другие экземпляры подстроки, можно указать для этой функции третий параметр — минимальное значение позиции, которое она будет возвращать при поиске экземпляра подстроки. Выглядит это так:
|
$x = index($большая_строка,$маленькая_строка,$пропуск);
|
Вот несколько примеров того, как работает этот третий параметр:
$where = index("hello world","l"); # возвращает 2 (первая буква 1)
$where = index("hello world","l",0); # то же самое
$where = index("hello world","l",1); # опять то же самое
$where = index("hello world", "l", 3) ; # теперь возвращает З
# (3 - первая позиция, которая больше или равна 3)
$where = index("hello world", "o", 5); # возвращает 7 (вторая о)
$where = index("hello world", "o", 8) ; # возвращает -1 (ни одной после 8)
|
Вы можете пойти другим путем и просматривать строку справа налево с помощью функции rindex, чтобы получить правый крайний экземпляр. Эта функция тоже возвращает количество символов между левым концом строки и началом подстроки, как и раньше, но вы получите крайний правый экземпляр, а не первый слева, если их несколько. Функция rindex тоже принимает третий параметр, как и функция index, чтобы вы могли получить целочисленный индекс первого символа экземпляра подстроки, который меньше или равен адресу выбранной позиции. Вот несколько примеров того, что можно таким образом получить:
$w = rindex("hello world","he"); # $w принимает значение 0
$w = rindex("hello world","l"); # $w принимает значение 9 (крайняя правая l)
$w = rindex("hello world","o"); # $w принимает значение 7
$w = rindex("hello world","o "); # теперь $w принимает значение 4
$w = rindex("hello world","xx"); # $w принимает значение -1 (не найдена)
$w = rindex("hello world","o",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); # $grab получает "lo"
$grab = substr($hello,7,100); # 7 до конца, или "world!"
|
Можно даже выполнять подобным образом операцию "десять в степени n" для небольших целочисленных степеней, например:
|
$big = substr("10000000000",0,$power+1); # 10 ** $power
|
Если количество символов равно нулю, то возвращается пустая строка. Если либо начальная, либо конечная позиция меньше нуля, то такая позиция отсчитывается на соответствующее число символов, начиная с конца строки. Так, начальная позиция -1 и длина 1 (или более) дает последний символ. Аналогичным образом начальная позиция -2 отсчитывается от второго символа относительно конца строки:
$stuff = substr("a very long string",-3,3); # последние три символа
$stuff = substr("a very long string",-3,1); # буква і
|
Если начальная позиция указана так, что находится "левее" начала строки (например, задана большим отрицательным числом, превышающим длину строки), то в качестве начальной позиции берется начало строки (как если бы вы указали начальную позицию 0). Если начальная позиция — большое положительное число, то всегда возвращается пустая строка. Другими словами, Эта функция всегда возвращает нечто, отличное от сообщения об ошибке.
Отсутствие аргумента "длина" эквивалентно взятию в качестве этого аргумента большого числа — в этом случае извлекается все от выбранной позиции до конца строки*.
* В очень старых версиях Perl пропуск третьего аргумента не допускался, поэтому первые Perl-программисты использовали в качестве этого аргумента большие числа. Вы, возможно, столкнетесь с этим в своих археологических исследованиях программ, написанных Perl.
Если первый аргумент функции substr — скалярная переменная (другими словами, она может стоять в левой части операции присваивания), то сама эта функция может стоять в левой части операции присваивания. Если вы перешли к программированию на Perl из С, вам это может показаться странным, но для тех, кто когда-нибудь имел дело с некоторыми диалектами Basic, это вполне нормально.
|
substr($hw, 0, 5) = "hi"; # $hw теперь равна "hi world!"
|
В результате такого присваивания изменяется та часть строки, которая была бы возвращена, будь substr использована не в левой, а в правой части выражения. Например, substr ($var, 3,2) возвращает четвертый и пятый символы (начиная с 3 в количестве 2), поэтому присваивание изменяет указанные два символа в $var подобно тому, как это приведено ниже:
Длина заменяющего текста (который присваивается функции substr) не обязательно должна быть равна длине заменяемого текста, как в этом примере. Строка автоматически увеличивается или уменьшается в соответствии с длиной текста. Вот пример, в котором строка укорачивается:
$hw = "hello world!";
substr($hw, 0, 5) = "howdy"; # $hw теперь равна "howdy world!"
|
В следующем примере эта строка удлиняется:
$hw = "hello world!";
substr($hw, -6, 5) = "nationwide news"; # заменяет "world"
|
Процедуры укорачивания и удлинения заменяемой строки выполняются достаточно быстро, поэтому не бойтесь их использовать — хотя лучше все же заменять строку строкой той же длины.
Функция printf оказывается удобной, когда нужно взять список значений и создать выходную строку, в которой эти значения отображались бы в заданном виде. Функция sprintf использует такие же аргументы, как и функция printf, но возвращает то, что выдала бы printf, в виде одной строки. (Можете считать ее "строковой функцией printf".) Например, чтобы создать строку, состоящую из буквы x и значения переменной $y, дополненного нулями до пяти разрядов, нужно записать:
|
$result = sprintf("x%05d",$y);
|
Описание аргументов функции sprintf вы найдете в разделе sprintf главы 3 книги Programming Perl и на man-странице printf(3) (если она у вас есть).
Вы уже знаете, что с помощью встроенной функции sort можно получить какой-либо список и отсортировать его по возрастанию кодов ASCII. Что, если вы хотите отсортировать список не по возрастанию кодов ASCII, а, скажем, с учетом числовых значений? В Perl есть инструменты, которые позволят вам решить и эту задачу. Вы увидите, что Perl-функция sort может выполнять сортировку в любом четко установленном порядке.
Чтобы задать порядок сортировки, следует определить программу сравнения, которая задает способ сравнения двух элементов. Для чего она нужна? Нетрудно понять, что сортировка — это размещение множества элементов в определенном порядке путем их сравнения между собой. Поскольку сравнить сразу все элементы нельзя, нужно сравнивать их по два, используя результаты этих попарных сравнений, расставить все их по местам.
Программа сравнения определяется как обычная подпрограмма. Она будет вызываться многократно, и каждый раз ей будут передаваться два аргумента сортируемого списка. Данная подпрограмма должна определить, как первое значение соотносится со вторым (меньше, равно или больше), и возвратить закодированное значение (которое мы опишем чуть ниже). этот процесс повторяется до тех пор, пока не будет рассортирован весь список.
Чтобы повысить скорость выполнения, эти два значения передаются в подпрограмму не в массиве, а как значения глобальных переменных $a и $b. (Не волнуйтесь: исходные значения $a и $b надежно защищены.) эта подпрограмма должна возвратить любое отрицательное число, если $a меньше $b, нуль, если $a равно $b, и любое положительное число, если $a больше $b. Теперь учтите, что "меньше чем" соответствует вашему пониманию этого результата в данном конкретном случае; это может быть сравнение чисел, сравнение по третьему символу строки, наконец, сравнение по значениям какого-то хеша с использованием передаваемых значений как ключей — в общем, это очень гибкий механизм.
Вот пример подпрограммы сортировки в числовом порядке:
sub by_number {
if ($a < $b) {
return -1;
}
elsif ($a == $b) {
return 0;
}
elsif ($a > $b) {
return 1;
}
}
|
Обратите внимание на имя by_number. На первый взгляд, в имени этой подпрограммы нет ничего особенного, но скоро вы поймете, почему нам нравятся имена, которые начинаются с префикса bу_.
Давайте разберем эту подпрограмму. Если значение $а меньше (в данном случае в числовом смысле), чем значение $b, мы возвращаем значение -1.
Если значения численно равны, мы возвращаем нуль, а в противном случае возвращаем 1. Таким образом, в соответствии с нашей спецификацией программы сравнения для сортировки этот код должен работать.
Как использовать данную программу? Давайте попробуем рассортировать такой список:
|
@somelist = (1,2,4,8,16,32,64,128,256);
|
Если использовать с этим списком обычную функцию sort без всяких "украшений", числа будут рассортированы так, как будто это строки, причем сортировка будет выполнена с учетом кодов ASCII, т.е.:
@somelist = (1,2,4,8,16,32,64,128,256);
@wronglist = sort @somelist; # @wronglist теперь содержит (1,128,16,2,256,32,4,64,8)
|
Конечно, это не совсем числовой порядок. Давайте используем в функции sort нашу только что определенную программу сортировки. Имя этой программы ставится сразу после ключевого слова sort:
@rightlist = sort by_number @wronglist;
# @rightlist теперь содержит (1,2,4,8,16,32,64,128,256)
|
Задача решена. Обратите внимание: функцию sort можно прочитать вместе с ее спутницей, программой сортировки, на человеческом языке, т.е. "рассортировать по числовым значениям". Вот почему мы использовали в имени подпрограммы префикс by_ ("по").
Такое тройное значение (-1, 0, +1), отражающее результаты сравнения числовых значений, встречается в программах сортировки достаточно часто, поэтому в Perl есть специальная операция, которая позволяет сделать все это за один раз. Эту операцию часто называют "челноком" (или "космическим кораблем", как следует из дословного перевода английского spaceship), потому что ее знак — <=>.
Используя "космический корабль", можно заменить предыдущую подпрограмму сортировки следующим кодом:
sub by_number {
$a <=> $b;
}
|
Обратите внимание на знак операции между двумя переменными. Да, он действительно состоит из трех символов. эта операция возвращает те же значения, что и цепочка if/elsif из предыдущего определения этой программы. Теперь все записано очень кратко, но этот вызов можно сократить и дальше, заменив имя подпрограммы сортировки самой подпрограммой, записанной в той же строке:
|
@rightlist = sort { $a <=> $b } @wronglist;
|
Некоторые считают, что такая запись снижает удобочитаемость. Мы с ними не согласны. Некоторые говорят, что благодаря этому в программе исчезает необходимость выполнять переход к определению подпрограммы. Но языку Perl все равно. Наше собственное правило гласит: если код не умещается в одной строке или должен использоваться более чем однажды, он оформляется как подпрограмма.
Для операции сравнения числовых значений "челнок" есть соответствующая строковая операция — cmp*. эта операция возвращает одно из трех значений в зависимости от результата сравнения двух аргументов по строковым значениям. Вот как можно по-другому записать порядок сортировки, который установлен по умолчанию:
|
@result = sort { $a <=> $b } @somelist;
|
* Не вполне соответствующая. Встроенная функция sort отбрасывает элементы undef, а эта функция — нет.
Вам, вероятно, никогда не придется писать именно такую подпрограмму (имитирующую встроенную функцию стандартной сортировки) — если только вы не пишете книгу о Perl. Тем не менее, операция cmp все же находит применение в каскадных схемах упорядочивания. Например, вам необходимо расставить элементы по численным значениям, если они численно не равны; при равенстве они должны идти быть упорядочены по строковым значениям. (По умолчанию приведенная выше подпрограмма by_number просто ставит нечисловые строки в случайном порядке, потому что при сравнении двух нулевых значений числовое упорядочение провести нельзя.) Вот как можно сказать "числовые, если они численно не равны, иначе строковые":
sub by_mostly_numeric {
($a <=> $b) || ($a cmp $b);
}
|
этот код работает следующим образом. Если результат работы "челнока" равен -1 или 1, то остальная часть выражения пропускается и возвращается -1 или 1. Если "челнок" дает нуль, то выполняется операция cmp, которая возвращает соответствующее значение, сравнивая сортируемые значения как строки.
Сравниваются не обязательно те значения, которые передаются в программу. Пусть, например, у вас есть хеш, ключи которого — регистрационные имена, а значения — реальные имена пользователей. Предположим, вы хотите напечатать таблицу, в которой регистрационные и реальные имена будут рассортированы по порядку реальных имен.
Сделать это довольно легко. Давайте Предположим, что значения находятся в массиве %names. Регистрационные имена, таким образом, представляют собой список keys (%names). Нам нужно получить список регистрационных имен, рассортированных по соответствующим значениям, поэтому для любого конкретного ключа $а мы должны проверить значение $names{$а} и провести сортировку относительно данного значения. Если следовать этой логике, то программа практически напишется сама:
@sortedkeys = sort by_names 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, а все буквы b — в буквы а. это нельзя сделать посредством двух команд s///, потому что вторая команда отменит все изменения, сделанные первой.
Такое преобразование данных очень просто выполняется в shell с помощью стандартной команды tr(/):
(Если вы ничего не знаете о команду tr, загляните на man-страницу tr(/); это полезный инструмент.) В Perl тоже применяется операция tr, которая работает в основном так же:
Операция tr принимает два аргумента: старая_строка и новая_строка. Они используются так же, как аргументы команды s///; другими словами, имеется некий разделитель, который стоит сразу же за ключевым словом tr и разделяет и завершает аргументы (в данном случае это косая черта, но в этой роли могут выступать почти все символы).
Аргументы операции tr похожи на аргументы команды tr(/). Операция tr изменяет содержимое переменной $_ (совсем как s///}, отыскивая в ней символы старой строки и заменяя найденные символы соответствующими символами новой строки. Вот несколько примеров:
$_ = "fred and barney";
tr/fb/bf/; # $_ теперь содержит "bred and farney"
tr/abcde/ABCDE/; # $_ теперь содержит "BrED AnD fArnEy"
tr/a-z/A-Z/; # $_ теперь содержит "BRED AND FARNEY"
|
Обратите внимание на то, что диапазон символов можно обозначить двумя символами, разделенными дефисом. Если вам нужен в строке дефис как таковой, поставьте перед ним обратную косую.
Если новая строка короче старой, то последний символ новой строки повторяется столько раз, сколько нужно для того, чтобы строки имели одинаковую длину, например:
$_ = "fred and barney";
tr/a-z/x/; # $_ теперь содержит "xxxx xxx xxxxxx"
|
Чтобы такое не происходило, поставьте в конце операции tr/// букву d, которая означает delete ("удалить"). В данном случае последний символ не повторяется. Все символы старой строки, для которых нет соответствующих символов в новой строке, просто удаляются:
$_ = "fred and barney";
tr/a-z/ABCDE/d; # $_ теперь содержит "ED AD BAE"
|
Обратите внимание на то, что все буквы, стоящие после буквы е, исчезают, потому что в новом списке соответствующей буквы нет, и на то, что на пробелы это не влияет, потому что их нет в старом списке. По принципу работы это эквивалентно команде tr c опцией -d.
Если новый список пуст и опция d не используется, то новый список будет совпадать со старым. Это может показаться глупым — зачем заменять 1 на 1 и 2 на 2? — но на самом деле в этом есть довольно глубокий смысл. Операция tr/// возвращает количество символов, совпавших со старой строкой, и путем замены символов на самих себя вы можете получить число таких символов, содержащихся в новой строке*. Например:
* Это справедливо только для одиночных символов. Для подсчета строк в операции сопоставления с образцом используйте флаг /g:
while (/образец/g) {
$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 = 2
tr/a-z/_/c; # $_ теперь содержит "fred_and_barney" (символы-небуквы => _)
tr/a-z//cd; # $_ теперь содержит "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"
|
В первом из этих примеров каждое слово (стоящие друг за другом буквы) было заменено одной буквой X. Во втором примере все группы стоящих друг за другом символов-небукв стали одиночными знаками подчеркивания.
Как и команда s///, операция tr может быть проведена над другой строкой, а не только над строкой, хранящейся в переменной $_. это достигается с помощью операции =~:
$names = "fred and barney";
$names =~ tr/aeiou/X/; # $names теперь содержит "frXd Xnd bXrnXy"
|
- Напишите программу, которая читает список имен файлов и разбивает каждое имя на начальный и конечный компоненты. (Все, что стоит в имени файла до последней косой черты — начальный компонент, а все, что за ней — конечный компонент. Если косой нет, то все имя является конечным компонентом.) Попробуйте выполнить эту программу с именами вроде /fred, bamey, fred/barney. Имеют ли результаты смысл?
- Напишите программу, которая читает список чисел, стоящих в отдельних строках, и сортирует их по числовым значениям, выводя список-результат в столбец с выравниванием справа. (Совет: для вывода столбца с выравниванием справа нужно использовать формат наподобие %20g.)
- Напишите программу вывода реальных и регистрационных имен пользователей из файла /etc/passwd с сортировкой по фамилиям пользователей. Работоспособно ли ваше решение в случае, если у двух пользователей одинаковые фамилии?
- Создайте файл, состоящий из предложений, каждое из которых стоит в отдельной строке. Напишите программу, которая переводит первый символ каждого предложения в верхний регистр, а остальную часть предложения — в нижний. (Работает ли эта программа в случае, если первый символ — небуква? Как решить эту задачу, если предложения не стоят в отдельных строках?)
|
|
|