Ruby/Подробнее о строках: различия между версиями

м
<source> -> <syntaxhighlight> (phab:T237267)
м (<source> -> <syntaxhighlight> (phab:T237267))
 
Скорее всего вы будете редко вспоминать про то, что существуют работящие и ленивые строки. Тем более, что это различие действительно только на момент создания строки. Рядовой программист пользуется либо работящими, либо ленивыми строками. Давайте посмотрим, как выглядит код программиста, который использует только ленивые строки:
 
<sourcesyntaxhighlight lang="ruby">my_number = 18
my_array = [1, 2, 3, 4]
puts 'Моё число = ' + my_number.to_s + ', а мой массив длины ' + my_array.size.to_s</sourcesyntaxhighlight>
 
Обратите внимание, что перед сцеплением (умные дяди называют это конкатенацией) необходимо все данные преобразовывать к строке методом <code>.to_s</code>. Вставка позволяет этого избежать. Вот, как будет выглядеть та же самая программа с использованием вставки:
 
<sourcesyntaxhighlight lang="ruby">my_number = 18
my_array = [1, 2, 3, 4]
puts "Моё число = #{my_number}, а мой массив длины #{my_array.size}"</sourcesyntaxhighlight>
 
Программа стала не только меньше, но и лучше читаться. Исчезли многочисленные сцепления.
Если внутри вставки надо создать строку, то экранировать кавычки не сто́ит. Внутренности вставки не являются частью строки́, а значит живут по своим законам.
 
<sourcesyntaxhighlight lang="ruby">my_array = [1, 2, 3, 4]
puts "Повторенье — мать ученья. Мой массив = #{my_array.join(\", \")}"</sourcesyntaxhighlight>
 
Программа вызовет ошибку, так как внутри вставки было использовано экранирование кавычек. Правильный пример будет выглядеть так:
 
<sourcesyntaxhighlight lang="ruby">my_array = [1, 2, 3, 4]
puts "Повторенье — мать ученья. Мой массив = #{my_array.join(", ")}"</sourcesyntaxhighlight>
 
{{info|Нет необходимости в экранировании символов внутри вставки.}}
Вы можете поступить вот так:
 
<sourcesyntaxhighlight lang="ruby">array = [4, 4, 2, 5, 2, 7]
puts array.max #=> 7</sourcesyntaxhighlight>
 
Несмотря на правильность решения, вывод результата выглядит некрасиво. Гораздо профессиональней будет написать вот такую программу:
 
<sourcesyntaxhighlight lang="ruby">array = [4, 4, 2, 5, 2, 7]
puts "Максимальный элемент array = #{array.max}" #=> Максимальный элемент array = 7</sourcesyntaxhighlight>
 
Заметили, насколько привлекательней стал вывод результата, когда мы задействовали стро́ки?
Всем известно, что чи́сла внутри компьютера хранятся в двоичной системе. Но вот парадокс: получить двоичное представление числа иногда очень сложно. В частности, для того, чтобы получить двоичную запись числа, необходимо создать строку и записывать результат в неё. Это значит, что результатом преобразования в другую систему счисления (из десятичной) будет строка. Давайте посмотрим, как эта задача решается:
 
<sourcesyntaxhighlight lang="ruby">
my_number = 123
puts "В двоичном виде: %b" % my_number #=> В двоичном виде: 1111011</sourcesyntaxhighlight>
 
Мы задействовали метод <code>%</code> (аналог <code>sprintf</code> в Си), который осуществляет форматирование строки́. Эту же задачу можно решить несколько иначе.
 
<sourcesyntaxhighlight lang="ruby">my_number = 123
puts "В двоичном виде: #{my_number.to_s(2)}" #=> В двоичном виде: 1111011</sourcesyntaxhighlight>
 
Со вторым вариантом вы могли уже́ встречаться в разделе, посвящённом числам (подраздел Хитрости).
Кто бы мог подумать, но строки можно складывать, умножать и, если постараться, то и делить. Естественно, что это не арифметические операции чисел, а методы со своим особенным поведением. Рассмотрим сложение, которое в строках работает как сцепление (конкатенация):
 
<sourcesyntaxhighlight lang="ruby">boy = "Саша"
girl = "Маша"
puts boy + " + " + girl + " = любовь!" #=> "Саша + Маша = любовь!"
# или так:
puts "#{boy} + #{girl} = любовь!" #=> "Саша + Маша = любовь!"</sourcesyntaxhighlight>
 
Вот такое вот романтическое сцепление сердец!
Переходим к умножению. Умножают стро́ки только на целое число. Строка, которую умножают просто копируется указанное число раз. Давайте напишем речь для кукушки, чтобы она не сбилась и не накукукала нам слишком мало.
 
<sourcesyntaxhighlight lang="ruby">years_left = 100
"Ку-ку! " * years_left
#=> "Ку-ку! Ку-ку! Ку-ку! Ку-ку! Ку-ку! Ку-ку! Ку-ку! … Ку-ку! Ку-ку! "</sourcesyntaxhighlight>
 
Теперь за своё будущее можно не беспокоиться! Хотя нам может попасться неграмотная кукушка…
С делением совсем другая история. Сейчас его в языке просто нет, но этот недостаток уже ощущают многие. Смысл деления состоит в том, чтобы преобразовать строку в массив, разбив её по разделителю. Давайте преобразуем речь кукушки в массив и посмотрим, сколько же лет она нам насчитала:
 
<sourcesyntaxhighlight lang="ruby">cuckoo_talk = "Ку-ку! Ку-ку! Ку-ку! Ку-ку! Ку-ку! Ку-ку! Ку-ку! … Ку-ку! Ку-ку! "
(cuckoo_talk / " ").size #=> 100</sourcesyntaxhighlight>
 
В нашем примере метод <code>.size</code> считает число элементов массива, который получился в результате деления.
Деление в примере работает также как и метод <code>.split</code>, о котором речь пойдёт чуть позже. А чтобы оно заработало у вас, необходимо добавить небольшой код в начало программы:
 
<sourcesyntaxhighlight lang="ruby">class String
alias / :split
end</sourcesyntaxhighlight>
 
Этот код как раз и говорит о том, что деление и <code>.split</code> — одно и тоже.
Случилось так, что итераторы в строках работают настолько неуклюже, что рассмотрение их в рамках учебника будет пропущено. Следовательно, для того, чтобы задействовать механизм итераторов, мы будем преобразовывать строки в массивы. После того, как итераторы нам станут не нужны, то мы вернёмся к строкам.
 
<sourcesyntaxhighlight lang="ruby">"Ку-ку".split('-') #=> ["Ку", "ку"]
"Ку-ку".split('y') #=> ["К", "-к"]</sourcesyntaxhighlight>
 
Обратите внимание, что разделитель из результата исчезает.
За преобразование строки́ в массив отвечает метод <code>.split</code>. Ему передаётся разделитель, по которому будет происходить деление строки на элементы. В нашем случае это <code>'-'</code>. Теперь вернём всё на место. Для этого мы будем использовать метод <code>.join</code> из арсенала массивов:
 
<sourcesyntaxhighlight lang="ruby">["Ку", "ку"].join('-') #=> "Ку-ку"
["Ку", "ку"].join(', ') #=> "Ку, ку"
["Ку", "ку"].join #=> "Куку"
["Ку", "ку"].to_s #=> "Куку"</sourcesyntaxhighlight>
 
Кукушка кукует,<br />
Чуть ранее мы упоминали, что можно использовать умножение вместо <code>.join</code>. Давайте посмотрим, как это выглядит:
 
<sourcesyntaxhighlight lang="ruby">["Ку", "ку"] * '-' #=> "Ку-ку"
["Ку", "ку"] * 3 #=> ["Ку", "ку", "Ку", "ку", "Ку", "ку"]</sourcesyntaxhighlight>
 
Если умножать массив на целое число, то будет размножение массива (прямо как умножение в строках), а не преобразование в строку.
Длина строки определяется точно также, как и длина массива или хеша, то есть методом <code>.size</code>:
 
<sourcesyntaxhighlight lang="ruby">"Во дворе дрова, а в дровах трава!".size #=> 33
"Три".size #=> 3</sourcesyntaxhighlight>
 
{{info|Следует помнить, что пробел является символом, хотя и не отображается на экране.}}
Получение подстрок работает точно также, как и получение подмассива. С тем лишь отличием, что нумерация идёт не по элементам, а по символам. Это логично, особенно если учесть, что для строки элементом является символ.
 
<sourcesyntaxhighlight lang="ruby">string = "Во дворе дрова, а в дровах трава!"
string[27..-1] #=> "трава!"
string[27...-1] #=> "трава"
string[9...14] #=> "дрова"
string[9..13] #=> "дрова"</sourcesyntaxhighlight>
 
Отрицательная нумерация тоже работает, то есть последний элемент имеет индекс <code>-1</code>, предпоследний <code>-2</code> и так далее.
Чтобы получить один единственный символ, необходимо получить подстроку длины 1.
 
<sourcesyntaxhighlight lang="ruby">
string = "Во дворе дрова, а в дровах трава!"
string[3..3] #=> "д"
string[3] #=> 190</sourcesyntaxhighlight>
 
Для получения подстроки или символа из строки необходимо всегда указывать диапазон. Даже если в диапазоне всего один элемент.
Если мы указываем не диапазон, а число, то метод <code>[]</code> выдаёт нам не символ, а его целочисленный код.
 
<sourcesyntaxhighlight lang="ruby">string = "Во дворе дрова, а в дровах трава!"
string[3] #=> 190
string[3].chr #=> "д"</sourcesyntaxhighlight>
<!-- не работает для UTF-8 -->
 
Узнаем настоящее имя Алукарда из аниме Hellsing:
 
<sourcesyntaxhighlight lang="ruby">"Алукард.".reverse
#=> ".дракулА"</sourcesyntaxhighlight>
 
 
Для того, чтобы заменить <code>"шило"</code> на <code>"мыло"</code> используется не газета «Из рук в руки», а методы <code>.sub</code> и <code>.gsub</code>:
 
<sourcesyntaxhighlight lang="ruby">"шило в мешке не утаишь".sub("шило", "мыло")
#=> "мыло в мешке не утаишь"</sourcesyntaxhighlight>
 
Естественно, что менять можно не только шило и мыло, но и другие данные. Например, возраст. Девушка Ирина утверждает, что ей 18, но мы-то знаем, что ей 26. Давайте восстановим истину, возможно — в ущерб своему здоровью:
 
<sourcesyntaxhighlight lang="ruby">"Ирине 18 лет.".sub("18", "26") #=> "Ирине 26 лет."</sourcesyntaxhighlight>
 
Заметили, что мы используем только метод <code>.sub</code>? Давайте теперь рассмотрим работу метода <code>.gsub</code> и его отличие от <code>.sub</code>. На этот раз мы будем исправлять текст, авторы которого — недоучки, забывшие про правило «Жи, Ши — пиши через 'и'»
 
<sourcesyntaxhighlight lang="ruby">string = "жыло-было шыбко шыпящее жывотное"
string.sub("жы", "жи") #=> "жило-было шыбко шыпящее жывотное"
string.gsub("жы", "жи") #=> "жило-было шыбко шыпящее животное"
string.gsub("жы", "жи").gsub("шы", "ши") #=> "жило-было шибко шипящее животное"</sourcesyntaxhighlight>
 
Метод <code>.sub</code> производит только одну замену, а <code>.gsub</code> — все возможные.
Давайте найдем и посчитаем ошибки. Искать мы будем методом <code>.scan</code>.
 
<sourcesyntaxhighlight lang="ruby">string = "жыло-было шыбко шыпящее жывотное"
string.scan("шы") #=> ["шы", "шы"]
string.scan("шы").size #=> 2
string.scan("жы").size #=> 2
string.scan("жы").size + string.scan("шы").size #=> 4</sourcesyntaxhighlight>
 
Метод <code>.scan</code> находит все указанные подстроки и возвращает их в виде массива строк. В данном примере, метод <code>.size</code> считает количество элементов массива, возвращаемого <code>.scan</code>.
Правила в Ruby ограничиваются символами <code>/</code> (косая черта). Примеры правил:
 
<sourcesyntaxhighlight lang="ruby">
/(жы|шы)/
/\w+@[\w\.]+\w+/i</sourcesyntaxhighlight>
 
Страшно? А зря. На самом деле работа с правилами очень проста. Главное привыкнуть и попрактиковаться.
Символьный класс — просто конечный набор символов. Он ограничивается квадратными скобками и содержит перечисление символов, которые можно вместо него подставить. Заменяется он всего на один символ, входящий в этот класс. Примеры символьных классов:
 
<sourcesyntaxhighlight lang="ruby">
/[абвгде]/ #=> простое перечисление символов
/[а-яА-ЯЁё]/ #=> все русские буквы
/[0-9a-z]/ #=> цифры и строчная латиница
/[^0-9]/ #=> все символы, кроме цифр</sourcesyntaxhighlight>
 
'''Замечание''': В таблице Unicode символы 'Ё' и 'ё' стоят [[w:Кириллица_(блок_Юникода)|немного отдельно]] от остальных символов русского алфавита.
Примеры квантификаторов:
 
<sourcesyntaxhighlight lang="ruby">/\w{3}/ #=> три латинских буквы или цифры
/\d{1, 3}/ #=> одна, две или три цифры
/[а-яА-ЯЁё]{3,}/ #=> русское слово длиной три символа и больше</sourcesyntaxhighlight>
 
* Квантификатор с одним параметром называется ''точным'' и указывает точное количество повторений.
Альтернатива нужна, когда необходимо объединить несколько правил в одно. При этом совпадение засчитывается, когда есть совпадение хотя бы с одним правилом. Желательно альтернативу заключать внутрь группировки (круглые скобки). Правила, входящие в альтернативу, разделяются <code>|</code> (вертикальной чертой, которая и является альтернативой). Примеры альтернатив:
 
<sourcesyntaxhighlight lang="ruby">/(жы|шы)/ #=> или "жы", или "шы"
/(\w+|[а-яА-Я]+)/ #=> или слово на латинице, или русское</sourcesyntaxhighlight>
 
Вместо альтернативы можно задействовать логические итераторы <code>.any?</code> и <code>.all?</code> внутри <code>.inject</code>. Получается более гибкая конструкция.
Группировка используется, когда необходимо обрабатывать результат частями. Например, при обработке ссылок в [[w:HTML|HTML]]-документе удобно отдельно обрабатывать текст ссылки и [[w:URL|URL]]. Группировка также как и альтернатива, заключается в круглые скобки. Более того, альтернатива обрабатывается как группировка. Доступ к результату совпадения каждой группировки осуществляется посредством специальных переменных <code>$1</code>, <code>$2</code>, …, <code>$9</code>. Подробнее группировки будут рассмотрены в подразделе «[[#Правильная замена|Правильная замена]]». Пример использования группировки:
 
<sourcesyntaxhighlight lang="ruby">"2+7*3".gsub(/(\d+)\*(\d+)/){ $1.to_i * $2.to_i } #=> "2+21"</sourcesyntaxhighlight>
 
Почти калькулятор!
Фиксирующие директивы — это символы, которые привязывают правило к некоторому признаку. Например, к концу или началу строки.
 
<sourcesyntaxhighlight lang="ruby">/^\d+/ #=> строка начинается с числа
/\w+$/ #=> последнее слово на латинице или число
/^$/ #=> пустая строка</sourcesyntaxhighlight>
 
Насколько видно из примеров,
Модификатор предназначен для изменения поведения правила. Он размещается сразу же после правила (после последней наклонной черты). Пример использования модификатора:
 
<sourcesyntaxhighlight lang="ruby">/(hello|world)/i #=> или "hello", или "world". Причём независимо от регистра
/\s+/mix #=> несколько подряд идущих пробельных символов</sourcesyntaxhighlight>
 
Бывают следующие модификаторы:
Разбиение называется «правильным» тогда, когда в качестве аргумента метода <code>.split</code> используется правило. Например, можно разбить текст по знакам препинания. Для этого необходимо выполнить следующий код.
 
<sourcesyntaxhighlight lang="ruby">"Раз, два, три!".split(/[, \.?!]+/) #=> ["Раз", "два", "три"]</sourcesyntaxhighlight>
 
Обратите внимание, что в результирующем массиве знаки препинания отсутствуют.
С правильной заменой не всё так просто. Дело в том, что методы <code>.sub</code> и <code>.gsub</code> совместно с правилами становятся итераторами, которые последовательно обрабатывают каждое совпадение с правилом. Чтобы это увидеть в действии, давайте решим задачу исправления ошибок:
 
<sourcesyntaxhighlight lang="ruby">"Жыло-было шыбко шыпящее жывотное".gsub(/(ж|ш)ы/){ $1 + "и" }
#=> "Жыло-было шибко шипящее животное"</sourcesyntaxhighlight>
 
Опаньки, а первое слово не исправилось! Видимо дело в том, что слово <code>Жыло</code> начинается с прописной буквы. Сейчас исправим:
 
<sourcesyntaxhighlight lang="ruby">"Жыло-было шыбко шыпящее жывотное".gsub(/(Ж|Ш|ж|ш)ы/){ $1 + "и" }
#=> "Жило-было шибко шипящее животное"</sourcesyntaxhighlight>
 
Вот, теперь гораздо лучше. Как мы этого добились? Давайте разберёмся. Начнём с регулярного выражения:
 
<sourcesyntaxhighlight lang="ruby">/(Ж|Ш|ж|ш)ы/</sourcesyntaxhighlight>
 
Оно состоит из двух частей:
Зачем была использована группировка? Для пояснения причины, рассмотрим код в фигурных скобках:
 
<sourcesyntaxhighlight lang="ruby">{ $1 + "и" }</sourcesyntaxhighlight>
 
Вот для того, чтобы можно было использовать переменную <code>$1</code> (результат первой группировки) мы и задействовали группировку. В данном случае, в <code>$1</code> сохраняется первая буква слога, которая в результате исправления оШЫбки не меняется.
Можно ли было решить эту же задачу иначе? Конечно можно!
 
<sourcesyntaxhighlight lang="ruby">"Жыло-было шыбко шыпящее жывотное".gsub(/([ЖШжш])ы/){ $1 + "и" }
#=> "Жило-было шибко шипящее животное"</sourcesyntaxhighlight>
 
На этот раз мы просто задействовали символьный класс вместо альтернативы, который описывает первую букву слога с оШЫбкой.
Ответ на этот вопрос однозначный — нет! Достаточно придумать название переменной (которая будет содержать совпавший текст) и правильно описать внутри ушек:
 
<sourcesyntaxhighlight lang="ruby">"Раз, два, три!".gsub(/[а-я]+/){ |word| word.reverse }
#=> "заР, авд, ирт!"</sourcesyntaxhighlight>
 
==== Правильный поиск ====
Вот здесь метод <code>.scan</code> может развернуться в полную силу. Хотите получить массив всех русских слов в тексте? Запросто:
 
<sourcesyntaxhighlight lang="ruby">"Раз, два, три!".scan(/[А-Яа-я]+/) #=> ["Раз", "два", "три"]</sourcesyntaxhighlight>
 
Хотите получить все знаки препинания? Нет ничего проще:
 
<sourcesyntaxhighlight lang="ruby">"Раз, два, три!".scan(/[, \.;:!]+/) #=> [", ", ", ", "!"]</sourcesyntaxhighlight>
 
Если необходимо в метод <code>.scan</code> передавать правило с группировкой, то желательно использовать группировку без сохранения результата, то есть <code>(?:…)</code>. Иначе результатом метода <code>.scan</code> будет совпадение с группировкой, а не с правилом.
Например, ниже записана программа, которая занимается поиском адресов электронной почты.
 
<sourcesyntaxhighlight lang="ruby">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"]</sourcesyntaxhighlight>
 
Выполните её, посмотрите результат, а потом замените любую из группировок <code>(?:…)</code> на <code>(…)</code> и снова взгляните на результат.
Ну со <code>.scan</code> должно быть всё понятно. А вот то, что метод <code>[]</code> начинает тоже правильно искать — пока нет.
 
<sourcesyntaxhighlight lang="ruby">"Раз, два, три!"[/[А-Яа-я]+/] #=> "Раз"</sourcesyntaxhighlight>
 
Если методу <code>[]</code> передать в качестве параметра правило, то он вернёт либо совпадение с правилом, либо <code>nil</code>.
Существует древнее поверье, что если использовать одно и тоже правило для <code>.scan</code> и <code>.split</code>, то получаются две части текста, из которых реально получить исходный.
 
<sourcesyntaxhighlight lang="ruby">Text.scan(rule) + Text.split(rule) = Text</sourcesyntaxhighlight>
 
Это значит, что если метод <code>.split</code> использует правило, описывающие все знаки припинания, то результатом будет текст без знаков припинания. А вот если это же правило будет использовать метод <code>.scan</code>, то в результате мы получим все знаки препинания без текста.
Для решения вышеописанной проблемы и был придуман так называемый щедрый квантификатор. От жадного он отличается обратным ходом обработки, то есть длину последовательности он не уменьшает от <code>m</code> к <code>n</code>, а наоборот, увеличивает от <code>n</code> до <code>m</code>. Научить щедрости квантификатор можно знаком вопроса <code>?</code> после любого жадного квантификатора.
 
<sourcesyntaxhighlight lang="ruby">"Раз, два, три!".scan(/[А-Яа-я]+?/)
#=> ["Р", "а", "з", "д", "в", "а", "т", "р", "и"]
"Жуй жвачку, жывотное!".gsub(/([жЖшШ]??)ы/){ $1 + 'и' }
#=> "Жуй жвачку, животное!"</sourcesyntaxhighlight>
 
{{info|С рождения квантификаторы жадные. Щедрость — обретаемый признак.}}
Несколько лет назад (ещё при жизни http://ruby-forum.ru) решали мы интересную задачу: как реализовать автоматический перенос на новую строку (wrap). Для тех, кто не застал те времена, уточню задание: дан текст, необходимо, вставить переносы таким образом, чтобы каждая из полученных строк была меньше <code>n</code> (для определённости <code>n = 80</code>). Недавно я осознал, что не могу решить эту задачу тем способом, который был нами тогда найден. Я его просто не помнил… Решение нашлось быстро, достаточно было вспомнить, что на английском языке эта задача именуется коротким и ёмким словом wrap.
 
<sourcesyntaxhighlight lang="ruby">class String
def wrap(col = 80)
gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}})/, "\\1\\3\n")
end
end</sourcesyntaxhighlight>
 
Немного о структуре кода. Метод <code>.wrap</code> реализован для экземпляров класса <code>String</code>. Также стоит обратить внимание на то, что внутри правила (регулярного выражения) возможна «вставка» (как в «рабочих строках»). Используется сей метод следующим образом:
 
<sourcesyntaxhighlight lang="ruby">p "wrapping text with regular expressions".wrap(10)
#=> "wrapping\ntext with\nregular\nexpression\ns\n"</sourcesyntaxhighlight>
 
Теперь давайте разберёмся с правилом. Чтобы не смущать неокрепшие умы, заменим вставку на 80. Правило станет короче и менее страшным.
 
<sourcesyntaxhighlight lang="ruby">(.{1, 80})( +|$\n?)|(.{1, 80})</sourcesyntaxhighlight>
 
Очевидно, что оно состоит из четырёх частей:
Ruby сам преобразует типы для некоторых простых операций. Например, при включении строки в другую он воспользуется имеющимся у объекта методом <code>.to_s</code>:
 
<sourcesyntaxhighlight lang="ruby">class Container
def to_s
"контейнер"
cont = Container.new
 
p "Это #{cont}" #=> "Это контейнер"</sourcesyntaxhighlight>
 
Если нужно, чтобы ваши объекты упорядочивались и сравнивались с обычными строками, следует применять примесь <code>Comparable</code> и единственный специальный метод <code>to_str</code>. Наличие этого метода у вашего объекта — знак для Ruby, что для сравнения следует применять не встроенный в <code>String</code> метод, а ваш.
 
<sourcesyntaxhighlight lang="ruby">class Container
include Comparable
def to_str
cont = Container.new
 
"контейнер" == cont #=> true</sourcesyntaxhighlight>
 
=== Задачи ===
583

правки