В статье 2 мы уже рассмотрели много операций над строками. Однако, совершенно не были затронуты строковые операции, операндами которых являются косвенные ссылки на функторы. Теперь подробно рассмотрим и их.
Начнём с группы встроенных унарных предикатов, которая осуществляет проверку своих операндов-символов на принадлежность к некоторому символьному набору. Все их имена начинаются с префикса "сс_" ("character class", или "character category"):
|
cc_lower (Char)
|
Char – код строчной буквы?
|
|
cc_upper (Char)
|
Char – код заглавной буквы?
|
|
cc_alpha (Char)
|
Char – код любой (заглавной или строчной) буквы?
|
|
cc_digit (Char)
|
Char – код десятичной цифры?
|
|
cc_odigit (Char)
|
Char – код восьмеричной цифры?
|
|
cc_xdigit (Char)
|
Char – код шестнадцатеричной цифры?
|
|
cc_blank (Char)
|
Char – код пробельного символа?
|
|
cc_blank (Char)
|
Char – код любого печатаемого символа?
|
У них всех один операнд: код символа (это целое число, не строка)! Как результат, они возвращают 1 (если проверяемое условие истинно) или 0 (если оно ложно). Их можно вызывать напрямую: например, cc_alpha (s_ord (Str, Pos)) истинно, если символ строки Str с индексом Pos является буквой. Однако, чаще они вызываются косвенно, и передаются как операнды для других встроенных функторов языка.
(Для получения кода первого символа строки-литерала, лучше использовать не s_ord, а префикс \c. Например, \c' ' – это код пробела (т.е. 32), а \c'0' – код нуля (48).)
Далее (для удобства) будем называть символ "Pred-позитивным", если применение предиката Pred к его коду возвращает истинное (ненулевое) значение, и "Pred-негативным", если он возвращает 0. Следующая группа встроенных операций проверяет на заданный предикат начало заданной строки:
|
s_span_in (String, CPred)
|
Количество CPred-позитивных символов в начале строки String
|
|
s_span_ex (String, CPred)
|
Количество CPred-негативных символов в начале строки String
|
Вот несколько иная формулировка: возвращается индекс для первого CPred-негативного символа (s_span_in) или CPred-позитивного (s_span_ex). В граничных случаях: s_span_in возвращает 0 (если CPred-негативен самый первый символ в String) или длину String (когда ВСЕ символы в ней CPred-позитивны). Для s_span_ex "негативность" и "позитивность" меняются местами. Как пример, определим функтор trim_left, обрезающий все пробелы (и вообще пробельные символы) в начале заданной строки:
Следующие две операции действуют точно как s_span..., но уже от конца заданной строки:
|
s_rspan_in (String, CPred)
|
Разность между длиной строки String и количеством CPred-позитивных символов в её конце
|
|
s_rspan_ex (String, CPred)
|
Разность между длиной строки String и количеством CPred-негативных символов в её конце
|
Почему возвращается именно такое значение? Потому, что интересна (как правило) не сама длина конечной последовательности символов – а индекс (относительно начала строки), с которого она начинается! В граничных случаях: s_rspan_in возвращает длину String (если CPred-негативен самый последний символ в String) или 0 (когда ВСЕ символы в строке CPred-позитивны). Для s_rspan_ex "негативность" и "позитивность" меняются местами. И теперь нетрудно определить trim_right, обрезающий все пробельные символы в конце заданной строки:
В завершение, просто trim – "обрезает" заданную строку и в начале, и в конце:
Все следующие функторы действуют не в начале или в конце строки, а по всей длине строки.
|
s_filter_in (String, CPred)
|
Фильтрация (позитивная): возвращает String без CPred-негативных символов
|
|
s_filter_ex (String, CPred)
|
Фильтрация (негативная): возвращает String без CPred-позитивных символов
|
Эти функторы "выбрасывают" из строки все CPred-негативные (для s_filter_in) или CPred-позитивные (для s_filter_ex) символы, возвращая строку-результат. Если в строке вообще нет CPred-позитивных символов, s_filter_ex просто возвращает саму строку, а s_filter_in – пустую строку. Следующий пример "выбрасывает" из строки String вообще все пробельные символы:
А вот в этом примере, из строки "выбрасываются" не только пробелы, а вообще всё, кроме алфавитных символов:
Наконец, для этих функторов, результатом является не строка, а целое число:
|
s_count_in (String, CPred)
|
Счётчик (позитивный): сколько CPred-позитивных символов в String?
|
|
s_count_ex (String, CPred)
|
Счётчик (негативный): сколько CPred-негативных символов в String?
|
Для любых строки String и предиката CPred действуют очевидные тождества:
Встроенных в язык "символьных классов" бывает недостаточно. Но нетрудно определять и свои собственные:
|
cc_incl (String)
|
Возврашается ссылка на предикат, проверяющий символ-операнд на присутствие в строке String
|
|
cc_excl (String)
|
Возврашается ссылка на предикат, проверяющий символ-операнд на отсутствие в строке String
|
Мы впервые встречаем нечто трудновообразимое для императивных языков: функторы, результатом которых также являются функторы (точнее говоря, ссылки на них), создаваемые динамически прямо при выполнении программы! Разумеется, они все анонимны: их можно вызвать только через эти ссылки, которые должны быть где-то сохранены (например, в переменной):
Мы присвоили переменной check_vowel предикат, проверяющий: является ли символ-операнд гласной буквой? (Для английского языка: в большинстве европейских языков списки допустимых гласных подлиннее!) Для согласных потребовался бы более длинный список, но тут можно немного схитрить: определим "согласные" как все символы латинского алфавита (cc_alpha-позитивные), которые при этом НЕ являются гласными:
Теперь можно это всё проверить. Возьмём известную фразу "The quick brown fox jumps over the lazy dog!", и исключим из неё сперва все гласные, а потом все согласные:
Fox_Dog = "The quick brown fox jumps over the lazy dog!"; <: (s_filter_ex (Fox_Dog, check_vowel), "\n"); <: (s_filter_ex (Fox_Dog, check_consonant), "\n");
И получим:
Th qck brwn fx jmps vr th lz dg! e ui o o u oe e ay o!
Заметьте, что из строки исключается только то, что надо (пробелы, пунктуация и пр. остаются нетронутыми). Если строка-операнд для cc_incl состоит только из одного символа, сравнение осуществляется только с ним. Если операндом является пустая строка – то cc_incl ("") и cc_excl ("") являются просто синонимами для !false и !true.
Для строк, как и для списков, определены свои итераторы (очень похожие функционально на l_loop и l_loop_r):
|
s_loop (Var, String, @Loop)
|
Итератор (прямой) по символам строки String: для всех символов (от первого до последнего) присваивает переменной Var код символа и выполняет Loop |
|
s_loop_r (Var, String, @Loop)
|
Итератор (обратный) по символам строки String: для всех символов (от последнего до первого) присваивает переменной Var код символа и выполняет Loop |
Как и прочие итераторы, они возвращают значение: результат последнего вычисления Loop. Эти итераторы чаще всего полезны (например) для вычисления контрольной суммы (или, как в примере ниже, контрольного "исключающего ИЛИ") для всех кодов в строке:
! string_XOR (Str) : [Code Value] = {
Value = 0;
s_loop (Code, Str, Value =~: Code);
Value };
Для уже определённой строки Fox_Dog – результатом string_XOR (Fox_Dog) будет 110.
Рассмотрим ещё пару полезных операций, работающих с кодами символов в строке.
|
s_map (C_Mapper, String)
|
Возвращает строку из символов, полученных применением C_Mapper к кодам символов String
|
Длина и тип строки-результата будут такие же, как у String, а вот коды его символов получаются из кодов String путём применения Mapper (который должен принимать целое число, и возвращать числовой результат). В следующем примере, мы просто генерируем строку, состоящую из символов, предшествующих оригинальным:
! prevchars (Str) = s_map (!(code) = (code - 1), Str);
Можете проверить, что prevchars ("IBM") – возвращает строку "HAL". Как многие помнят, в "Космической одиссее" Артура Кларка – главный суперкомпьютер назывался "HAL-9000". Впрочем, потом сам Кларк старательно открещивался от любых намёков на IBM (и утверждал, что это надо расшифровывать примерно как "Heuristical ALgorithic") – но в эту версию многие не верят до сих пор.
А вот другой пример: реализация алгоритма "шифрования" ROT-13. Это – простой циклический поворот латинского алфавита на 13 букв (буква 'A' переходит в 'N', буква 'B' переходит в 'O' и так далее). Понятно, что ROT-13 не предназначен для сколь-нибудь серьёзного шифрования – и применяется в основном для того, чтобы скрыть от случайного прочтения подсказки к играм, или спойлеры к сюжету. Реализовать это довольно просто:
! rot13 (String) = s_map ( ! (Char) = ( (\c'a' <= Char && Char <= \c'z') ? Char + (Char >= \c'n' ? \c'a' - \c'n' : \c'n' - \c'a') : (\c'A' <= Char && Char <= \c'Z') ? Char + (Char >= \c'N' ? \c'A' - \c'N' : \c'N' - \c'A') : Char), String);
Мы "вращаем" только заглавные и строчные латинские буквы – все прочие символы строки остаются нетронутыми. Этот алгоритм выполняет одновременно шифровку и расшифровку: его повторное применение восстанавливает строку в её оригинальном виде.
Gur dhvpx oebja sbk whzcf bire gur ynml qbt!
The quick brown fox jumps over the lazy dog!
Теперь покажем, как создать новую строку фактически с "нуля":
|
s_create (Type, Length, I_Mapper)
|
Возвращает строку типа Type (символы получаются применением I_Mapper к индексам 0..Length) |
В отличие от s_map, здесь вообще нет строки-операнда. Строка-результат генерируется полностью процедурно: с помощью функтора I_Mapper. Кроме него, параметрами являются: длина результата Length и тип результата Type (это одна из тех операций, где тип символов должен быть задан явно). При выполнении, I_Mapper вызывается последовательно (от 0 до Length-1 включительно), возвращаемые им значения являются кодами символов (т.е. он отображает не коды символов на коды, как s_map – а индексы в строке на коды). Например, вот так можно создать строку из всех десятичных цифр:
В результате получится строка "0123456789". Хотя (в данном случае) это слишком сложный способ её задания (литералом будет короче). Но вот в ситуациях, когда автоматически сгенерированная строка имеет тысячи символов, s_create трудно заменить.
В заключение, вот ещё несколько полезных встроенных строковых функторов:
|
s_retype (Type, String)
|
Строка из тех же символов, что и String (но с типом Type)
|
Тип строки явно "расширяется" или "сужается" до нового Type. Когда происходит расширение (например, с 8-битовой строки до 16-битовой), никаких проблем не возникает. В обратной ситуации, возможно тривиальное усечение некоторых кодов до заданного числа битов.
|
s_common_head (StringA, StringB)
|
Общий префикс строк StringA и StringB
|
|
s_common_tail (StringA, StringB)
|
Общий суффикс строк StringA и StringB
|
Они возвращают (соответственно) самый длинный общий фрагмент строк: в их начале (s_common_head) или в их конце (s_common_tail).
Следующие операции преобразуют регистр символов:
|
s_ucase (String)
|
Перевести всю строку String в заглавный регистр
|
|
s_lcase (String)
|
Перевести всю строку String в строчный регистр
|
|
s_icase (String)
|
Инвертировать регистр в строке String
|
Например:
<: (s_lcase (Fox_Dog), "\n"); <: (s_ucase (Fox_Dog), "\n"); <: (s_icase (Fox_Dog), "\n");
выведет:
the quick brown fox jumps over the lazy dog! THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG! tHE QUICK BROWN FOX JUMPS OVER THE LAZY DOG!
Эти операции похожи, но меняют регистр только первого символа строки (остальные остаются неизменными):
|
s_ucfirst (String)
|
Перевести первый символ строки String в заглавный регистр
|
|
s_lcfirst (String)
|
Перевести первый символ строки String в строчный регистр
|
|
s_icfirst (String)
|
Инвертировать регистр первого символа строки String
|
В большинстве скалярных операций, преобразования между числами и строками выполняется неявно. Однако, иногда полезны и явные образования – например, когда нужна система счисления, отличная от десятичной.
|
s_hex (Int)
|
Преобразует целое Int в строку 16-ричных цифр
|
|
s_dec (Int)
|
Преобразует целое Int в строку десятичных цифр
|
|
s_oct (Int)
|
Преобразует целое Int в строку 8-ричных цифр
|
|
s_bin (Int)
|
Преобразует целое Int в строку двоичных цифр
|
|
s__base (Int, Base)
|
Преобразует целое Int в строку цифр по основанию Base
|
Последняя операция – преобразует число Int в строку цифр, используя систему счисления с основанием Base. Для цифр, больших 9, используются буквы от 'A' (10) до 'Z' (35) – максимальное значение Base равно 36. Прочие операции – просто синонимы для s__base с фиксированным основанием.
Эти операции выполняют обратные преобразования:
|
n_hex (String)
|
Преобразует 16-ричную строку String в целое
|
|
n_dec (String)
|
Преобразует десятичную строку String в целое
|
|
n_oct (String)
|
Преобразует 8-ричную строку String в целое
|
|
n_bin (String)
|
Преобразует двоичную строку String в целое
|
|
n__base (String, Base)
|
Преобразует строку цифр по основанию Base в целое
|
В начале строки из цифр допустима любая последовательность пробельных символов, которые игнорируются.