Хэл Фултон - Программирование на языке Ruby
num = 0
str.each do |line|
num += 1
print "Строка #{num}: #{line}"
end
Выполнение этого кода дает следующий результат:
Строка 1: Когда-то
Строка 2: давным-давно...
Строка 3: Конец
Альтернативно можно было бы воспользоваться методом each_with_index.
2.6. Побайтовая обработка
Поскольку на момент написания этой книги язык Ruby еще не поддерживал интернационализацию в полной мере, то символ и байт — по существу одно и то же. Для последовательной обработки символов пользуйтесь итератором each_byte:
str = "ABC"
str.each_byte {|char| print char, " " }
#Результат: 65 66 67.
В текущей версии Ruby строку можно преобразовать в массив односимвольных строк с помощью метода scan, которому передается простое регулярное выражение, соответствующее одному символу:
str = "ABC"
chars = str.scan(/./)
chars.each {|char| print char, " " }
#Результат: ABC.
2.7. Специализированное сравнение строк
В язык Ruby уже встроен механизм сравнения строк: строки сравниваются в привычном лексикографическом порядке (то есть на основе упорядочения, присущего данному набору символов). Но при желании можно задать собственные правила сравнения любой сложности.
Предположим, например, что мы хотим игнорировать английские артикли a, an и the, если они встречаются в начале строки, а также не обращать внимания на большинство знаков препинания. Для этого следует переопределить встроенный метод <=> (он вызывается из методов <, <=, > и >=). В листинге 2.1 показано, как это сделать.
Листинг 2.1. Специализированное сравнение строкclass String
alias old_compare <=>
def <=>(other)
a = self.dup
b = other.dup
# Удалить знаки препинания.
a.gsub!(/[,.?!:;]/, "")
b.gsub!(/[,.?!:;]/, "")
# Удалить артикли из начала строки.
a.gsub!(/^(a |an | the )/i, "")
b.gsub!(/^(a |an | the )/i, "")
# Удалить начальные и хвостовые пробелы.
a.strip!
b.strip!
# Вызвать старый метод <=>.
# a.old_compare(b)
end
end
title1 = "Calling All Cars"
title2 = "The Call of the Wild"
# При стандартном сравнении было бы напечатано "yes".
if title1 < title2
puts "yes"
else
puts "no" # А теперь печатается "no".
end
Обратите внимание, что мы «сохранили» старый метод <=> с помощью ключевого слова alias и в конце вызвали его. Если бы мы вместо этого воспользовались методом <, то был бы вызван новый метод <=>, что привело бы к бесконечной рекурсии и в конечном счете к аварийному завершению программы.
Отметим также, что оператор == не вызывает метод <=> (принадлежащий классу-примеси Comparable). Это означает, что для специализированной проверки строк на равенство пришлось бы отдельно переопределить метод ==. Но в рассмотренном случае в этом нет необходимости.
Допустим, что мы хотим сравнивать строки без учета регистра. Для этого есть встроенный метод casecmp; надо лишь добиться, чтобы он вызывался при сравнении. Вот как это можно сделать:
class String
def <=>(other)
casecmp(other)
end
end
Есть и более простой способ:
class String
alias <=> casecmp(other)
end
Но это не все. Надо еще переопределить оператор ==, чтобы он вел себя точно так же:
class String
def ==(other)
casecmp(other) == 0
end
end
Теперь все строки будут сравниваться без учета регистра. И при всех операциях сортировки, которые определены в терминах метода <=>, регистр тоже не будет учитываться.
2.8. Разбиение строки на лексемы
Метод split разбивает строку на части и возвращает массив лексем. Ему передаются два параметра: разделитель и максимальное число полей (целое).
По умолчанию разделителем является пробел, а точнее, значение специальной переменной $; или ее англоязычного эквивалента $FIELD_SEPARATOR. Если же первым параметром задана некоторая строка, то она и будет использоваться в качестве разделителя лексем.
s1 = "Была темная грозовая ночь."
words = s1.split # ["Была", "темная", "грозовая", "ночь]
s2 = "яблоки, груши, персики"
list = s2.split(", ") # ["яблоки", "груши", "персики"]
s3 = "львы и тигры и медведи"
zoo = s3.split(/ и /) # ["львы", "тигры", "медведи"]
Второй параметр ограничивает число возвращаемых полей, при этом действуют следующие правила:
1. Если параметр опущен, то пустые поля в конце отбрасываются.
2. Если параметр — положительное число, то будет возвращено не более указанного числа полей (если необходимо, весь «хвост» строки помещается в последнее поле). Пустые поля в конце сохраняются.
3. Если параметр — отрицательное число, то количество возвращаемых полей не ограничено, а пустые поля в конце сохраняются.
Ниже приведены примеры:
str = "alpha,beta,gamma,,"
list1 = str.split(",") # ["alpha","beta","gamma"]
list2 = str.split(",",2) # ["alpha", "beta,gamma,,"]
list3 = str.split(",",4) # ["alpha", "beta", "gamma", ","]
list4 = str.split(",",8) # ["alpha", "beta", "gamma", "", "")
list5 = str.split(",",-1) # ["alpha", "beta", "gamma", "", ""]
Для сопоставления строки с регулярным выражением или с другой строкой служит метод scan:
str = "I am a leaf on the wind..."
# Строка интерпретируется буквально, а не как регулярное выражение.
arr = str.scan("а") # ["а","а","а"]
# При сопоставлении с регулярным выражением возвращаются все соответствия.
arr = str.scan(/w+/) # ["I", "am", "a", "leaf", "on", "the",
"wind"]
# Можно задать блок.
str.scan(/w+/) {|x| puts x }
Класс StringScanner из стандартной библиотеки отличается тем, что сохраняет состояние сканирования, а не выполняет все за один раз:
require 'strscan'
str = "Смотри, как я парю!"
ss = StringScanner.new(str)
loop do
word = ss.scan(/w+/) # Получать по одному слову.
break if word.nil?
puts word
sep = ss.scan(/W+/) # Получить следующий фрагмент,
# не являющийся словом.
break if sep.nil?
end
2.9. Форматирование строк
В Ruby, как и в языке С, для этой цели предназначен метод sprintf. Он принимает строку и список выражений, а возвращает строку. Набор спецификаторов в форматной строке мало чем отличается от принятого в функции sprintf (или printf) из библиотеки С.
name = "Боб"
age =28
str = sprintf("Привет, %s... Похоже, тебе %d лет.", name, age)
Спрашивается, зачем нужен этот метод, если можно просто интерполировать значения в строку с помощью конструкции #{expr}? А затем, что sprintf позволяет выполнить дополнительное форматирование - например, задать максимальную ширину поля или максимальное число цифр после запятой, добавить или подавить начальные нули, выровнять строки текста по левой или правой границе и т.д.
str = sprintf("%-20s %3d", name, age)
В классе String есть еще метод %, который делает почти то же самое. Он принимает одно значение или массив значений любых типов:
str = "%-20s %3d" % [name, age] # To же, что и выше
Имеются также методы ljust, rjust и center; они принимают длину результирующей строки и дополняют ее до указанной длины пробелами, если это необходимо.
str = "Моби Дик"
s1 = str.ljust(12) # "Моби Дик"
s2 = str.center(12) # " Моби Дик "
s3 = str.rjust(12) # " Моби Дик"
Можно задать и второй параметр, который интерпретируется как строка заполнения (при необходимости она будет урезана):
str = "Капитан Ахав"
s1 = str.ljust(20,"+") # "Капитан Ахав++++++++"
s2 = str.center(20,"-") # "----Капитан Ахав----"
s3 = str.rjust(20,"123") # "12312312Капитан Ахав"
2.10. Строки в качестве объектов ввода/вывода
Помимо методов sprintf и scanf, есть еще один способ имитировать ввод/вывод в строку: класс StringIO.
Из-за сходства с объектом IO мы рассмотрим его в главе, посвященной вводу/выводу (см. раздел 10.1.24).
2.11. Управление регистром
В классе String есть множество методов управления регистром. В этом разделе мы приведем их краткий обзор.
Метод downcase переводит символы всей строки в нижний регистр, а метод upcase — в верхний:
s1 = "Бостонское чаепитие"