Перейти к содержанию

Ruby/Подробнее о строках

Материал из Викиучебника — открытых книг для открытого мира

Подробнее о строках

[править]

Строка — это упорядоченная последовательность символов, которая располагается между ограничительными символами.

Строковый тип является самым популярным в любом языке программирования. Ведь без него невозможно написать любую программу (особенно учитывая, что любая программа — это строка). При выводе на экран или записи в файл, любой тип данных преобразуется к строке (явно или неявно). Это значит, что в конечном итоге всё сводится к строковому типу. Кстати, и ввод данных тоже осуществляется в виде строки (и только потом преобразуется в другие типы).

Информация

Студенты четвёртого курса МЭТТ ГАИ поступили на подготовительные курсы в МГИУ. Там им начали преподавать основы программирования на Ruby. И одна из заданных им задач была: «Дано число, необходимо поменять порядок цифр на обратный». Задача сложная, но наши студенты об этом не знали и решили её преобразованием к строке: given.to_s.reverse. Преподаватели были поражены и впредь запретили им использовать преобразования к строке в своих программах. И всё потому, что это сильно упрощало решение и давало студентам огромное преимущество перед остальными слушателями курсов.

Язык Ruby унаследовал работу со строками из языка Perl (признанного лидера по работе со строками). В частности такой мощный инструмент как правила (rules).

Но наследование не подразумевает бездумного копирования. В частности, правила, в рамках Ruby, получили объектно-ориентированную реализацию, что позволяет применять к ним различные методы. Помимо правил, присутствует великое множество методов работы со строками. Причём некоторые из них являются нашими старыми знакомыми (+, *, [] и так далее). Работают они несколько иначе, но некоторая параллель с массивами все же присутствует. Следует упомянуть два очень интересных момента:

  • Cтроки — это универсальный тип данных, так как в строку можно преобразовать любой другой тип данных. Также строку можно преобразовать в любой другой тип данных (ведь изначально любой код программы — это строка).
  • Cтроки очень удобно преобразовывать в массив и обратно (методы .join и .split). Поэтому работа со строками практически столь же удобная, как и с массивами.

Если работа со строками обходится без преобразования в массив, то программа либо очень простая, либо бесполезная.

Способы создания строки

[править]

Строка создаётся при помощи ограничительных символов. Для этих целей чаще всего используются " (программистская кавычка) и ' (машинописный апостроф). Их смысл различен. Строка в апострофах гарантирует, что в ней будет содержаться тот же текст, что и в коде программы, без изменений. Строка в кавычках будет проходить предварительное преобразование. Будут раскрыты конструкции «вставка» и «специальный символ».

Давайте будем называть строки в апострофах «ленивыми», а строки в кавычках — «работящими».

Вставка — это хитрая конструкция, которая вставляется между ограничительными символами внутри строки. Она состоит из комбинации октоторпа и двух фигурных скобок (#{ 'здесь был Вася' }). Внутри неё можно писать не только 'Здесь был Вася', но и любой программный код. Результат программного кода будет преобразован к строке и вставлен на место вставки.

Вставка жизнью заправляет:
Код программный выполняет,
Тихо результат считает,
Вместо вставки подставляет.

«Вставка» работает только в момент создания строки́. После создания придётся придумывать другие способы подстановки данных в стро́ки.

Специальный символ начинается со знака \ (обратная косая черта). Самые популярные из них: \n (переход на новую строку), \t (табуляция), \\ (обратная косая черта) и \" (двойная кавычка).

Хотя специальный символ и пишется, как два знака, но на деле это всего один символ. Доказать это можно выполнением простенького кода: "\n".size #=> 1.

Для чего нужны работящие и ленивые строки?

[править]

Скорее всего вы будете редко вспоминать про то, что существуют работящие и ленивые строки. Тем более, что это различие действительно только на момент создания строки. Рядовой программист пользуется либо работящими, либо ленивыми строками. Давайте посмотрим, как выглядит код программиста, который использует только ленивые строки:

my_number = 18
my_array  = [1, 2, 3, 4]
puts 'Моё число = ' + my_number.to_s + ', а мой массив длины ' + my_array.size.to_s

Обратите внимание, что перед сцеплением (умные дяди называют это конкатенацией) необходимо все данные преобразовывать к строке методом .to_s. Вставка позволяет этого избежать. Вот, как будет выглядеть та же самая программа с использованием вставки:

my_number = 18
my_array  = [1, 2, 3, 4]
puts "Моё число = #{my_number}, а мой массив длины #{my_array.size}"

Программа стала не только меньше, но и лучше читаться. Исчезли многочисленные сцепления.

Если внутри вставки надо создать строку, то экранировать кавычки не сто́ит. Внутренности вставки не являются частью строки́, а значит живут по своим законам.

my_array = [1, 2, 3, 4]
puts "Повторенье — мать ученья. Мой массив = #{my_array.join(\", \")}"

Программа вызовет ошибку, так как внутри вставки было использовано экранирование кавычек. Правильный пример будет выглядеть так:

my_array = [1, 2, 3, 4]
puts "Повторенье — мать ученья. Мой массив = #{my_array.join(", ")}"
Информация

Нет необходимости в экранировании символов внутри вставки.

Методы работы со строками

[править]

Методы строк умеют:

  • преобразовывать входные данные в красивый вид,
  • красиво оформить выходные данные,
  • дезертировать в массивы.

Допустим, вы нашли максимальный элемент массива. И вам надо вывести результат на экран. Вы можете поступить вот так:

array = [4, 4, 2, 5, 2, 7]
puts array.max    #=> 7

Несмотря на правильность решения, вывод результата выглядит некрасиво. Гораздо профессиональней будет написать вот такую программу:

array = [4, 4, 2, 5, 2, 7]
puts "Максимальный элемент array = #{array.max}"    #=> Максимальный элемент array = 7

Заметили, насколько привлекательней стал вывод результата, когда мы задействовали стро́ки?

Заниматься оформлением выходных данных сто́ит только непосредственно перед выводом результата. В остальное время использование оформительских элементов будет только мешать.

Всем известно, что чи́сла внутри компьютера хранятся в двоичной системе. Но вот парадокс: получить двоичное представление числа иногда очень сложно. В частности, для того, чтобы получить двоичную запись числа, необходимо создать строку и записывать результат в неё. Это значит, что результатом преобразования в другую систему счисления (из десятичной) будет строка. Давайте посмотрим, как эта задача решается:

my_number = 123
puts "В двоичном виде: %b" % my_number    #=> В двоичном виде: 1111011

Мы задействовали метод % (аналог sprintf в Си), который осуществляет форматирование строки́. Эту же задачу можно решить несколько иначе.

my_number = 123
puts "В двоичном виде: #{my_number.to_s(2)}"    #=> В двоичном виде: 1111011

Со вторым вариантом вы могли уже́ встречаться в разделе, посвящённом числам (подраздел Хитрости).

Методы sprintf и printf в Ruby также присутствуют, но используются крайне редко. Чаще всего они заменяются на методы % и puts.

Арифметика строк

[править]

Кто бы мог подумать, но строки можно складывать, умножать и, если постараться, то и делить. Естественно, что это не арифметические операции чисел, а методы со своим особенным поведением. Рассмотрим сложение, которое в строках работает как сцепление (конкатенация):

boy = "Саша"
girl = "Маша"
puts boy + " + " + girl + " = любовь!"    #=> "Саша + Маша = любовь!"
# или так:
puts "#{boy} + #{girl} = любовь!"    #=> "Саша + Маша = любовь!"

Вот такое вот романтическое сцепление сердец!

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

years_left = 100
"Ку-ку! " * years_left
    #=> "Ку-ку! Ку-ку! Ку-ку! Ку-ку! Ку-ку! Ку-ку! Ку-ку! … Ку-ку! Ку-ку! "

Теперь за своё будущее можно не беспокоиться! Хотя нам может попасться неграмотная кукушка…

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

cuckoo_talk = "Ку-ку! Ку-ку! Ку-ку! Ку-ку! Ку-ку! Ку-ку! Ку-ку! … Ку-ку! Ку-ку! "
(cuckoo_talk / " ").size    #=> 10

В нашем примере метод .size считает число элементов массива, который получился в результате деления.

Скобки нужны для того, чтобы вызвать .size от результата деления, а не от " ".

Деление в примере работает также как и метод .split, о котором речь пойдёт чуть позже. А чтобы оно заработало у вас, необходимо добавить небольшой код в начало программы:

class String
    alias / :split
end

Этот код как раз и говорит о том, что деление и .split — одно и тоже.

Деление стало нужно, когда метод * для массивов получил возможность работать, как .join (преобразовать массив в строку, расположив элементы через разделитель). В виду того, что .join и .split работают вместе точно также, как умножение и деление, то появилась идея заставить работать деление как .split.

Преобразование в массив или путешествие туда и обратно

[править]

Случилось так, что итераторы в строках работают настолько неуклюже, что рассмотрение их в рамках учебника будет пропущено. Следовательно, для того, чтобы задействовать механизм итераторов, мы будем преобразовывать строки в массивы. После того, как итераторы нам станут не нужны, то мы вернёмся к строкам.

"Ку-ку".split('-')    #=> ["Ку", "ку"]
"Ку-ку".split('y')    #=> ["К", "-к"]

Обратите внимание, что разделитель из результата исчезает.

За преобразование строки́ в массив отвечает метод .split. Ему передаётся разделитель, по которому будет происходить деление строки на элементы. В нашем случае это '-'. Теперь вернём всё на место. Для этого мы будем использовать метод .join из арсенала массивов:

["Ку", "ку"].join('-')     #=> "Ку-ку"
["Ку", "ку"].join(', ')    #=> "Ку, ку"
["Ку", "ку"].join          #=> "Куку"
["Ку", "ку"].to_s          #=> "Куку"

Кукушка кукует,
строку генерит.
Из строчки массив
Получу через .split.

  • Разделитель будет вставлен между элементами исходного массива.
  • Метод .join, вызванный без разделителя, даёт такой же результат, как и метод .to_s.

Чуть ранее мы упоминали, что можно использовать умножение вместо .join. Давайте посмотрим, как это выглядит:

["Ку", "ку"] * '-'    #=> "Ку-ку"
["Ку", "ку"] * 3      #=> ["Ку", "ку", "Ку", "ку", "Ку", "ку"]

Если умножать массив на целое число, то будет размножение массива (прямо как умножение в строках), а не преобразование в строку.

Длина строки

[править]

Длина строки определяется точно также, как и длина массива или хеша, то есть методом .size:

"Во дворе дрова, а в дровах трава!".size    #=> 33
"Три".size                                  #=> 3
Информация

Следует помнить, что пробел является символом, хотя и не отображается на экране.

Информация

Ограничительные кавычки или апострофы в количество символов не входят! Они предназначены лишь для того, чтобы Ruby понял, где начинается и заканчивается строка.

Получение подстрок

[править]

Получение подстрок работает точно также, как и получение подмассива. С тем лишь отличием, что нумерация идёт не по элементам, а по символам. Это логично, особенно если учесть, что для строки элементом является символ.

string = "Во дворе дрова, а в дровах трава!"
string[27..-1]     #=> "трава!"
string[27...-1]    #=> "трава"
string[9...14]     #=> "дрова"
string[9..13]      #=> "дрова"

Отрицательная нумерация тоже работает, то есть последний элемент имеет индекс -1, предпоследний -2 и так далее.

Помните, что три точки в диапазоне дают подстроку без крайнего правого элемента. Кто-то шлагбаумом балуется!

Чтобы получить один единственный символ, необходимо получить подстроку длины 1.

string = "Во дворе дрова, а в дровах трава!"
string[3..3]    #=> "д"
string[3]       #=> 190

Для получения подстроки или символа из строки необходимо всегда указывать диапазон. Даже если в диапазоне всего один элемент.

Если мы указываем не диапазон, а число, то метод [] выдаёт нам не символ, а его целочисленный код.

string = "Во дворе дрова, а в дровах трава!"
string[3]     #=> 190
string[3].chr #=> "д"

Для преобразования целочисленного кода обратно в символ, используется метод .chr.

В последних версиях Ruby код string[3] уже выдает искомую "д" и без использования метода .chr. Но - зачем рисковать, если можно сразу указать точный диапазон?

Строка-перевёртыш

[править]

Иногда хочется перевернуть строку задом наперед. Причины могут быть разные. Например, вы ищете палиндром (число, которое можно перевернуть без ущерба для его значения). Занимается этим благородным делом метод .reverse. Узнаем настоящее имя Алукарда из аниме Hellsing:

"Алукард.".reverse
    #=> ".дракулА"


Не стоит путать метод .reverse для массивов с методом .reverse для строк. В массивах меняется порядок элементов, а в строках — символов.

Меняю шило на мыло!

[править]

Для того, чтобы заменить "шило" на "мыло" используется не газета «Из рук в руки», а методы .sub и .gsub:

"шило в мешке не утаишь".sub("шило", "мыло")
    #=> "мыло в мешке не утаишь"

Естественно, что менять можно не только шило и мыло, но и другие данные. Например, возраст. Девушка Ирина утверждает, что ей 18, но мы-то знаем, что ей 26. Давайте восстановим истину, возможно — в ущерб своему здоровью:

"Ирине 18 лет.".sub("18", "26")    #=> "Ирине 26 лет."

Заметили, что мы используем только метод .sub? Давайте теперь рассмотрим работу метода .gsub и его отличие от .sub. На этот раз мы будем исправлять текст, авторы которого — недоучки ( или белорусы ), забывшие про правило «Жи, Ши — пиши через 'и'»

string = "жыло-было шыбко шыпящее жывотное"
string.sub("жы", "жи")                      #=> "жило-было шыбко шыпящее жывотное"
string.gsub("жы", "жи")                     #=> "жило-было шыбко шыпящее животное"
string.gsub("жы", "жи").gsub("шы", "ши")    #=> "жило-было шибко шипящее животное"

Метод .sub производит только одну замену, а .gsub — все возможные.

Название метода .sub произошло от английского «substitute» — «замена», созвучное же название метода .gsub отличается от него только словом «global».

Сканируем текст на ошибки

[править]

Давайте найдем и посчитаем ошибки. Искать мы будем методом .scan.

string = "жыло-было шыбко шыпящее жывотное"
string.scan("шы")                                  #=> ["шы", "шы"]
string.scan("шы").size                             #=> 2
string.scan("жы").size                             #=> 2
string.scan("жы").size + string.scan("шы").size    #=> 4

Метод .scan находит все указанные подстроки и возвращает их в виде массива строк. В данном примере, метод .size считает количество элементов массива, возвращаемого .scan.

Ужас, в одном предложении целых четыре ошибки. Будем отчислять!

Нашел ошибку метод .scan,
В массив её запомнил.
Учителям он свыше дан!
Зачем его я вспомнил?!

Правила (они же регулярные выражения) работы со строками.

[править]

Правила — это образцы к которым можно примерять строки. Правила обладают своим собственным языком, который позволяет описывать одну, две, сотню и вообще любое количество строк. Это своеобразная упаковка для множества строк в одну компактную запись.

Правила в Ruby ограничиваются символами / (косая черта). Примеры правил:

/(жы|шы)/
/\w+@[\w\.]+\w+/i

Страшно? А зря. На самом деле работа с правилами очень проста. Главное привыкнуть и попрактиковаться.

Составные части правил:

Символьные классы
Перечисление символов, которые может содержать строка.
Квантификаторы
Количество символов.
Альтернативы
Перечисление всевозможных вариантов.
Группировки
Возможность выделить несколько групп, которые могут обрабатываться отдельно.
Модификаторы
Изменение поведения правила. Например, игнорирование регистра символов.

Правила в рамках учебника будут описаны очень сжато. Многие тонкости освещены не будут, поэтому для освоения «фигур высшего пилотажа» необходимо прочитать специализированную литературу. Например, книгу «Регулярные выражения»

Символьный класс.
[править]

Символьный класс — просто конечный набор символов. Он ограничивается квадратными скобками и содержит перечисление символов, которые можно вместо него подставить. Заменяется он всего на один символ, входящий в этот класс. Примеры символьных классов:

/[абвгде]/    #=> простое перечисление символов
/[а-яА-ЯЁё]/  #=> все русские буквы
/[0-9a-z]/    #=> цифры и строчная латиница
/[^0-9]/      #=> все символы, кроме цифр

Замечание: В таблице Unicode символы 'Ё' и 'ё' стоят немного отдельно от остальных символов русского алфавита.

  • Можно использовать - (дефис) для указания диапазонов символов.
  • Если первый символ класса (идущий сразу после открывающейся квардратной скобки) — ^ (циркумфлекс), то это означает символ, который отсутствует в данном классе.
  • Некоторые популярные классы имеют короткую запись.
Короткие записи популярных символьных классов
Короткая
запись
Полная
запись
Описание
\s [\f\t\n\r] Пробельный символ
\S [^\f\t\n\r] Любой символ, кроме пробельного
\d [0-9] Цифра
\D [^0-9] Любой символ, кроме цифры
\w [a-zA-Z0-9] Латиница или цифра
\W [^a-zA-Z0-9] Любой символ, кроме латиницы или цифры
. [^\n\r] Любой символ, кроме перевода строки
\b Граница слова
\B Не граница слова
\A Начало строки
\Z Конец строки

Взгляните на примеры правил! Правда, они стали понятней? По крайней мере второе…

Квантификатор
[править]

Показывает, сколько раз может повторяться предыдущий символ, группа, альтернатива, etc. Квантификатор ограничивается фигурными скобками.

Примеры квантификаторов:

/\w{3}/           #=> три латинских буквы или цифры
/\d{1, 3}/        #=> одна, две или три цифры
/[а-яА-ЯЁё]{3,}/  #=> русское слово длиной три символа и больше
  • Квантификатор с одним параметром называется точным и указывает точное количество повторений.
  • Квантификатор с двумя агрументами называется конечным и указывает конечный диапазон, в котором варьируется количество повторений.
  • Квантификатор без второго параметра (но с запятой) называется бесконечным и ограничивает количество повторений лишь снизу.
  • Некоторые популярные квантификаторы имеют короткую запись.
Короткие записи популярных квантификаторов
Короткая
запись
Полная
запись
Описание
* {0, } Любое количество
+ {1, } Один и более
? {0, 1} Есть или нет

Снова посмотрите на примеры правил. Теперь вам они понятны? Если нет, то перечитайте две предыдущие главы — в них основа правил.

Альтернатива
[править]

Альтернатива нужна, когда необходимо объединить несколько правил в одно. При этом совпадение засчитывается, когда есть совпадение хотя бы с одним правилом. Желательно альтернативу заключать внутрь группировки (круглые скобки). Правила, входящие в альтернативу, разделяются | (вертикальной чертой, которая и является альтернативой). Примеры альтернатив:

/(жы|шы)/            #=> или "жы", или "шы"
/(\w+|[а-яА-Я]+)/    #=> или слово на латинице, или русское

Вместо альтернативы можно задействовать логические итераторы .any? и .all? внутри .inject. Получается более гибкая конструкция.

В данном примере продемонстрирована альтернатива с группировкой. В принципе альтернатива может существовать и без неё, но так возникает меньше ошибок у начинающих.

Группировка
[править]

Группировка используется, когда необходимо обрабатывать результат частями. Например, при обработке ссылок в HTML-документе удобно отдельно обрабатывать текст ссылки и URL. Группировка также как и альтернатива, заключается в круглые скобки. Более того, альтернатива обрабатывается как группировка. Доступ к результату совпадения каждой группировки осуществляется посредством специальных переменных $1, $2, …, $9. Подробнее группировки будут рассмотрены в подразделе «Правильная замена». Пример использования группировки:

"2+7*3".gsub(/(\d+)\*(\d+)/){ $1.to_i * $2.to_i }    #=> "2+21"

Почти калькулятор!

Существует много видов группировок. Например, (?:…) — группировка без сохранения результата в «долларовую переменную» или (?!…) — негативная группировка. В любом случае они ограничиваются парой круглых скобок.

Фиксирующая директива
[править]

Фиксирующие директивы — это символы, которые привязывают правило к некоторому признаку. Например, к концу или началу строки.

/^\d+/    #=> строка начинается с числа
/\w+$/    #=> последнее слово на латинице или число
/^$/      #=> пустая строка

Насколько видно из примеров,

  • ^ — привязка к началу строки,
  • $ — привязка к концу строки.

Фиксирующих директив гораздо больше двух. Об остальных читайте в специализированной литературе.

Модификатор
[править]

Модификатор предназначен для изменения поведения правила. Он размещается сразу же после правила (после последней наклонной черты). Пример использования модификатора:

/(hello|world)/i    #=> или "hello", или "world". Причём независимо от регистра
/\s+/mix            #=> несколько подряд идущих пробельных символов

Бывают следующие модификаторы:

  • multiline — перенос строки считается простым символом,
  • ignorcase — поиск без учёта регистра,
  • extended — игнорировать пробельные символы.

Игнорирование регистра работает только для латиницы.

  • Можно применять любое количество модификаторов и в любом порядке.
  • Обратите внимание, что модификаторы образуют слово mix.

Теперь можно не бояться страшных правил.

Правильное разбиение

[править]

Разбиение называется «правильным» тогда, когда в качестве аргумента метода .split используется правило. Например, можно разбить текст по знакам препинания. Для этого необходимо выполнить следующий код.

"Раз, два, три!".split(/[, \.?!]+/)    #=> ["Раз", "два", "три"]

Обратите внимание, что в результирующем массиве знаки препинания отсутствуют.

Правильная замена

[править]

С правильной заменой не всё так просто. Дело в том, что методы .sub и .gsub совместно с правилами становятся итераторами, которые последовательно обрабатывают каждое совпадение с правилом. Чтобы это увидеть в действии, давайте решим задачу исправления ошибок:

"Жыло-было шыбко шыпящее жывотное".gsub(/(ж|ш)ы/){ $1 + "и" }
    #=> "Жыло-было шибко шипящее животное"

Опаньки, а первое слово не исправилось! Видимо дело в том, что слово Жыло начинается с прописной буквы. Сейчас исправим:

"Жыло-было шыбко шыпящее жывотное".gsub(/(Ж|Ш|ж|ш)ы/){ $1 + "и" }
    #=> "Жило-было шибко шипящее животное"

Вот, теперь гораздо лучше. Как мы этого добились? Давайте разберёмся. Начнём с регулярного выражения:

/(Ж|Ш|ж|ш)ы/

Оно состоит из двух частей:

  • альтернативы с группировкой — (Ж|Ш|ж|ш),
  • символа — ы.

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

Зачем была использована группировка? Для пояснения причины, рассмотрим код в фигурных скобках:

{ $1 + "и" }

Вот для того, чтобы можно было использовать переменную $1 (результат первой группировки) мы и задействовали группировку. В данном случае, в $1 сохраняется первая буква слога, которая в результате исправления оШЫбки не меняется.

  • Для того, чтобы получить доступ к результату первой группировки, надо обратиться к переменной $1 (один доллар), ко второй — $2 (два доллара) и так далее до переменной $9 (девять долларов).
  • Переменные $1$9 заимствованы из языка Perl.

Можно ли было решить эту же задачу иначе? Конечно можно!

"Жыло-было шыбко шыпящее жывотное".gsub(/([ЖШжш])ы/){ $1 + "и" }
    #=> "Жило-было шибко шипящее животное"

На этот раз мы просто задействовали символьный класс вместо альтернативы, который описывает первую букву слога с оШЫбкой.

Есть ещё пару интересных моментов, которые вам необходимо знать. Во время предыдущего примера вас могли посетить следующие вопрос: а как получить весь текст, который совпал с правилом? Неужели необходимо делать всеобщую группировку?

Ответ на этот вопрос однозначный — нет! Достаточно придумать название переменной (которая будет содержать совпавший текст) и правильно описать внутри ушек:

"Раз, два, три!".gsub(/[а-я]+/){ |word| word.reverse }
    #=> "заР, авд, ирт!"

Правильный поиск

[править]

Вот здесь метод .scan может развернуться в полную силу. Хотите получить массив всех русских слов в тексте? Запросто:

"Раз, два, три!".scan(/[А-Яа-я]+/)    #=> ["Раз", "два", "три"]

Хотите получить все знаки препинания? Нет ничего проще:

"Раз, два, три!".scan(/[, \.;:!]+/)    #=> [", ", ", ", "!"]

Если необходимо в метод .scan передавать правило с группировкой, то желательно использовать группировку без сохранения результата, то есть (?:…). Иначе результатом метода .scan будет совпадение с группировкой, а не с правилом.

Например, ниже записана программа, которая занимается поиском адресов электронной почты.

string = "495-506-13 56 nata@rambler.ru(34) 1.5.1232 12.14.56 31.декабря.9999"
string.scan(/(?:[-a-z_\d])+@(?:[-a-z])*(?:\.[a-z]{2,4})+/)    #=> ["nata@rambler.ru"]

Выполните её, посмотрите результат, а потом замените любую из группировок (?:…) на (…) и снова взгляните на результат.

Ну со .scan должно быть всё понятно. А вот то, что метод [] начинает тоже правильно искать — пока нет.

"Раз, два, три!"[/[А-Яа-я]+/]    #=> "Раз"

Если методу [] передать в качестве параметра правило, то он вернёт либо совпадение с правилом, либо nil.

Очень полезно использовать [] в ситуациях, когда надо узнать ответ на вопрос «есть хотя бы одна подстрока, которая удовлетворяет правилу?» или получить первое (или единственное) совпадение с правилом.

Существует древнее поверье, что если использовать одно и тоже правило для .scan и .split, то получаются две части текста, из которых реально получить исходный.

Text.scan(rule) + Text.split(rule) = Text

Это значит, что если метод .split использует правило, описывающие все знаки припинания, то результатом будет текст без знаков припинания. А вот если это же правило будет использовать метод .scan, то в результате мы получим все знаки препинания без текста.

Рекомендуется использовать метод [] вместо метода =~ (заимствованного из Perl), так как [] более функционален.

Жадность

[править]

Речь пойдёт о жадности среди квантификаторов. Возьмем некоторый квантификатор {n, m} и посмотрим как он работает.

Информация

Нежадные квантификаторы иногда называют щедрыми.

Сперва он начинает искать последовательность длины m (вот так жадность), и если правило не срабатывает, он начинает уменьшать длину последовательности вплоть до n. Так работают обычные жадные кванторы.

Но иногда жадные кванторы не могут справиться с задачей. Например, в файле на языке HTML мы осуществляем поиск ссылок (тег <a>). Правило с жадным квантором найдёт начало первой ссылки и конец последней. Весь остальной текст оно примет за текст ссылки. Понятно, что работать верно оно будет только в двух случаях: когда в тексте нет ссылок или когда ссылка только одна.

Для решения вышеописанной проблемы и был придуман так называемый щедрый квантификатор. От жадного он отличается обратным ходом обработки, то есть длину последовательности он не уменьшает от m к n, а наоборот, увеличивает от n до m. Научить щедрости квантификатор можно знаком вопроса ? после любого жадного квантификатора.

"Раз, два, три!".scan(/[А-Яа-я]+?/)
    #=> ["Р", "а", "з", "д", "в", "а", "т", "р", "и"]
"Жуй жвачку, жывотное!".gsub(/([жЖшШ]??)ы/){ $1 + 'и' }
    #=> "Жуй жвачку, животное!"
Информация

С рождения квантификаторы жадные. Щедрость — обретаемый признак.

Информация

На самом деле, жадный квантификатор называется жадным (или - максимальным) потому, что он пытается забрать все себе, а щедрый (минимальный) - стремится отдать все другим. И потому с рождения квантификаторы жадны, а щедрость — обретаемый признак. Подробнее вы можете узнать об этом в книге Джона Фридла «Регулярные выражения»

Хитрости

[править]

Перенос по словам

[править]

Несколько лет назад (ещё при жизни http://ruby-forum.ru) решали мы интересную задачу: как реализовать автоматический перенос на новую строку (wrap). Для тех, кто не застал те времена, уточню задание: дан текст, необходимо, вставить переносы таким образом, чтобы каждая из полученных строк была меньше n (для определённости n = 80). Недавно я осознал, что не могу решить эту задачу тем способом, который был нами тогда найден. Я его просто не помнил… Решение нашлось быстро, достаточно было вспомнить, что на английском языке эта задача именуется коротким и ёмким словом wrap.

class String
    def wrap(col = 80)
        gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}})/, "\\1\\3\n")
    end
end

Немного о структуре кода. Метод .wrap реализован для экземпляров класса String. Также стоит обратить внимание на то, что внутри правила (регулярного выражения) возможна «вставка» (как в «рабочих строках»). Используется сей метод следующим образом:

p "wrapping text with regular expressions".wrap(10)
    #=> "wrapping\ntext with\nregular\nexpression\ns\n"

Теперь давайте разберёмся с правилом. Чтобы не смущать неокрепшие умы, заменим вставку на 80. Правило станет короче и менее страшным.

(.{1, 80})( +|$\n?)|(.{1, 80})

Очевидно, что оно состоит из четырёх частей:

  • (.{1, 80}) — строка длиной от 1 до 80 символов (любых). Результат группировки записывается в $1 (один доллар) или "\\1".
  • ( +|$\n?) — пробелы или конец строки. Результат группировки записывается в $2 (два доллара) или "\\2". Обратите внимание на запись $\n?, которая означает «конец строки ($), после которого может идти перенос (\n)». Обратите внимание, что $2 мы не используем и поэтому можно использовать (?:) (группировку без сохранения результата).
  • | — или.
  • (.{1, 80}) — строка длиной от 1 до 80 символов (любых). Результат группировки записывается в $3 (три доллара) или "\\3".

В результате работы этого правила произойдёт сопоставление с группировками 1 и 2 или 3. В первом случае будет обрабатываться строка, слова в которой по длине не превышают 80. Во втором случае строка будет принудительно усечена до 80 символов. Другими словами, мы пытаемся сделать перенос по словам, но если у нас не получается, то мы будем делать перенос так, как у нас получится.

Представленное решение не идеально. В частности, слова с дефисом хорошо бы переносить именно по дефису (при этом оставляя его на первой строке). Про остальные изыски (перенос по слогам и так далее) вы можете догадаться самостоятельно. Эта задача может решаться бесконечно, предусматривая всё большее количество различных вариантов. Плюс ко всему, возможны комбинации с другими задачами (найти длину самого длинного слова и осуществить перенос по этой длине).

Источник (англоязычный): http://macromates.com/blog/archives/2006/06/28/wrapping-text-with-regular-expressions/

Методы преобразования к строке

[править]

Ruby сам преобразует типы для некоторых простых операций. Например, при включении строки в другую он воспользуется имеющимся у объекта методом .to_s:

class Container
  def to_s
     "контейнер"
  end
end

cont = Container.new

p "Это #{cont}"    #=> "Это контейнер"

Если нужно, чтобы ваши объекты упорядочивались и сравнивались с обычными строками, следует применять примесь Comparable и единственный специальный метод to_str. Наличие этого метода у вашего объекта — знак для Ruby, что для сравнения следует применять не встроенный в String метод, а ваш.

class Container
    include Comparable
    def to_str
        "контейнер"
    end

    def to_s
        "контейнер"
    end

    def <=>(other)
        to_s <=> other.to_s
    end
end

cont = Container.new

"контейнер" == cont    #=> true

Задачи

[править]
  1. Дана строка слов, разделёных пробелами. Вывести длиннейшее слово.
  2. Дана строка, содержащая кириллицу, латиницу и цифры. Вывести все слова, длина которых равна средней.
  3. Найти в строке первое целиком кириллическое слово.
  4. Дан текст (строка с переносами). Найти все слова, содержащие лишь три буквы «о».
    • Только для русских слов.
    • Для французских и русских слов.
    • Для любого регистра буквы «о».
  5. Найти в тексте время в формате «часы:минуты:секунды».
  6. Найти все слова без повторяющихся букв (например, «Лисп» или «Ruby», но не «Паскаль» или «Java»).
    • Только для русскоязычных слов.
    • Не учитывайте цифры в словах.
  7. Найти в тексте слова́, содержащие две прописные буквы, и исправить.
    • Решите задачу для слов и в кириллице, и в латинице.
  8. Найти в тексте даты формата «день.месяц.год».
    • Найдите дату, где день ограничен числом 31, а месяц 12. Год ограничивайте четырёхзначными числами.
    • Распознавайте месяц в виде «31.марта.2001».
  9. Дан текст. Найдите все URL адреса и вычлените из них ссылку на корневую страницу сайта (например, из http://ru.wikibooks.org/wiki/Ruby сделайте http://ru.wikibooks.org).