355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Хэл Фултон » Программирование на языке Ruby » Текст книги (страница 8)
Программирование на языке Ruby
  • Текст добавлен: 24 сентября 2016, 06:40

Текст книги "Программирование на языке Ruby"


Автор книги: Хэл Фултон



сообщить о нарушении

Текущая страница: 8 (всего у книги 56 страниц) [доступный отрывок для чтения: 20 страниц]

1.7. Заключение

На этом завершается наш обзор объектно-ориентированного программирования и краткая экскурсия по языку Ruby. В последующих главах изложенный материал будет раскрыт более полно.

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

Глава 2. Строки

Когда-то элементарными кирпичиками мироздания считались атомы, потом протоны, потом кварки. Теперь таковыми считаются струны[7]7
  В английском языке словом «string» обозначается как «строка», так и «струна» (Прим. перев.)


[Закрыть]
.

Дэвид Гросс, профессор теоретической физики,
Принстонский университет

В начале 1980-х годов один профессор информатики, начиная первую лекцию по структурам данных, не представился студентам, не сказал, как называется курс, не рассказал о его программе и не порекомендовал никаких учебников – а вместо этого сходу спросил: «Какой тип данных самый важный?»

Было высказано несколько предположений. Когда профессор услышал слово «указатели», он выразил удовлетворение, но все-таки не согласился со студентом, а высказал свое мнение: «Самым важным является тип символ».

У него были на то основания. Компьютерам предназначено быть нашими слугами, а не хозяевами, а человеку понятны только символьные данные. (Есть, конечно, люди, которые без труда читают и двоичные данные, но о них мы сейчас говорить не будем.) Символы, а стало быть, и строки, позволяют человеку общаться с компьютером. Любую информацию, в том числе и текст на естественном языке, можно закодировать в виде строк.

Как и в других языках, строка в Ruby – просто последовательность символов. Подобно другим сущностям, строки являются полноценными объектами. В программах приходится выполнять разнообразные операции над строками: конкатенировать, выделять лексемы, анализировать, производить поиск и замену и т.д. Язык Ruby позволяет все это делать без труда.

Почти всюду в этой главе предполагается, что байт – это символ. Но при работе в многоязычной среде это не совсем так. Вопросы интернационализации обсуждаются в главе 4.

2.1. Представление обычных строк

Строка в Ruby – это последовательность 8-битовых байтов. Она не завершается нулевым символом, как в С, и, следовательно, может содержать такие символы. В строке могут быть символы с кодами больше 0xFF, но такие строки имеют смысл, лишь если выбран некоторый набор символов (кодировка). Дополнительную информацию о кодировках вы найдете в главе 4.

Простейшая строка в Ruby заключается в одиночные кавычки. Такие строки воспринимаются буквально; в качестве управляющих символов в них распознаются только экранированная одиночная кавычка (') и экранированный символ обратной косой черты (\):

s1 = 'Это строка'        # Это строка.

s2 = 'Г-жа О'Лири'      # Г-жа О'Лири.

s3 = 'Смотри в С:\TEMP' # Смотри в C:TEMP.

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

s1 = "Это знак табуляции: (t)"

s2 = "Несколько символов забоя: xyzbbb"

s3 = "Это тоже знак табуляции: 11"

s4 = "А это символы подачи звукового сигнала: а 07"

Внутри строки, заключенной в двойные кавычки, могут встречаться даже выражения (см. раздел 2.21).

2.2. Альтернативная нотация для представления строк

Иногда встречаются строки, в которых много метасимволов, например одиночных и двойных кавычек и т.д. В этом случае можно воспользоваться конструкциями %q и %Q. Вслед за ними должна идти строка, обрамленная с обеих сторон символами-ограничителями; лично я предпочитаю квадратные скобки ([]).

При этом %q ведет себя как одиночные кавычки, a %Q - как двойные.

S1 = %q[Как сказал Магритт, "Ceci n'est pas une pipe."]

s2 = %q[Это не табуляция: (t)] # Равнозначно 'Это не табуляция: t'

s3 = %Q[А это табуляция: (t)]  # Равнозначно "А это табуляция: t"

В обоих вариантах можно применять и другие ограничители, помимо квадратных скобок: круглые, фигурные, угловые скобки.

s1 = %q(Билл сказал: "Боб сказал: 'This is a string.'")

s2 = %q{Дpyгaя строка.}

s3 = %q

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

s1 = %q:"Я думаю, что это сделала корова г-жи О'Лири," сказал он.:

s2 = %q*r – это control-M, a n – это control-J.*

2.3. Встроенные документы

Для представления длинной строки, занимающей несколько строк в тексте, можно, конечно, воспользоваться обычными строками в кавычках:

str = "Три девицы под окном

Пряли поздно вечерком..."

Но тогда отступ окажется частью строки.

Можно вместо этого воспользоваться встроенным документом, изначально предназначенным для многострочных фрагментов. (Идея и сам термин заимствованы из более старых языков.) Синтаксически он начинается с двух знаков <<, за которыми следует концевой маркер, нуль или более строк текста и в завершение тот же самый концевой маркер в отдельной строке:

str = <

Три девицы под окном

Пряли поздно вечерком...

EOF

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

Встроенные документы могут быть вложенными. В примере ниже показано, как передать методу три представленных таким образом строки:

some_method(<

первый кусок

текста...

str1

второй кусок...

str2

третий кусок

текста.

str3

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

str = <<'EOF'

Это не знак табуляции: t

а это не символ новой строки: n

EOF

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

str = <<-EOF

  Каждая из этих строк

  начинается с пары

  пробелов.

  EOF

Опишу стиль, который нравится лично мне. Предположим, что определен такой метод margin:

class String

 def margin

  arr = self.split("n") # Разбить на строки.

  arr.map! {|x| x.sub!(/s*|/,"")) # Удалить начальные символы.

  str = arr.join("n") # Объединить в одну строку.

  self.replace(str) # Подменить исходную строку.

 end

end

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

str = <

 |Этот встроенный документ имеет "левое поле"

 |на уровне вертикальной черты в каждой строке.

 |

 | Можно включать цитаты,

 | делать выступы и т.д.

end

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

2.4. Получение длины строки

Для получения длины строки служит метод length. У него есть синоним size.

str1 = "Карл"

x = str1.length # 4

str2 = "Дойль"

x = str2.size # 5

2.5. Построчная обработка

Строка в Ruby может содержать символы новой строки. Например, можно прочитать в память файл и сохранить его в виде одной строки. Применяемый по умолчанию итератор each в этом случае перебирает отдельные строки:

str = "Когда-тоnдавным-давно...nКонецn"

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 = "Бостонское чаепитие"

s2 = s1.downcase # "бостонское чаепитие"

s3 = s2.upcase   # "БОСТОНСКОЕ ЧАЕПИТИЕ"

Метод capitalize представляет первый символ строки в верхнем регистре, а все остальные – в нижнем:

s4 = s1.capitalize # "Бостонское чаепитие"

s5 = s2.capitalize # "Бостонское чаепитие"

s6 = s3.capitalize # "Бостонское чаепитие"

Метод swapcase изменяет регистр каждой буквы на противоположный:

s7 = "ЭТО БЫВШИЙ попугай."

s8 = s7.swapcase # "это бывший ПОПУГАЙ."

Начиная с версии 1.8, в язык Ruby включен метод casecmp, который работает аналогично стандартному методу <=>, но игнорирует регистр:

n1 = "abc".casecmp("xyz") # -1

n2 = "abc".casecmp("XYZ") # -1

n3 = "ABC".casecmp("xyz") # -1

n4 = "ABC".casecmp("abc") # 0

n5 = "xyz".casecmp("abc") # 1

У каждого из перечисленных методов имеется аналог, осуществляющий модификацию «на месте» (upcase!, downcase!, capitalize!, swapcase!).

He существует встроенных методов, позволяющих узнать регистр буквы, но это легко сделать с помощью регулярных выражений:

if string=~ /[a-z]/

 puts "строка содержит символы в нижнем регистре"

end

if string =~ /[A-Z]/

 puts "строка содержит символы в верхнем регистре"

end

if string =~ /[A-Z]/ and string =~ /а-z/

 puts "строка содержит символы в разных регистрах"

end

if string[0..0] =~ /[A-Z]/

 puts "строка начинается с прописной буквы"

end

Отметим, что все эти методы не учитывают местные особенности (locale).

2.12. Вычленение и замена подстрок

В Ruby к подстрокам можно обращаться разными способами. Обычно применяются квадратные скобки, как для массивов, но внутри скобок может находиться пара объектов класса Fixnum, диапазон, регулярное выражение или строка. Ниже мы рассмотрим все варианты.

Если задана пара объектов класса Fixnum, то они трактуются как смещение от начала строки и длина, а возвращается соответствующая подстрока.

str = "Шалтай-Болтай"

sub1 = str[7,4]   # "Болт"

sub2 = str[7,99]  # "Болтай" (выход за границу строки допускается)

sub3 = str[10,-4] # nil (отрицательная длина)

Важно помнить, что это именно смещение и длина (число символов), а не начальное и конечное смещение.

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

str1 = "Алиса"

sub1 = str1[-3,3] # "иса"

str2 = "В Зазеркалье"

sub3 = str2[-8,6] # "зеркал"

Можно задавать диапазон. Он интерпретируется как диапазон позиций внутри строки. Диапазон может включать отрицательные числа, но в любом случае нижняя граница не должна быть больше верхней. Если диапазон «инвертированный» или нижняя граница оказывается вне строки, возвращается nil:

str = "Уинстон Черчилль"

sub1 = str[8..13]  # "Черчил"

sub2 = str[-4..-1] # "илль"

sub3 = str[-1..-4] # nil

sub4 = str[25..30] # nil

Если задано регулярное выражение, то возвращается строка, соответствующая образцу. Если соответствия нет, возвращается nil:

str = "Alistair Cooke"

sub1 = str[/1..t/] # "list"

sub2 = str[/s.*r/] # "stair"

sub3 = str[/foo/]  # nil

Если задана строка, то она и возвращается, если встречается в качестве подстроки в исходной строке; в противном случае возвращается nil:

str = "theater"

sub1 = str["heat"]  # "heat"

sub2 = str["eat"]   # "eat"

sub3 = str["ate"]   # "ate"

sub4 = str["beat"]  # nil

sub5 = str["cheat"] # nil

Наконец, в тривиальном случае, когда в качестве индекса задано одно число Fixnum, возвращается ASCII-код символа в соответствующей позиции (или nil, если индекс выходит за границы строки):

str = "Aaron Burr"

ch1 = str[0]  # 65

ch1 = str[1]  # 97

ch3 = str[99] # nil

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

str1 = "Шалтай-Болтай"

str1[7,3] = "Хва"        # "Шалтай-Хватай"

str2 = "Алиса"

str2[-3,3] = "ександра"  # "Александра"

str3 = "В Зазеркалье"

str3[-9,9] = "стеколье"  # "В Застеколье"

str4 = "Уинстон Черчилль"

str4[8..11] = "X"        # "Уинстон Хилль"

str5 = "Alistair Cooke"

str5[/e$/] ="ie Monster" # "Alistair Cookie Monster"

str6 = "theater"

str6["er"] = "re"        # "theatre"

str7 = "Aaron Burr"

str7[0] = 66             # "Baron Burr"

Присваивание выражения, равного nil, не оказывает никакого действия.

2.13. Подстановка в строках

Мы уже видели, как выполняются простые подстановки. Методы sub и gsub предоставляют более развитые средства, основанные на сопоставлении с образцом. Имеются также варианты sub! и gsub!, позволяющие выполнить подстановку «на месте».

Метод sub заменяет первое вхождение строки, соответствующей образцу, другой строкой или результатом вычисления блока:

s1 = "spam, spam, and eggs"

s2 = s1.sub(/spam/,"bacon") # "bacon, spam, and eggs"

s3 = s2.sub(/(w+), (w+),/,'2, 1,') # "spam, bacon, and eggs"

s4 = "Don't forget the spam."

s5 = s4.sub(/spam/) { |m| m.reverse } # "Don't forget the maps."

s4.sub!(/spam/) { |m| m.reverse }

# s4 теперь равно "Don't forget the maps."

Как видите, в подставляемой строке могут встречаться специальные символы 1, 2 и т.д. Но такие специальные переменные, как $& (или ее англоязычная версия $MATCH), не допускаются.

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

Метод gsub (глобальная подстановка) отличается от sub лишь тем, что заменяются все вхождения, а не только первое:

s5 = "alfalfa abracadabra"

s6 = s5.gsub(/a[bl]/,"xx")# "xxfxxfa xxracadxxra"

s5.gsub!(/[lfdbr]/) { |m| m.upcase + "-" }

# s5 теперь равно "aL-F-aL-F-a aB-R-acaD-aB-R-a"

Метод Regexp.last_match эквивалентен действию специальной переменной $& (она же $MATCH).


    Ваша оценка произведения:

Популярные книги за неделю

    wait_for_cache