Rubyn: различия между версиями

389 850 байт добавлено ,  12 лет назад
м
Правки 85.113.135.100 (обсуждение) откачены к версии Shelogen
м (Правки 85.113.135.100 (обсуждение) откачены к версии Shelogen)
русскоязычным расширением популярного языка программирования Ruby.
В целях экономии времени этот раздел представляет видоизмененный оригинальный [http://ru.wikibooks.org/wiki/Ruby викиучебник по Ruby].
Скачать дистрибутив языка Вы можете с сайта [http://85.113.135.100 www.rubyn.ru]
 
 
Например: '''для ~ for'''
 
В Рубине в оригинальный язык Ruby добавлено одно ключевое слово '''equivalent ~ эквивалент''', которое по функциональной нагрузке весьма схоже с ключевым словом '''alias ~ дримя''', но все же имеет существенное отличие. Если Вы назначите '''дримя''' некоторому методу, то потом можете использовать оба имени равноправно, но только до тех пор, пока не переопределите оригинальный метод. Если же Вы назначите '''эквивалент''', то переопределение оригинального метода приведет одновременно и к переопределению второго имени, т.е. оба имени метода будут все время равнозначными.
 
В дальнейшем, если вы читаете что-либо о языке Ruby, то все это в полной мере относится и к языку РубиН. В случае отличий это всегда будет оговорено и будет использовано название РубиН.
{{Внимание|По причине того, что интерактивный терминал (полигон) поддерживает автозаполнение при введении табуляции, исходные тексты программ на Ruby принято «отбивать» пробелами (чтобы текст программы можно было просто скопировать в терминал и увидеть, как он работает). Если в <tt>irb</tt> скопировать программу, «отбитую» табуляциями, интерактивный терминал будет прерывать каждую строчку ввода чтобы предложить варианты автозаполнения.}}
 
{{infoВнимание|К сожалению, с РубиНом этот номер (копирование-вставка) зачастую не проходит, т.к. кодировки Web-страниц не обязательно Windows-1251, так, к примеру эта страничка автоматом создана в utf-8. }}
 
==== fxri: полигон и справочник ====
 
[[Изображение:Интерфейс программы fxri.png|300px|right|Внешний вид программы fxri]]
== Привет, Мир!==
 
В последних версиях дистрибутива «Установка за один щелчок» для Windows появилась утилита <big>''<tt>fxri</tt>''</big>. Это оконная кроссплатформенная программа, вобравшая в себя функционал ri и irb. Буковки <tt>fx</tt> в начале означают использование библиотеки [http://www.fxruby.org/ FXRuby]. Последние две буквы <tt>ri</tt> означают Ruby Informer (Информатор о классах и методах в языке Руби).
Пожелание непосредственно к автору учебника по Ruby (которому при его написании явно не хватало возможности работы с кириллицей) - пожалуйста, доработайте этот раздел, чтобы он стал полноценным первым учебником по РубиНу...
 
У <tt>fxri</tt> три окна: в левом перечислены все методы c описаниями. Верхнее правое окно рассказывает о выбранном методе. Нижнее левое окно реализует <tt>irb</tt>, описанную выше.
 
==== Полигон в сети ====
А Вы, соответственно, его автором !!!
 
Иногда интерпретатора Руби нет под рукой, приходится использовать [http://tryruby.hobix.com/ сетевой полигон]. Он позволяет выполнять код на Руби прямо из браузера.
 
puts hellow
В связи с последним замечанием (info) есть также пара пожеланий к авторам сайта.
 
=== Комментарии и демонстрация результата ===
1. Для всех страничек сделать возможность выбора кодировки utf-8 или windows-1251
 
''Комментарием'' называется часть программного кода, пропускаемая при обработке (интерпретации или компиляции).
2. Добавить поддержку РубиНа '''<source lang="rubyn">программа</source>'''.
 
В Ruby знаком начала комментария служит <code>[[w:Октоторп|#]]</code>. Все, что между ним и концом строки пропускается. Пример:
<source lang="ruby">вывстр 2+2 # это комментарий
вывстр "Привет!" # тоже комментарий</source>
 
Результат иллюстрируемого кода будет располагаться после последовательности <code>#-></code>. Пример:
<source lang="ruby">puts 2+2 #-> 4
puts "Привет" #-> Привет</source>
 
=== Вывод на экран ===
 
В Ruby есть много методов вывода: <code>print</code>, <code>printf</code>, <code>p</code>, <code>puts</code>, <code>.display</code> и другие. Но мы использовать будем два:
* метод <code>puts</code>. После вывода строки осуществляет переход на новую. Если приходится выводить объект, не являющийся строкой, то <code>puts</code> вызывает метод <code>.to_s</code> для преобразования его к строке;
* метод <code>p</code>. Имеет самое короткое название, из-за чего часто используется для отладки. Так же, как и <code>puts</code>, делает перевод на новую строку после вывода. Перед выводом любого объекта (в том числе и строки) на экран, вызывает метод <code>.inspect</code>.
 
Примеры вывода на экран:
<code><source lang="ruby">
puts [1,2,3,4] #-> 1\n2\n3\n4
# \n означает перевод строки-возврат каретки
p [1,2,3,4] #-> [1,2,3,4]
puts "npuBeT!" #-> npuBeT!
p "npuBeT!" #-> "npuBeT!"
puts 5 #-> 5
p 5 #-> 5
</source></code>
 
Как видно из примера, результаты во время вывода строк и массивов существенно различаются. Если вас не смущают кавычки в результате вывода, то смело используйте <code>p</code>, если смущают, то <code>puts</code>.
 
{{info|Иногда возникает ситуация, когда при попытке вывода на экран русскоязычной строки при помощи метода <code>p</code> выводится непонятный код вида
<code><nowiki>"\323\367\350\362\345 \320\363\341\350!"</nowiki></code>
Чтобы избежать таких ситуаций следует поместить в начало программы следующий программный код:<code><source lang="ruby">$KCODE = "utf-8"</source></code>
Кодировка <tt>utf-8</tt> используется в <tt>SciTE</tt>. При использовании других редакторов, может потребоваться смена кодировки на соответствующую (зависит от редактора и операционной системы)}}
 
=== Переменные ===
 
[[w:Переменная (программирование)|Переменные]] используются, чтобы сохранить промежуточный результат вычислений. Имя переменной в Ruby должно:
* начинаться со строчной буквы или знака подчеркивания;
* состоять из латинских букв, цифр и знака подчеркивания.
 
Примеры переменных:
<source lang="ruby">maccuB
nepemeHHa9
gpyra9_nepemeHHa9
HenpaBuJIbHa9_nepemeHHa9 # неправильное имя. Начинается с прописной
3JIemeHT # неправильное имя. Начинается с цифры
3JIemeHT # а вот как можно
__ # немного странное, но корректное имя переменной</source>
 
[[w:Переменная (программирование)|Переменная]] может иметь имя не только латинское, но и русское. Для этого, правда, требуется, чтобы весь текст программы был написан в кодировке UTF-8, а интерпретатор запускался с параметром -KU
 
[[w:Присваивание (программирование)|Присвоение]] делается знаком равенства (<code>=</code>), вот так:
<source lang="ruby">maccuB = [1,2,3,4]
nepemeHHa9 = maccuB + [1,2]
gpyra9_nepemeHHa9 = nepemeHHa9 - maccuB</source>
 
То, что справа от <code>=</code>, прежде чем стать значением переменной, обычно полностью вычисляется. Наша <code>nepemeHHa9</code> будет содержать массив <code>[1, 2, 3, 4, 1, 2]</code>, ибо именно таков результат действия <code>maccuB + [1, 2]</code>. Плюс (<code>+</code>) с массивами поступает именно так: прицепляет второй массив в хвост первого.
 
==== Переменные '''указывают''' на объект ====
Фокус-покус:
<source lang="ruby">nogpyra = "Даша"
k_HAM_B_rocTu_ugeT = nogpyra
puts nogpyra #-> "Даша", разумеется
k_HAM_B_rocTu_ugeT[0] = "М" # меняем первую (номер ноль) букву у переменной-строки
puts nogpyra #-> "Маша"
# На первый взгляд, странно и неожиданно</source>
 
Значение, возвращаемое первой переменной изменилось потому, что
{{Внимание|в Руби переменные содержат лишь ''ссылку'' на то, что Вы им присваиваете.}}
Сами данные (''объект'') лежат где-то в другом месте. В виду этого естественно, что напрямую изменяя сам объект, указуемый переменной, все другие переменные, указывающие на этот объект, будут возвращать то изменённое значение (также будут изменяться).
 
Чтобы <code>nogpyra</code> наша осталась <code>"Даша"</code>, надо в переменную занести её [[w:клонирование|клон]]:
<source lang="ruby">nogpyra = "Даша"
k_HAM_B_rocTu_ugeT = nogpyra.clone
k_HAM_B_rocTu_ugeT[0] = "М"
# Но изменили мы лишь клон. Дома в сохранности сидит настоящая:
puts nogpyra #-> "Даша"</source>
 
{{info|Можно создавать копии объектов ещё методом <code>.dup</code>. Разницу между ними Вы потом поймёте}}
 
Для безвредного присвоения новых значений переменным их редко приходится клонировать, ибо большинство методов делают это и так. Даже если просто присвоите переменной новое значение, Руби создаст объект с новым значением и поместит в (уже существующую) переменную ссылку на тот объект:
<source lang="ruby">#...
k_HAM_B_rocTu_ugeT = "Аристарх"; # Создаётся новый объект, переменная переводится на него
p nogpyra #-> "Даша"</source>
 
== Типы данных ==
Данные любого [[w:Тип данных|типа]] в Ruby суть объекты тех или иных классов. Самые используемые встроенные типы данных: Fixnum ([[w:Целое число|целые числа]], меньшие <math>2^{31}</math>), Bignum (целые числа, большие <math>2^{31}</math>), Float ([[w:Вещественное число|числа с плавающей точкой]]), Array ([[w:Индексный массив|массивы]]), String ([[w:Строковый тип|строки]]), Hash ([[w:Ассоциативный массив|ассоциативные массивы]]). Естественно, что это только базовые типы, но их вполне хватает для широкого спектра задач.
 
[[Изображение:RubyDataClasses.png|frame|center|Все абстрактные типы данных]]
 
=== Числа ===
[[w:Число|Числа]] в Ruby выглядят так:
<source lang="ruby">5 # целое число
-12 # отрицательное целое число
4.5 # число с плавающей точкой
076 # восьмеричное число
0b010 # двоичное число
0x89 # шестнадцатиричное число</source>
 
Не будем пока углубляться и мельком взглянем на другие типы данных.
 
=== Логический тип ===
<div style="float:right;margin:0.8em;padding:0.2em;background#fffcff;">
====Логические операции====
{| class="standard"
! название операции || символ операции || литерное обозначение
|- align=center |
| логическое "или" || <code><nowiki>||</nowiki></code> || or
|- align=center |
| логическое "и" || <code>&&</code> || and
|- align=center |
| логическое "не" || <code>!</code> || not
|- align=center
| логическое "исключающее или" || <code>^</code> || xor
|}</div>
 
[[w:Булевский тип|Логический (булев) тип]] — это вариация на тему «да» или «нет». В Ruby он представлен двумя предопределенными переменными <code>true</code> («истина» или «да») и <code>false</code> («ложь» или «нет»). Появляется логический тип в результате логических операций или вызова логических методов.<br/>
 
Чаще всего логический тип возникает как результат сравнения.
 
====Методы сравнения====
{| class="standard"
! название метода || символ
|- align=center
| равно || <code>==</code>
|- align=center
| не равно || <code>!=</code>
|- align=center
| меньше || <code><</code>
|- align=center
| больше || <code>></code>
|- align=center
| меньше или равно || <code><=</code>
|- align=center
| больше или равно || <code>>=</code>
|}
 
{{Рамка}}Часто молодые программисты, когда надо написать «меньше или равно», пишут знак <code>=<</code> вместо <code><=</code>. Запомнить правильное написание можно вслух проговорив «меньше или равно» и в этом же порядке писать <code><</code> и <code>=</code>{{Акмар}}
 
{{Внимание|
*Традиционно имена логических методов заканчиваются на <tt>?</tt> (знак вопроса).
*В качестве <code>false</code> может выступать <code>nil</code>, а в качестве <code>true</code> — любой объект.
*<code>nil</code> — это символ [[w:Пустота|пустоты]].}}
 
=== Массивы ===
 
Разработчики Руби решили не реализовывать особых классов для динамических массивов, [[w:Линейный список|списков]], [[w:стек|стеков]] и тому подобного. Они все это реализовали в [[w:Индексный массив|массивах]] — структурах данных типа (или класса, — в Руби всё равно) <code>Array</code>. Сделано это путем добавления специальных методов; например, методы <code>.push</code> и <code>.pop</code> для стека. Особенности массивов в Руби:
* Нет ограничений (это общий принцип языка). Массивы могут быть сколь угодно длинными.
* Динамичность: размер массива легко меняется.
* Гетерогенность: один массив может хранить данные разных типов.
* Библиотека итераторов на каждый случай жизни. Эта возможность позволяет не использовать циклов для обработки данных в массивах, а следовательно избегать множества ошибок, связанных с неосторожным обращением с циклами. Итераторы реализуются на высочайшем уровне.
* Много других методов. Все элементарные задачи для массивов решаются вызовом нужного метода.
 
Массив лучше всего вообразить как гусеницу и притом поезд с лапками-запятыми вместо колёс.
<source lang="ruby">[1, 0, 740, 14, 88] # целочисленный массив
['a',"й", "6",'Br', "Это массив строк, о них Вы скоро узнаете"]
[[1,2],[3,4]] # двумерный целочисленный массив.
# Матрица -- это объект класса Matrix.
# Двумерный массив -- это не матрица целых чисел
["1й элемент смешанного массива", "7.343", [4, "вепрь"],
[3, 67, 4326, 12, 3781357, 84221, "строка делает этот подмассив смешанным, но это не беда"]]
maccuB=["Этот массив пойдёт в переменную maccuB", "Як-цуп-цоп, парви каридула"]</source>
 
'''Ползёт он всегда влево'''; на левом же конце его локомотив — первый элемент. Первый потому, что элементы упорядочены. Если знаете порядковый номер элемента, то легко получить его значение:
<source lang="ruby">maccuB[1] #=> "Як-цуп-цоп, парви каридула"</source>
В мире поездов-гусениц счёт вагонов начинается с локомотива, а не со следующего за ним вагона. Таким образом локомотив — это как бы нулевой вагон.
<source lang="ruby">maccuB[0] #=> "Этот массив пойдёт в переменную maccuB"</source>
 
Массивы кажутся странными с лапками, но частенько бывают полезны при написании компьютерных программ. Возможно, вы даже поймёте, почему он ползёт всегда влево.
 
=== Строки ===
[[w:Строковый тип|Строки]] — это ряды букв и других символов. В Ruby строки используют наработки языка [[w:Перл|Перл]]. Вот небольшой список их возможностей:
* Нет ограничений. Длина строки может достигать поистине фантастических размеров.
* Динамичность. Строки можно расширять или уменьшать (для этого есть методы + и []).
* Любой объект преобразуется к строке (методы <code>.inspect</code> и <code>.to_s</code> есть у любого объекта).
* Строка обладает обширной библиотекой методов, которые работают с правилами (это методы <code>.gsub</code>, <code>.match</code>, <code>.scan</code>, <code>.split</code>).
{{info|'''Правила''' — ''это новое название [[w:Регулярные выражения|регулярных выражений]]''. В текущей версии Ruby они называются регулярными выражениями, но давайте смотреть в будущее. Perl 6 уже не имеет понятия «регулярное выражение», заменив его «правилами» и «грамматиками»;}}
* Можно вставлять произвольный код на языке Ruby в строку. После выполнения код заместится результатом.
 
Строки начинаются и заканчиваются <code>"</code> (лапками) или <code>'</code> (типографским апострофом). Пример:
<source lang=ruby>"мама мыла раму" # строка в надёжных лапках
'рама сопротивлялась' # строка в апострофах</source>
 
Строки подобны массивам символов, поэтому их часто преобразуют к массивам, чтобы использовать богатый набор методов, а потом результат делают строкой.
 
=== Ассоциативные массивы ===
 
[[w:Ассоциативный массив|Ассоциативные массивы]] подобны массивам [[w:упорядоченная пара|упорядоченных пар]]. Работают они подобно словарям: фигурная скобка символизирует боковой вид на открытую книгу, а стрелка <code>=></code> покажет читателю связь каждого одного с чем-то другим. Вторая фигурная скобка говорит, что пора закрывать книгу.
<source lang=ruby>{ "мама" => "мыла раму", 807 => "Это число улыбается!"}</source>
Но можно и без фигурных скобок, одной стрелкой:
<source lang=ruby> "npeBeg" => "MegBeg"</source>
Например:
<source lang=ruby>puts maccuB["мама"] #-> мыла раму
puts maccuB["807"] #-> nil
puts maccuB[807] #-> Это число улыбается!
puts maccuB[1] #-> nil
puts maccuB["npeBeg"]#-> MegBeg</source>
 
Ассоциативные массивы оставляют возможность хранения данных разного типа только в ассоциативном виде.
 
=== Диапазоны значений ===
 
Чтобы было удобней получать подмассив или подстроку, был введен простенький тип данных — диапазон (класс <code>Range</code>). Диапазон формируется тремя элементами: начало, конец и тип протяженности (символ <code>..</code> или <code>...</code>). Начало и конец должны быть одного типа данных (одного класса) и быть '''перечислимыми''', что значит, иметь метод <code>.succ</code>. Пример диапазонов:
 
<source lang=ruby>'a'..'z'
'a'...'z' # то же, что и 'a'..'y'
1..100
1...100 # то же, что и 1..99</source>
 
Диапазон-мотоцикл (<big><code>..</code></big>) проезжает от первого указанного объекта к его <code>.succ</code> (''succedent'' — «последующему»), и до последнего включительно. Три точки — то же, но мотоцикл остановился прямо перед последним элементом. Ещё раз:
 
<big><code>1..99</code>&equiv;<code>1...100</code></big>
 
{{info|Объекты, имеющие <code>.succ</code> называют [[w:Последовательность|последовательными]]: этим методом можно по текущему элементу достоверно определить следующий.}}
 
=== Классы и объекты ===
Самодельные и неабстрактные, составные типы данных называются классами. Если для вас это новость, то почитайте [[Объектно-ориентированное программирование|викиучебник об объектно-ориентированном программировании]] или [[w:Объектно-ориентированное программирование|статью в Википедии]]. Вообще, в Руби ''всё'' в конечном счёте принадлежит классу <code>Object</code>.
<source lang=ruby>str = "Aз есмь строка"
str.class #-> объект класса String
str.class.superclass #-> подкласса класса Object</source>
Классы можно определять и создавать по ним объекты. Внутри класса может быть много всего интересного, и у него может быть фамильное [[w:Дерево (теория графов)|дерево]], то есть классы Руби поддерживают наследование. Однако заметим, что [[w:множественное наследование|множественное наследование]] в Руби не разрешается. И ещё много всего интересного можно сделать с классами и объектами. Но об этом позже.
 
== Подробнее о числах ==
Изначально числа представлены тремя типами: два целых типа (классы <code>Fixnum</code> и <code>Bignum</code>) и один дробный (класс <code>Float</code>). Возможно подключение дополнительных типов, — например, [[w:Комплексное число|комплексных]] и [[w:Рациональное число|рациональных]] чисел, но пока ограничимся тремя.
 
=== Целые числа ===
Целые числа в Ruby не ограничены по величине, то есть могут хранить сколь угодно большие значения. Для обеспечения такого волшебного свойства было создано два класса. Один из них хранит числа меньшие <math>2^{31}</math> (по модулю), а второй — всё, что больше. По сути, для больших чисел создается массив из маленьких, а раз массив не имеет ограничений по длине, то и число получается неограниченным по значению.
 
Как только число типа <code>Fixnum</code> становится больше <math>2^{31}</math> (по модулю), то оно преобразовывается к классу Bignum. Если число типа Bignum становится меньше <math>2^{31}</math>, то оно преобразовывается к типу <code>Fixnum</code>.
 
При записи целых чисел сначала указывается знак числа (знак <code>+</code> обычно не пишется). Далее идет основание [[w:система счисления|системы счисления]], в которой задается число (если оно отлично от [[w:десятичная система счисления|десятичной]]): <code>0</code> — для [[w:восьмеричная система счисления|восьмеричной]], <code>0x</code> — для [[w:шестнадцатеричная система счисления|шестнадцатеричной]], <code>0b</code> — для [[w:двоичная система счисления|двоичной]]. Затем идет последовательность цифр, выражающих число в данной системе счисления. При записи чисел можно использовать символ подчеркивания, который игнорируется при обработке. Чтобы закрепить вышесказанное, посмотрим примеры целых чисел:
<source lang=ruby># тип Fixnum
123_456 # подчеркивание игнорируется
-567 # отрицательное число
0xbad # шестнадцатеричное число
0377 # восьмеричное
-0b101010 # отрицательное двоичное
0b0101_0101 # подчеркивание игнорируется
# тип Bignum
123_456_789_123_456 # подчеркивание игнорируется
-123_456_789_123_456 # отрицательное
07777777777777777777 # восьмеричное большое</source>
 
Как видно из примеров, маленькие целые (<code>Fixnum</code>) и большие целые (<code>Bignum</code>) отличаются только значением.
 
=== Дробные числа ===
Дробные числа задаются только в десятичной системе счисления, при этом для отделения дробной части используется символ <code>.</code> (точка). Для задания дробных чисел может быть применена и экспоненциальная форма записи: два различных представления <code>0.1234e2</code> и <code>1234e-2</code> задают одно и тоже число <code>12.34</code>.
 
<source lang=ruby># тип Float
-12.34 # отрицательное дробное
0.1234е2 # экспоненциальная форма для числа 12.34
1234е-2 # экспоненциальная форма для числа 12.34</source>
 
Следует упомянуть, что дробные числа имеют фиксированный диапазон значений в отличие от целых чисел. Этот недостаток легко устраняется подключением библиотеки <code>mathn</code> (подключаются рациональные и комплексные числа).
 
=== Семейный портрет чисел ===
[[Изображение:RubyNumericClasses.png|thumb|right|Числовые типы данных]]
 
В отличие от большинства элементарных типов данных, числа обладают своей иерархией. Все числа в Руби наследованы от класса Numeric (Числовой). Поэтому, если хотите добавить новый метод ко всем числам, то нужно расширять именно этот класс. Далее идет деление чисел: Integer ([[w:Целое число|целое]]), Float ([[w:Десятичная дробь|дробное]]) и Complex ([[w:Комплексное число|комлексное]]). При желании можно добавить и Rational ([[w:Рациональное число|рациональное]]), но на данном семейном портрете оно отсутствует.
 
От класса Integer наследуются <big>два</big> класса: Fixnum (целое маленькое) и Bignum (целое большое). К первому относятся все числа, [[w:Абсолютная величина|по модулю]] меньшие <math>2^{31}</math> , а ко второму — все остальные.
 
{{Внимание|
* Fixnum автоматически становится Bignum по превышении <math>2^{31}</math> по модулю. И наоборот, падая ниже, Bignum преобразуется в Fixnum.
* Из отрицательного числа можно получить корень, когда подключена библиотека <code>mathn</code>. Он будет типа Complex (комплексное).
* Как только число типа Complex (комплексное) лишается мнимой части, то оно становится либо Integer (Fixnum или Bignum), либо Float (в зависимости от типа действительной части). Если подключена библиотка <code>mathn</code>, получится число типа Rational (рациональное).
* Если в результате арифметических действий в числе типа Rational знаменатель приравнивается 1, то оно преобразуется к числу Integer (Fixnum или Bignum).}}
 
=== Арифметические операции ===
 
Арифметические операции в Ruby обычны: [[w:Сложение|сложение]] (<code>+</code>), [[w:Вычитание|вычитание]] (<code>-</code>), [[w:Умножение|умножение]] (<code>*</code>), [[w:Деление (математика)|деление]] (<code>/</code>), получение остатка от деления (<code>%</code>), возведение в степень (<code>**</code>).
<source lang=ruby>6 + 4 #-> 10
6 - 4 #-> 2
6 * 4 #-> 24
6 / 4 #-> 1
6 % 4 #-> 2
6 ** 4 #-> 1296</source>
 
Эти операции используются как дробными, так и целыми числами (а также рациональными дробями и комплексными).
 
Порядок вычисления обычный, как учили в школе. Для изменения приоритета применяются круглые скобки:
<source lang=ruby>2 + 2 * 2 #-> 6
(2 + 2) * 2 #-> 8</source>
 
Первое, что бросается в глаза — результат арифметической операции двух целых чисел всегда будет целым. Особенно это видно при делении:
<source lang=ruby>1/3 #-> 0
2/3 #-> 0
3/3 #-> 1</source>
 
{{Рамка}}Если все аргументы арифметического выражения целые числа, то результат будет целым, если хотя бы одно дробное, то результат будет дробным.
{{Начало цитаты}}
Одна вторая в Ruby ноль,<br />
А три вторые — единица.<br />
Запомнить надо эту соль,<br />
Чтоб результату не дивиться.
{{Конец цитаты}}
{{Акмар}}
 
Посмотрим, каковы результаты, когда одно из чисел дробное.
<source lang=ruby>6.0 + 4 #-> 10.0
6 - 4.0 #-> 2.0
6.0 * 4.0 #-> 24.0
6.0 / 4 #-> 1.5 (одно из чисел дробное, значит результат дробный)
6.0 % 4 #-> 2.0
6 ** 4.0 #-> 1296.0</source>
 
Лучше проверить эти сведения самостоятельно. Для этого даже не обязательно [[#Начало работы|устанавливать интерпретатор Ruby]]. Достаточно лишь зайти на сайт [http://tryruby.hobix.com/ try ruby!].
 
==== Поразрядная арифметика ====
{| style="float:right;margin:0.5em 0 0.5em 1em;" class="standard" border=1
|-
! Знак операции || Название
|-
| <center><code>&</code></center>
| побитовое «и»
|-
| <center><code><nowiki>|</nowiki></code></center>
| побитовое «или»
|-
| <center><code>^</code></center>
| побитовое «исключающее или»
|-
| <center><code><<</code></center>
| побитовый сдвиг влево
|-
| <center><code>>></code></center>
| побитовый сдвиг вправо
|-
| <center><code>~</code></center>
| побитовая инверсия
|}
 
Операции побитовой арифметики заимствованы из языка [[w:Си (язык программирования)|Си]]. На этот раз без всяких экзотических особенностей.
<source lang=ruby>6 & 4 #-> 4
6 | 4 #-> 6
6 ^ 4 #-> 2
6 << 4 #-> 96
6 >> 4 #-> 0 (чересчур намного сдвинули)
~4 #-> -5 (операция только над одним аргументом)</source>
 
Здесь, вроде, всё понятно и без дополнительных пояснений. А если не понятно, то справочник по языку [[w:Си (язык программирования)|Си]] поможет.
 
==== Операции с присвоением ====
 
Часто можно встретить выражения вида:
<source lang=ruby>ogHo_4ucJIo += gpyroe_4ucJIo</source>
 
Это выполнение операции сразу с присваиванием. Вышеуказанная запись равнозначна следующей:
<source lang=ruby>ogHo_4ucJIo = ogHo_4ucJIo + gpyroe_4ucJIo</source>
 
Вполне естественно, что вместо операции <code>+</code> может использоваться любая другая, а вместо чисел могут быть другие типы данных.
 
<source lang=ruby>cTpoka = "едем"
cTpoka += ", "
cTpoka *= 3
cTpoka #-> "едем, едем, едем, "
 
maccuB = [1,2,3]
maccuB += [4,5]
maccuB #-> [1,2,3,4,5]</source>
 
{{info|''При определении метода <code>+</code>, метод <code><nowiki>+=</nowiki></code> вы получаете в подарок''. Это правило касается всех бинарных операций, обозначаемых значками.}}
 
=== Методы явного преобразования типов ===
{| style="float:right;margin:0.5em 0 0.5em 1em;" class="standard" border=1
|-
! Метод || Операция
|-
| <code>to_f</code> || преобразовать в дробное
|-
| <code>to_i</code> || преобразовать в целое
|-
| <code>to_s</code> || преобразовать в строку
|-
| <code>to_a</code> || преобразовать в массив
|}
 
Методы преобразования типов в Ruby традиционно начинаются с приставки <code>to_</code>. Последующая буква — это сокращение от названия класса, в который происходит преобразование (<code>f</code> — <code>Float</code> — дробное, <code>i</code> — <code>Integer</code> — целое, <code>s</code> — <code>String</code> — строка, <code>a</code> — <code>Array</code> — массив). Посмотрим их действие на примере:<source lang=ruby>7.to_f #-> 7.0
7.9.to_i #-> 7
7.to_s #-> "7"
"7".to_a #-> ["7"]</source>
 
=== Случайное число ===
Часто требуется получить [[w:Случайная величина|случайное число]]. Пример:<source lang=ruby>rand(100) # 86
rand #-> 0.599794231588021</source>
 
В первом случае, метод <code>rand</code> возвращает <u>целое число</u> в диапазоне от 0 до 99 (на 1 меньше 100). Во втором случае, метод <code>rand</code> возвращает <u>дробное число</u> в диапазоне от 0.0 до 1.0 включительно. Различие в результате обусловлено передаваемым параметром:
* если передается параметр (в данном случае 100), то генерируется <u>целое</u> случайное число (в диапазоне <code>0...N-1</code>, где <code>N</code> — передаваемый аргумент);
* если параметр отсутствует, то генерируется <u>дробное</u> число в диапазоне от 0.0 до 1.0.
 
Есть способ предсказать весь ряд «случайных» чисел. Делается это при помощи метода <code>srand</code>. Ему передается целое число (идентификатор «случайной» последовательности). После этого, весь случайный ряд можно предугадать. Проведем эксперимент: берусь угадать массив, который будет сгенерен следующей программой.<source lang=ruby>srand 123
Array.new(5){ rand(100) } #-> [69, 71, 28, 42, 22]</source>
 
Если вы выполните данную программу у себя дома, то получите тот же самый массив. 123 — номер «случайной» последовательности. Измените его и массив изменится!
 
Если вызвать <code>srand</code> без параметра или не вызывать его вообще, то номер «случайной» последовательности выбирается случайным образом.
 
=== Хитрости ===
 
Задача: выдать целое число в [[w:двоичная система счисления|двоичной системе счисления]].
<source lang=ruby>ucxogHoe_4ucJIo = 1234
puts sprintf("%b",ucxogHoe_4ucJIo) # метод sprintf заимствован из Си
puts ucxogHoe_4ucJIo.to_s(2) # современный метод - означает "по основанию",
# аргументом может служить не только 8 и 16, но и 5, 30...
# На самом деле, основание системы в моей версии ruby не может
# превышать 36, что вполне объяснимо - 10 цифр и 26 букв латинского
# алфавита.</source>
 
Поменять порядок цифр данного числа на обратный:
<source lang=ruby>ucxogHoe_4ucJIo = 1234
puts ucxogHoe_4ucJIo.to_s.reverse # метод reverse переворачивает строку</source>
 
Получить значение N-го двоичного разряда данного целого числа:
<source lang=ruby>ucxogHoe_4ucJIo, N = 1234, 5
puts ucxogHoe_4ucJIo[ N ]</source>
 
Поменять целочисленные значения двух переменных без использования третьей переменной:
<source lang=ruby>ucxogHoe_nepBoe, ucxogHoe_BTopoe = 134, 234
ucxogHoe_nepBoe, ucxogHoe_BTopoe = ucxogHoe_BTopoe, ucxogHoe_nepBoe
puts ucxogHoe_nepBoe, ucxogHoe_BTopoe</source>
 
Округлить дробное число до двух разрядов:
<source lang=ruby>gpo6Hoe_4ucJIo = 3.1415926535
puts ( gpo6Hoe_4ucJIo * 100 ).to_i.to_f / 100
puts (( gpo6Hoe_4ucJIo + 0.005) * 100 ).to_i / 100.0
puts sprintf( "%.2f", gpo6Hoe_4ucJIo ).to_f # полуСишный способ =)</source>
На самом деле во второй строке оставляются 2 знака после запятой, а остальные просто отбрасываются безо всяких округлений, в то время как в третьей строке действительно происходит округление до двух знаков после запятой. Это легко проверить попытавшись округлить до 3 знаков после запятой:
<source lang=ruby>gpo6Hoe_4ucJIo = 3.1415926535
puts ( gpo6Hoe_4ucJIo * 1000 ).to_i.to_f / 1000 # => 3.141
puts (( gpo6Hoe_4ucJIo + 0.0005) * 1000 ).to_i / 1000.0 # => 3.142
puts sprintf( "%.3f", gpo6Hoe_4ucJIo ).to_f # => 3.142</source>
 
== Подробнее о массивах ==
 
Массивы — это тип данных, с которым вам придется работать постоянно. Облик большинства программ зависит именно от правильного (читай «изящного») использования массивов.
 
=== Способы создания массива ===
 
Массив создается как минимум тремя способами. Первый способ:
<source lang=ruby>[1,2,3,4,5,6]</source>
 
Вы просто перечисляете элементы массива через запятую, а границы массива обозначаете квадратными скобками. С таким методом создания массива мы уже встречались. А теперь попробуем второй способ, через вызов метода <code>.new</code> класса Array. От слов перейдем к делу:
<source lang=ruby>Array.new( 6 ){ |index| index + 1 } #-> [1,2,3,4,5,6]</source>
 
Параметром метода <code>.new</code> является количество элементов будущего массива (в данном случае это число 6). В фигурных скобках указано, как мы будем заполнять массив. В данном случае, значение элемента массива будет больше на единицу его индекса. Третий способ заключается в создании объекта типа Range (диапазон) и вызове метода <code>.to_a</code>:
<source lang=ruby>(1..6).to_a #-> [1,2,3,4,5,6]</source>
 
Есть еще тысяча и один способ, но эти три используются чаще всего.
 
=== Диапазоны ===
 
Методом <code>to_a</code> очень удобно создавать из диапазона массив, содержащий упорядоченные элементы данного диапазона.
<source lang=ruby>(1..10).to_a #-> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
('a'..'d').to_a #-> ['a', 'b', 'c', 'd']</source>
 
Раз уж речь зашла о диапазонах, то давайте посмотрим, как они позволяют получать подмассивы. И насколько изящно у них это получается. Рассмотрим массив:
<source lang=ruby>[ 'a', 'b', 'c', 'd', 'e']</source>
 
Традиционно нумерация массива начинается с нуля и возрастает по одному:
<code>[ 'a'<math>{}^{0}</math>, 'b'<math>{}^{1}</math>, 'c'<math>{}^{2}</math>, 'd'<math>{}^{3}</math>, 'e'<math>{}^{4}</math> ]</code>
 
Такая нумерация называется в Ruby положительной индексацией. Хм, — скажете вы, — а есть еще и отрицательная? Да, есть!
<code>[ 'a'<math>{}^{+0}_{-5}</math>, 'b'<math>{}^{+1}_{-4}</math>, 'c'<math>{}^{+2}_{-3}</math>, 'd'<math>{}^{+3}_{-2}</math>, 'e'<math>{}^{+4}_{-1}</math> ]</code>
 
Плюсы расставлены лишь для красоты. Но вернемся к отрицательной индексации. Каков ее смысл? Чтобы его пояснить, давайте решим задачку: дан массив, требуется получить предпоследний элемент.
 
<code>maccuB = [ 'a', 'b', 'c', <u>'d'</u>, 'e']
maccuB[ maccuB.size - 2 ] #-> 'd'</code>
 
В данном случае мы использовали метод <code>.size</code>, который возвращает размер массива. Разработчики заметили, что вызов <code>maccuB.size</code> приходится писать довольно часто, и решили от него избавиться. Вот что получилось:
 
<code>maccuB = [ 'a', 'b', 'c', <u>'d'</u>, 'e']
maccuB[ -2 ] #-> 'd'</code>
 
Индекс -2 значит «второй с конца элемент массива». Вот так и появилась отрицательная индексация. Теперь давайте разберемся с диапазонами. Оказывается, в них тоже можно использовать отрицательную индексацию. Вот как можно получить все элементы массива кроме первого и последнего:
 
<code>maccuB = [ 'a', <u>'b', 'c', 'd'</u>, 'e']
maccuB[ 1..-2 ] #-> ['b', 'c', 'd']
Или так:
 
<code>maccuB = [ 'a', <u>'b', 'c', 'd'</u>, 'e']
maccuB[ 1...-1 ] #-> ['b', 'c', 'd']</code>
 
Второй вариант с тремя точками, что автоматически приближает правую границу диапазона на одну позицию влево.
 
=== О двумерных массивах ===
 
Для Ruby '''двумерный массив''' — ''это не более чем массив, содержащий одномерные массивы''. Вот несколько примеров двумерных массивов:
 
<source lang=ruby>[[1],[2,3],[4]] # разная длина элементов-массивов
[[1,2],[3,4]] # одинаковая длинна
[["прива","Привет"],["пока","Всего хорошего"]] # двумерный массив (классика)
[["прива","Привет"],[1,["пока","Всего хорошего"]]] # гибрид двух-трех-мерного массива</source>
 
{{Внимание|
* Двумерность массива средствами языка не отслеживается. Вполне могут возникнуть гибриды разномерных массивов.
* Подмассивы внутри двумерного массива могут иметь произвольную длину.
* Элементы из двумерного массива достаются последовательно: сначала элемент-массив, потом элемент.
}}
 
=== Методы работы с массивами ===
 
Разнообразие и полезность методов у массивов создаёт впечатление, что все сложные алгоритмы уже реализованы. Это не так, программистам Руби дана действительно обширная библиотека методов. Здесь мы рассмотрим лишь самые употребимые; остальные ищите в справочнике.
 
==== Получение размера массива ====
 
В Ruby массивы динамические: в каждый конкретный момент времени неизвестно сколько в нем элементов. Чтобы не плодить тайн подобного рода и был реализован метод <code>.size</code>:
 
<code>[1,'считайте',3,'количество',5,6,'зяпятых',2,5]<u>.size</u> #-> 9</code>
 
Мы явно указали массив, но на его месте могла стоять переменная:
 
<code><u>maccuB = </u>[1,'считайте',3,'количество',5,6,'запятых',2,5]
<u>maccuB</u>.size #-> 9</code>
 
{{info|Замечено, что количество запятых в объявлении одномерного массива на единицу меньше его размера. Поэтому, если подсчитать запятые и добавить единицу, то получается размер массива}}
{{info|Метод <code>.size</code> есть у многих классов. Например, у ассоциативных массивов и строк. И даже у целых чисел.}}
 
==== Поиск максимального/минимального элемента ====
 
Вспомните сколько усилий вам приходилось прилагать, чтобы найти максимальный элемент? А сколько раз вы повторяли этот кусок кода в своих программах? Ну а в Ruby поиск максимального элемента осуществляется при помощи метода <code>.max</code>, а в более сложных случаях при помощи метода <code>.max_by</code> (начиная с версии 1.9). Вот как это выглядит:
 
<code>['у','попа','была','собака']<u>.max</u> #-> 'у' максимальный по значению
['у','попа','была','собака']<u>.max_by{ |elem| elem.size }</u> #-> 'собака' максимальный по размеру строки</code>
 
Методы <code>.min</code> и <code>.min_by</code> работают аналогично:
 
<code>['у','попа','была','собака']<u>.min</u> #-> 'была' минимальный по значению
['у','попа','была','собака']<u>.min_by{ |elem| elem.size }</u> #-> 'y' минимальный по размеру строки</code>
Ну как? А в Руби эти методы уже давно.
 
==== Упорядочение ====
 
Не буду травить душу долгими байками. Чтобы упорядочить массив, нужно вызвать метод <code>.sort</code> или <code>.sort_by</code> (начиная с версии 1.8).
 
<code>['у','попа','была','собака']<u>.sort</u>
#-> ['была','попа','собака','у'] сортировка по значению
['у','попа','была','собака']<u>.sort_by{ |elem| elem.size }</u>
#-> ['у','попа','была','собака'] сортировка по размеру строки</code>
 
Остается только добавить, что массивы упорядочиваются по возрастанию. Если вам надо по убыванию, то придется писать собственный метод [[w:Сортировка пузырьком|сортировки пузырьком]]. Шутка! По правде же, есть много способов выстроить массив по убыванию. Пока мы будем использовать метод <code>.reverse</code>, обращающий массив.
 
==== Обращение массива ====
 
{{Рамка}}'''Обращение массива''' — это изменение порядка элементов на обратный, то есть первый элемент становится последним, второй элемент предпоследним и так далее.{{Акмар}}
 
Для обращения массива существует метод <code>.reverse</code>. Применим его к предыдущим примерам, чтобы получить сортировку по убыванию:
 
<code>['у','попа','была','собака'].sort<u>.reverse</u> #-> ['у','собака','попа','была']
['у','попа','была','собака'].sort_by{ |elem| elem.size }<u>.reverse</u> #-> ['собака','была','попа','у']</code>
 
Метод <code>.reverse</code> мы просто прицепили в конец предыдущего примера. Так можно выстроить произвольную цепочку допустимых методов; выполняться они будут по очереди, начиная с самого левого, то есть самого первого в цепочке.
 
==== Сложение/вычитание массивов ====
 
Для сложения массивов, строк и чисел используется метод <code>+</code>:
 
<code>[1,2,3,4]<u> + </u>[5,6,7]<u> + </u>[8,9] #-> [1,2,3,4,5,6,7,8,9]</code>
 
Плюс берёт массив справа и, будто это железнодорожный состав, прицепляет его к хвосту первого массива. Это называется [[w:Конкатенация|конкатенацией]].
 
Вычитаются массивы методом <code>-</code>, но происходит это сложнее, чем расцепление вагонов:
 
[ <s>1</s>, <s>1</s>, <s>2</s>, <s>2</s>, 3, 3, 3, <s>4</s>, 5 ]<u> - </u>[ <u>1</u>, <u>2</u>, <u>4</u> ] #-> [3, 3, 3, 5]
 
Из первого массива удаляются все элементы, имеющиеся во втором, независимо от их количества. Остальные элементы остаются без изменений, сохраняют относительные позиции.
 
==== Объединение и пересечение массивов (как множеств) ====
 
Очень часто приходится решать задачи, в которых нужно оперировать [[w:множество|множествами]]. У массивов припасено для этих целей два метода: <code>|</code> ([[w:объединение множеств|объединение]]) и <code>&</code> ([[w:пересечение множеств|пересечение]]).
 
Рассмотрим объединение множеств в действии:
<code>[1,2,3,4,5,5,6]<u> | </u>[0,1,2,3,4,5,7] #-> [1,2,3,4,5,6,0,7]</code>
 
Объединение получается вот так. Сначала массивы сцепляются:
<code>[1,2,3,4,5,5,6,0,1,2,3,4,5,7]</code>
 
Затем, начиная с первого вагона, инспектор идёт от вагона к вагону, удаляя элементы, которые уже встречались. После зачистки получается настоящее логическое объединение.
 
На деле это выглядит так:
<code>[1,2,3,4,5,<s>5</s>,6,0,<s>1</s>,<s>2</s>,<s>3</s>,<s>4</s>,<s>5</s>,7]</code>
 
Зачеркнутые числа — это удаленные элементы (дубликаты). Переходим от слов к делу:
<code>[1,2,3,4,5,5,6] & [0,2,1,3,5,4,7] #-> [1,2,3,4,5]</code>
 
При пересечении двух массивов, из первого удаляются все элементы, отсутствующие во втором. При этом относительный порядок остающихся элементов первого массива сохраняется.
<code>[1,2,3,4,5,<s>5</s>,<s>6</s>] & [<s>0</s>,<u>2</u>,<u>1</u>,<u>3</u>,<u>5</u>,<u>4</u>,<s>7</s>]</code>
 
Всё просто. Важно лишь помнить, что <code>|</code> и <code>&</code> не изменяют ни первый, ни второй исходные массивы. Они через описанные процедуры создают новый массив. Чтобы тот не уехал от вас, нужно присвоить его; но не себе, а переменной, приготовленной слева от <code>=</code>.
 
{{Внимание|В итоге пересечения или объединения множеств получается массив, не содержащий дубликатов.}}
 
==== Удаление дубликатов ====
 
Для удаления дубликатов (повторяющихся элементов массива) в Ruby используется метод <code>.uniq</code>:
<code>[1,2,3,4,5,5,6,0,1,2,3,4,5,7]<u>.uniq</u> #-> [1,2,3,4,5,6,0,7]</code>
 
Процесс зачистки массива от дубликатов такой же, как и в объединении.
<code>[1,2,3,4,5,<s>5</s>,6,0,<s>1</s>,<s>2</s>,<s>3</s>,<s>4</s>,<s>5</s>,7]</code>
 
Поэтому объединение массивов можно записать как <code>( nepBblu_maccuB + BTopou_maccuB ).uniq</u>. Но проще, конечно, объединять палкой.
 
==== Сплющивание массивов ====
 
Метод <code>.flatten</code> делает из многомерного массива простой, длинный одномерный массив. Он как бы расплющивает его. Например, чтобы найти в двумерном массиве наибольший элемент, мы сперва расплющим массив, а потом найдём максимум методом <code>.max</code>:
 
<code>maccuB = [[1,2],[3,4]]
maccuB<u>.flatten</u>.max #-> 4</code>
 
Расплющивание происходит в несколько этапов. Сначала происходит удаление всех квадратных скобок.
 
<code><s>[[</s>1,2<s>]</s>,<s>[</s>3,4<s>]]</s></code>
 
А потом, две квадратные скобки добавляются слева и справа. Но делать это надо быстро, чтобы элементы не успели разбежаться.
 
<code><u>[</u>1,2,3,4<u>]</u></code>
 
Вот и все! У нас они разбежаться не успели. Повторите данное упражнение на других массивах (двумерных, трехмерных и т.д).
 
==== Удаление неопределенных(nil) элементов ====
 
Функцию удаления nil элементов массива выполняет метод <code>.compact</code> например:
 
<code>maccuB = [1,nil,2,nil,3]
maccuB<u>.compact</u> #-> [1,2,3]</code>
 
==== Транспонирование двумерного массива ====
 
Задача: дан двумерный массив. Вывести одномерный массив с максимумами каждого из столбцов. Хм... посмотрим сперва, как эта задача решается для строчек, а не столбцов:
<source lang=ruby>maccuB_2D = [[1,2],[3,4]]
maccuB_2D.map{ |maccuB| maccuB.max } #-> [2,4]</source>
 
Чтобы решить задачу в первоначальном варианте, нам надо лишь предварительно транспонировать массив (поменять местами строки и столбцы):
<source lang=ruby>maccuB_2D = [[1,2],[3,4]]
maccuB_2D.transpose.map{ |maccuB| maccuB.max } #-> [3,4]</source>
 
Метод <code>.transpose</code> как раз и занимается транспонированием. Это позволяет с легкостью решать задачи про столбцы приемами, схожими с задачами про строки.
 
==== Размножение массивов ====
 
Речь пойдет не о почковании, а о методе, который позволяет умножать массив на целое число. В результате такого умножения мы получим массив, состоящий из нескольких копий элементов исходного массива.
 
<code>["много", "денег", "прячет", "теща"]<u> * 2</u>
#-> ["много", "денег", "прячет", "теща", "много", "денег", "прячет", "теща"]</code>
 
Того же самого эффекта можно добиться сцепив массив необходимое количество раз:
 
<code>maccuB = ["много", "денег", "прячет", "теща"]
maccuB<u> + </u>maccuB #-> ["много", "денег", "прячет", "теща", "много", "денег", "прячет", "теща"]</code>
 
Заметили, что есть некоторая параллель с целыми числами? Умножение можно заменить сложением и наоборот!
 
==== Функциональность [[w:Стек|стека]] ====
 
Часто и во многих алгоритмах надо добавить элемент в конец массива:
 
<code>maccuB = [1,2,3,4,5]
<u>maccuB[ maccuB.size ] = 6</u>
maccuB #-> [1,2,3,4,5,6]</code>
 
И если уж добавили, то надо как-то его и удалить. Делается это примерно так:
 
<code>maccuB = [1,2,3,4,5,6]
<u>maccuB[0...-1]</u> #-> [1,2,3,4,5]</code>
 
Но как всегда, эти задачи возникали слишком часто и их решили реализовать в виде методов. Методы назвали <code>.push</code> («втолкнуть» в конец массива) и <code>.pop</code> («вытолкнуть» элемент из массива):
 
<code>maccuB = [1,2,3,4,5]
maccuB<u>.push( 6 )</u>
maccuB #-> [1,2,3,4,5,6]
maccuB<u>.pop</u> #-> 6
maccuB #-> [1,2,3,4,5]</code>
 
==== Функциональность очереди и списка ====
 
Чтобы можно было использовать массив в качестве [[w:Очередь|очереди]] и/или [[w:Линейный список|списка]], потребуется сделать всего лишь пару методов. Первый из них добавляет элемент в начало массива, а второй удаляет элемент из начала. Давайте посмотрим, как это делается универсальными методами <code>[]</code>, <code>[]=</code> и <code>+</code>:
 
<code>maccuB = [1,2,3,4,5]
# добавим элемент в начало массива
maccuB = <u>[6] + maccuB</u>
maccuB #-> [6,1,2,3,4,5]
<u>maccuB[0]</u> #-> 6
# удалим элемент из начала массива
maccuB = <u>maccuB[1..-1]</u>
maccuB #-> [1,2,3,4,5]</code>
 
Теперь посмотрим, какие методы реализуют точно такую же функциональность:
 
<code>maccuB = [1,2,3,4,5]
# добавляем элемент в начало массива
maccuB<u>.unshift( 6 )</u> #-> [6,1,2,3,4,5]
# удаляем из начала массива
maccuB<u>.shift</u> #-> 6
maccuB #-> [1,2,3,4,5]</code>
 
Удаляющий метод — <code>.shift</code> («сдвинуть»), а метод, добавляющий элемент в начало массива, называется <code>.unshift</code> (непереводимое слово, стремящееся означать нечто противоположное слову «сдвинуть»).
 
==== Мнемограмма для методов стека/очереди/списка ====
 
Мнемограмма для методов <code>.shift</code>, <code>.unshift</code>, <code>.pop</code> и <code>.push</code>:
 
<code>.unshift( 0 )</code> <code>.push( 6 )</code>
<code>[1,2,3,4,5]</code>
<code>.shift</code> <code>.pop</code>
 
Методы с параметром (сверху) добавляют элемент в массив, а методы без параметра (снизу) — удаляют. По желанию можно дорисовать стрелочки.
 
{{Начало цитаты}}
Метод <code>.shift</code> сдвигает влево,<br />
Метод <code>.pop</code> — направо.<br />
Метод <code>.push</code> к концу цепляет,<br />
А <code>.unshift</code> — к началу.
{{Конец цитаты}}
 
<small>Замечание. Имена методов unshift/shift неинтуитивны. Во-первых, они не напоминают о том, что работа идет с головой массива, а не с хвостом, во-вторых ничего не говорят о том, идет заполнение или опустошение стека. Можно создать для этих методов псевдонимы с говорящими именами, например, feed/spit (кормить/выплевывать):<br />
<source lang="ruby">
class Array
alias feed :unshift
alias spit :shift
end
</source>
</small>
 
==== Создание своих классов, работающих как массивы ====
Если потребуется написание своего класса, который работает, как описанные массивы, то возникают некоторые тонкости.
Дело в том, что реализация всех описанных методов займет жуткое количество времени и сил. На самом деле, для реализации
большинства описанных методов, достаточно реализовать <code>.each</code>
 
Где это может понадобиться? Например, вы реализуете класс, который умеет читать из файла записи определенной структуры.
Основную его логику занимает именно чтение нужного формата, кеширование, разбор, десериализация и т.п.
 
Просто реализуйте <code>.each</code> и включите в ваш класс mixin <code>Enumerable</code>. В нем находится реализация
методов, таких как <code>.inject</code>, <code>.each_with_index</code> и т.п.
 
=== Логические методы ===
 
{{Рамка}}'''Логический метод''' — это метод, результатом которого является логическое выражение (true или false).{{Акмар}}
 
По японской традиции, имена логических методов принято заканчивать <code>?</code> (вопросительным знаком). Это позволяет также получить список логических методов, вызываемых в данном случае: просто отобрать из всех имеющихся методов те, что кончаются на <code>?</code>. Делается это при помощи небольшой вспомогательной программы:
<source lang=ruby>maccuB = [1,2,2,3]
puts maccuB.methods.grep(/\?$/)</source>
 
Для удобства, можно упорядочить полученный список:
<source lang=ruby>maccuB = [1,2,2,3]
puts maccuB.methods.grep(/\?$/).sort</source>
 
==== Есть элемент в массиве? ====
 
Как узнать, есть ли некоторый элемент в массиве? Попробуем решить эту задачу при помощи метода <code>.size</code> и итератора <code>.find_all</code>:
<source lang=ruby>maccuB = [1,2,3,4,5,6,7]
uckomoe = 5 # число, которое мы будем искать
maccuB.find_all{ |elem| elem == uckomoe }.size != 0 #-> true
# это значит, что такое число есть</source>
 
Использование связки из трех методов (<code>!=</code>, <code>.find_all</code> и <code>.size</code>) для такой задачи — возмутительно! Разработчики не могли с этим долго мириться и реализовали метод специально для этой задачи. Имя ему — <code>.include?</code>. Перепишем нашу задачу, но на этот раз будем использовать правильный метод:
 
<code>maccuB = [1,2,3,4,5,6,7]
uckomoe = 5 # число, которое мы будем искать
maccuB<u>.include?( uckomoe )</u> #-> true
# что бы это значило?</code>
 
{{Начало цитаты}}
Мутный горизонт скрывает берег,<br />
Ветер мокр, холоден и лют.<br />
Есть ли в озере акулы, я проверю<br />
Методом логическим <code>.include?</code>
{{Конец цитаты}}
 
<code>o3epo = ["правый берег", "ветер", "вода", "вода", "вода", "окунь", "вода", "вода", "левый берег"]
o3epo<u>.include?("акула")</u> #-> false</code>
 
Опытным путем мы доказали, что акулы в озере не водятся.
 
==== Массив пустой? ====
 
Если вы хотите задать массиву вопрос «ты пуст ли?», но боитесь обидеть, то можете пойти окружным путем. Например, спросить у него: ты равен пустому массиву?
 
<code>nycTou_maccuB = []
noJIHbIu_maccuB = [1,2,2,3]
nycTou_maccuB<u> == []</u> #-> true
noJIHbIu_maccuB<u> == []</u> #-> false</code>
 
Еще можно задать вопрос: твой размер равен нулю?
 
<code>nycTou_maccuB = []
noJIHbIu_maccuB = [1,2,2,3]
nycTou_maccuB<u>.size == 0</u> #-> true
noJIHbIu_maccuB<u>.size == 0</u> #-> false</code>
 
Но наш вам совет: не стоит искать обходных путей. Спросите его напрямую: <code>.empty?</code> («пуст?»):
 
<code>nycTou_maccuB = []
noJIHbIu_maccuB = [1,2,2,3]
nycTou_maccuB<u>.empty?</u> #-> true
noJIHbIu_maccuB<u>.empty?</u> #-> false</code>
 
<!-- Не согласен!
Выражение not maccuB.empty? выглядит более понятным, чем maccuB.any?
А если хочется короче, то написать !maccuB.empty? тоже труда не составляет.
В общем, жду дискуссии по этой теме!
~~~~
 
==== И наоборот ====
 
В Руби принято избегать отрицания условия. Например, если вам нужно сделать что-то, если массив <b>не пуст</b>, можно
воспользоваться методом, обратным <tt>empty?</tt>. Этот метод называется <tt>any?</tt>
 
<code>maccuB = [1,2,4]
maccuB.length > 0 #-> true
maccuB.empty? #-> false
maccuB.any? #-> true
</code>
-->
 
=== Итераторы ===
Массивы — эти эшелоны переменных, эти ожерелья запятых и элементов, — часто приходится проходить целиком, обследуя каждый элемент. Да и не только массивы, но и любые последовательности чего-нибудь.
 
В старину люди делали это [[w:цикл (программирование)|циклами]]. В Руби у списочных структур данных есть встроенные методы, которые проходят весь ряд поэлементно, но, в отличие от циклов:
* не зацикливаются: счётчик цикла нельзя докручивать;
* выполняются заведомое число раз;
* их много, и каждый делает своё дело.
<center><big>Имя им — <big>'''''итераторы'''''</big></big>.</center>
 
==== Изменение всех элементов массива ====
 
Изменить все элементы массива можно по-всякому. Начнём с обнуления:
 
<code>maccuB = ['шифровка','Штирлица','в','Центр','секретно']
maccuB.map{ <u>0</u> } #-> [0,0,0,0,0]</code>
 
{{Внимание|В приведенном примере каждый элемент массива будет заменен нулем независимо от того, чем является этот элемент. Например, при попытке обнулить таким образом двумерный массив <code>[[1, 2], [3, 4]]</code>, в результате получим <code>[0, 0]</code>.}}
 
Используется '''итератор''' <code>.map</code>, за которым следует <big>'''''блок'''''</big>, — кусочек кода, схваченный лапками-фигурными скобками. <code>.map</code> последовательно проходит <code>maccuB</code> и выполняет блок заново для каждого элемента. То, что выходит из блока, итератор <code>.map</code> делает очередным элементом нового массива.
 
Можно дать элементу <code>.map</code> иное задание. Для этого зажимаем в фигурные скобы блока иной код:
 
<code>maccuB = [1,2,3,4,5]
maccuB.map{ |<u>elem</u>| <u>elem**2</u> } #-> [1,4,9,16,25]</code>
 
Прежде, чем блоку выдать квадрат очередного элемента, ему нужно знать этот элемент. Итератор <code>.map</code> даёт ему значение элемента, словно фотографию, обрамлённую слева и справа вертикальными чертами <code>|</code>. Чтобы блок смог взять эту фотографию, обязательно дать ей имя. В нашем случае это <code>elem</code>, но подходят и такие названия:
* <code>_3JleMeHt</code>
* <code>x</code>
* <code>y</code>
* <code>a_He_x0tuTe_li_4awe4Ku_4a9_c_LuMoHom</code>
{{Внимание|Но недопустимы названия вроде <code>BoT_Takoro</code> или <code>3Takoro</code>. Правила именования тут такие же, как и [[#Переменные|для обычных переменных]].}}
 
Вы уже, наверное, хорошо поняли, что в итераторах массивы обрабатываются по очереди; двери вагона расходятся, появляется элемент. Блок даёт ему прозвище, выполняет код в лапках-фигурных скобках. Затем переходит к следующему вагону, и там всё сначала.
 
Но ещё важно помнить, что элементы не уходят из первого вагона: блок лишь осматривает каждый элемент, берёт его значение, но не меняет. Всё, что получается в результате работы блока, садится в очередной вагон ''другого'' поезда.
 
То имя, что показывается в раздвижных дверях, — это не сам элемент, это лишь его копия. Фотография. Голограмма. Это даже не другая переменная, это не переменная вообще. Бессмысленно присваивать новое значение фотографии:
 
<code>maccuB = [1,2,3,4,5]
maccuB.map{ |elem| <s>elem = </s>elem**2 } # присваивание не имеет смысла: elem несёт лишь значение элемента, не являясь тем</code>
 
Из итератора <code>.map</code> выезжает другой поезд, у которого в каждом вместо соответствующего элемента первого поезда сидит результат вычисления блока. Используйте этот массив как-нибудь, иначе поезд уедет.
 
<code>maccuB = [1,2,3,4,5]
maccuB.map{ |elem| elem**2 } #-> <u>[1,4,9,16,25]</u>
maccuB #-> <u>[1,2,3,4,5]</u> - неизменный первый поезд.</code>
 
Можно присвоить его новой переменной <code>maccuB_c_kBagpaTaMu</code>. А можно заместить им существующую переменную <code>maccuB</code>:
 
<code>maccuB = [1,2,3,4,5]
<u>maccuB = </u>maccuB.map{ |elem| elem**2 } #-> [1,4,9,16,25]
maccuB #-> [1,4,9,16,25]</code>
 
Это общее явление Руби: методы (здесь — итераторы) не меняют объект (массив), с которым работают. Они лишь выдают результат, который потом можно использовать как аргумент или присвоить переменной.
 
Явление было воспето в фольклоре:
 
{{Начало цитаты}}
Метод <code>.map</code> все изменяет,<br />
Как кто пожелает<br />
И обижается на тех,<br />
Кто результат не сохраняет.
{{Конец цитаты}}
 
==== Отбор элементов по признаку ====
 
Вот как итератор <code>.find_all</code> выберет из массива все чётные элементы:
 
<code>maccuB = [1,2,3,4,5]
maccuB.find_all{ |elem| <u>elem%2==0</u> } #-> [2,4]</code>
 
<code>elem%2==0</code> — это вопрос "чётен ли <code>elem</code>?". Ответом, как всегда, будет <code>true</code> или <code>false</code>. Ведь "чётность" — это равенство нулю (<code>==0</code>) остатка деления (<code>%</code>) на 2.
 
Кстати, равенство нулю можно проверять и при помощи метода <code>.zero?</code>. А чётность тоже можно проверить разными способами:
<source lang=ruby>(elem%2).zero?
(elem&1).zero?
(elem[0]).zero? # Этот вариант круче всех</source>
 
Если на вопрос, заданный в блоке, ответ <code>true</code>, то <code>|elem|</code> (значение очередного элемента исходного массива), заносится в новый массив, который в итоге будет выводом из итератора <code>.find_all</code>.
 
{{Внимание|Выражение в блоке для <code>.find_all</code> должно быть логическим, то есть принимать значение <code>true</code> или <code>false</code>.}}
 
<!-- Рассмотрим феномен, который мы наблюдали уже в итераторе <code>.map</code>:
 
<code>maccuB = [1,2,3,4,5]
maccuB.find_all{ |elem| elem[0].zero? } #-> <u>[2,4]</u>
maccuB #-> <u>[1,2,3,4,5]</u></code>
 
{{Внимание|Итератор <code>.find_all</code> лишь создает новый массив на базе старого. Он не изменяет исходный массив. Чтобы сохранить результат работы <code>.find_all</code>, надо его присвоить какой либо переменной.}}
 
Вот как будет выглядеть программа без феномена:
 
<code>maccuB = [1,2,3,4,5]
<u>maccuB = </u>maccuB.find_all{ |elem| <u>elem[0].zero?</u> } #-> <u>[2,4]</u>
maccuB #-> <u>[2,4]</u></code>-->
 
{{Начало цитаты}}
Если нужно элементы<br />
По условию искать,<br />
То полезнее <code>.find_all</code><br />
Метода вам не сыскать!
{{Конец цитаты}}
 
 
<small>Мысль: красивее делить лишь последнюю цифру(так в третьем классе учат четность определять :) ). Тем более, когда нужно определить четность factorial(100) :).
В любом случае базовый класс нужно расширить чем-то вроде метода <code>.chetnoe?</code>.(с)Вовчик
</small>
 
<small>Ошибка: <code>(elem&1).zero?</code> проверит на нечетность. Для проверки на четность надо <code>!(elem&1).zero?</code></small>
 
<small>Ошибка комментатора: как раз наоборот. Разберем: <code>(elem&1)</code> - битовая операция, вернет первый бит числа (составляющую 2<sup>0</sup> ). То есть результатом будет 0, если elem - число четное, и 1 в ином случае.
<code>0.zero? #true</code>
<code>1.zero? #false</code>
</small>
 
==== Суммирование/произведение/агрегация элементов ====
 
Очень часто возникает задача найти сумму/произведение всех элементов массива. Для этих целей (начиная с версии 1.8) традиционно используется итератор <code>.inject</code>. Для демонстрации его работы, давайте найдем сумму элементов массива:
 
<code>maccuB = [1,2,3,4,5]
maccuB.inject( <u>0</u> ){ |<u>result, elem</u>| <u>result + elem</u> } #-> 15</code>
 
Рассмотрим все по порядку. Начнем с нуля. Его следует расшифровывать как '''<code>result = 0</code>''' перед началом работы итератора, то есть это начальное значение переменной <code>result</code> (переменной промежуточного результата).
 
Далее идет объявление двух переменных. Первая из них (<code>result</code>) будет хранить промежуточный результат. Вторая (<code>elem</code>) — фотография текущего элемента массива (или последовательности), мы такую уже видели.
 
После объявления описан алгоритм работы итератора. В данном случае ему предписано каждый элемент массива складывать с промежуточной суммой: <code>result + elem</code>.
 
{{Внимание|
* Присвоение переменной (которая описывает промежуточную сумму или текущий элемент) внутри блока указывает на непонимание работы итератора <code>.inject</code> (и многих других). Вам должно быть стыдно!
* Результат последнего вычисления автоматически запишется в переменную с промежуточным результатом.}}
 
Учитывая эти два замечания, напишем код, который является неправильным:
<code>maccuB = [1,2,3,4,5]
maccuB.inject( 0 ){ |result, elem| <s>result = </s>result + elem } #-> 15</code>
 
{{info|Имена переменных <code>result</code> и <code>elem</code> созданы богатым воображением автора. В ваших программах они могут называться иначе.}}
 
Изменим имена переменных:
 
<code>maccuB = [1,2,3,4,5]
maccuB.inject( 0 ){ |<u>pe3yJibTaT, nepemeHHa9</u>| pe3yJibTaT + nepemeHHa9 } #-> 15</code>
 
Невероятно, но ''от изменения имен переменных, результат остается прежним''. Помните это!
 
Для полноты картины решим еще одну задачку. На этот раз будем искать произведение всех элементов массива:
 
<code>maccuB = [1,2,3,4,5]
maccuB.inject( <u>1</u> ){ |pe3yJibTaT, nepemeHHa9| pe3yJibTaT <u>*</u> nepemeHHa9 } #-> 120</code>
 
Чтобы закрепить материал, решите задачу: найти произведение всех положительных элементов массива. Подсказка: используйте метод <code>.find_all</code>.
 
{{Начало цитаты}}
Элементов надо кучу<br />
Перемножить иль сложить? <br />
Есть <code>.inject</code> на этот случай,<br />
Его бы вам употребить.
{{Конец цитаты}}
 
==== Разбиение надвое ====
 
Итератор <code>.partition</code> делит массив на две части по некоторому бинарному признаку (чётности, положительности, наличию высшего образования и тому подобным). Вот как разделить массив на две части по признаку кратности трём:
 
<code>a = [1,2,3,4,5,6,7,8,9]
a.partition{ |x| <u>(x%3).zero?</u> } #-> <u>[ [3,6,9], [1,2,4,5,7,8] ]</u></code>
 
В результате работы итератора получился массив, состоящий из двух элементов-массивов. Первый элемент-массив содержит все элементы, которые удовлетворяют условию, а второй, которые не удовлетворяют. Обратите внимание, как проверяется кратность трем. Ничего не напоминает? Например, итератор <code>.find_all</code>? Нет? Ну и ладно!
 
Есть интересная хитрость, позволяющая разместить массив, полученный <code>.partition</code>, в две разные переменные:
 
<code>a = [1,2,3,4,5,6,7,8,9]
<u>one, two </u>= a.partition{ |x| (x%3).zero? }
<u>one #-> [3,6,9]</u>
<u>two #-> [1,2,4,5,7,8]</u></code>
 
Этот метод называется «присвоение списков» (multiple assignment) и широко используется в ситуациях, когда из метода надо вернуть более одного значения. Но об этом позднее.
 
{{Внимание|Меняйте названия переменных хотя бы для того, чтобы не распознали вашего списывания.}}
 
=== Логические итераторы ===
 
В версии 1.8 появилось несколько логических методов: <code>.all?</code> и <code>.any?</code>. Они положили начало такому классу методов, как логические итераторы.
 
{{Рамка}}'''Логический итератор''' — это итератор (метод, обрабатывающий все элементы последовательности), возвращающий значение логического типа — <code>true</code> или <code>false</code>.{{Акмар}}
 
Конечно же, идея логических итераторов долгое время летала в ноосфере. Существовали итераторы, которые являлись условно-логическими: они возвращали <code>nil</code> в случае неудачи и какой-либо объект — в случае удачи. В логическом [[w:Контекст|контексте]] поведение таких итераторов можно было посчитать логическим (<code>false</code> -> <code>nil</code>, а <code>true</code> -> число/строка/любой_объект). Примером условно-логического итератора служит метод <code>.detect</code>.
 
==== Все ли элементы удовлетворяют условию? ====
 
В [[w:Математическая логика|математической логике]] такой «итератор» называется [[w:Квантор|квантором]] общности, обозначается символом <math>\forall</math>. На языке Ruby он называется <code>.all?</code>. По сложившейся традиции, давайте посмотрим, как решалась бы эта задача до версии 1.8, то есть до появления логических итераторов:
 
<code>maccuB = [1,2,2,3]
maccuB.inject( <u>true</u> ){ |result, elem|
result && <u>( elem > 2 )</u>
} #-> false</code>
 
{{info|В примере используется так называемый механизм презумпции виновности. Переменной <code>result</code> присваивается значение <code>true</code>. Логическое умножение изменяет значение переменной <code>result</code> на <code>false</code>.}}
 
Данная программа проверяет, все ли элементы <code>maccuB</code>а больше двух. Давайте решим эту же задачу при помощи новоявленного логического итератора:
 
<code>maccuB = [1,2,2,3]
maccuB<u>.all?{ |elem| elem > 2 }</u> #-> false</code>
 
Несмотря на то, что код получился короче, результат остался прежним: утверждение, что все элементы массива больше двух, ложно.
 
==== Хотя бы один элемент удовлетворяет условию? ====
 
Вслед за [[w:Квантор|квантором]] общности (он же — логический итератор <code>.all?</code>), из [[w:Математическая логика|математической логики]] был перенесен и квантор существования — <math>\exists</math>. На языке Ruby он называется <code>.any?</code>. Чтобы оценить его по достоинству, посмотрим решение задачи без его участия. Проверим, содержит ли <code>maccuB</code> хотя бы один элемент больший двух:
 
<code>maccuB = [1,2,2,3]
maccuB.inject( <u>false</u> ){ |result, elem|
result || <u>( elem > 2 )</u>
} #-> true </code>
 
{{info|В данном примере используется так называемый механизм презумпции невиновности. Переменной <code>result</code> присваивается значение <code>false</code>. В результате логического сложения, происходит изменение значения переменной <code>result</code> на <code>true</code>.}}
 
Теперь, тоже самое, но через логический итератор <code>.any?</code>:
 
<code>maccuB = [1,2,2,3]
maccuB<u>.any?{ |elem| elem > 2 }</u> #-> true</code>
 
Естественно, что с появлением логических итераторов, реализация задач математической логики (в рамках языка Ruby) стала удобней.
 
=== Хитрости ===
Вот так можно сгенерировать «хороший пароль» — произвольную последовательность из чисел или латинских букв, общей длиной в 8 символов.
<source lang=ruby>cumBoJIbI = ['a'..'z','A'..'Z','0'..'9'].map{ |range| range.to_a }.flatten
puts (0...8).map{ cumBoJIbI[ rand( cumBoJIbI.size ) ] }.join</source>
Перемешать упорядоченный массив:
<source lang=ruby>maccuB = [1,2,3,4,5,6,7]
maccuB.sort_by{ rand } #-> перемешанный массив</source>
Выстроить элементы массива по убыванию без использования <code>.reverse</code>:
<source lang=ruby>maccuB = [2,1,3,5,6,7,4]
maccuB.sort{ |x,y| y <=> x } #-> [7,6,5,4,3,2,1]</source>
 
=== Задачи про массивы ===
{{info|Помимо нижеследующих задач, советуем вам взглянуть на задачи из нашего [[/Задачник|сборника задач]]}}
==== Одномерные ====
 
# Вывести индексы массива в том порядке, в котором соотвествующие им элементы образуют возрастающую последовательность.
# В численном массиве найти сумму отрицательных элементов.
# Найти все индексы, по которым располагается максимальный элемент.
# В массиве переставить в начало элементы, стоящие на четной позиции, а в конец -- стоящие на нечётной.
 
===== Одномерные целочисленные =====
 
# Найти все элементы, большие среднего арифметического элементов.
# К четным элементам прибавить первый элемент, а к нечетным — последний. Первый и последний элемент не изменять.
# Заменить все положительные элементы на значение минимального.
# Найти произведение всех четных элементов массива.
# Найти количество минимальных элементов.
# Вывести индексы элементов, меньших своего левого соседа.
[Пример решения [http://rubyclub.com.ua/messages/show/163]]
 
==== Двумерные ====
 
# Поменять первый и последний столбец массива местами.
# Упорядочить N-ый столбец.
# Упорядочить строки, содержащие максимальный элемент.
# Упорядочить строки, если они не отсортированы и перемешать, если они отсортированы.
# Упорядочить строки массива по значению элемента главной диагонали в каждой из строк (в исходном массиве).
# Найти номера строк, элементы которых упорядочены по возрастанию.
 
[Частичное решение [http://rubyclub.com.ua/messages/show/164]]
 
=====Двумерные целочисленные=====
 
# Найти максимальный элемент для каждого столбца, а затем получить произведение этих элементов.
# Найти минимум в двумерном массиве.
# Найти произведение положительных элементов.
# Найти сумму положительных элементов, больших К.
# Вычислить сумму и среднее арифметическое элементов главной диагонали.
# Найти номера строк, все элементы которых &mdash; нули.
 
== Подробнее об ассоциативных массивах ==
 
Различают два типа массивов: индексные, у которых в качестве индекса только целое число и ассоциативные, где индексом может быть любой объект.
 
{{info|Индексные массивы чаще всего называют просто «массивами», а ассоциативные массивы — «хешами» или «словарями».}}
 
Хеши можно представить как массив пар: <code>ключ => значение</code>. Но в отличие от массива, <u>хеш неупорядочен</u>: нельзя заранее сказать, какая пара будет первой, а какая последней. Правда, удобство использования массива это шибко не умаляет. Более того, поскольку в Ruby переменные не типизированы и методам с похожей функциональностью дают похожие имена, то использование хеша чаще всего равносильно использованию массива.
 
Несмотря на мощь хеша, использовать его не всегда целесообразно. Бывают задачи, решаемые с хешами легко и удобно, но таких задач мало. Чаще всего хватает использования массива. Но представление о классе задач для хеша надо иметь.
 
Давайте создадим хеш, где в качестве ключа будем использовать целое число:
 
<code>xew = {5=>3,1=>6,3=>2}
xew[ 5 ] #-> 3
xew[ 2 ] #-> nil это значит, что объект отсутствует
xew[ 3 ] #-> 2</code>
А вот так будет выглядеть та же самая программа, если мы будем использовать массив:
 
<code>maccuB = [nil, 6, nil, 2, nil, 3]
maccuB[ 5 ] #-> 3
maccuB[ 2 ] #-> nil
maccuB[ 3 ] #-> 2</code>
 
{{Рамка}}'''Первый случай применимости хеша''': если в массиве намечаются обширные незаполненные (то есть заполненные <code>nil</code>) области, то целесообразнее использовать хеш с целочисленным индексом.{{Акмар}}
 
Использовать хеш в данном случае лучше потому, что, формально, хеш для данного примера состоит из трех значащих пар, а массив — из шести элементов, из которых лишь три элемента значащие. Исходя из этого, можно заключить, что массив будет хранить избыточную информацию, а хеш — только нужную.
 
Продолжим поиски случаев применимости хеша и, на этот раз подсчитаем, сколько раз каждое число повторяется в данном целочисленном массиве. Решение массивом:
 
<code>maccuB = [1,2,1,2,3,2,1,2,4,5]
maccuB.uniq.map{ |i| [i, maccuB.find_all{ |j| j == i }.size ] }
#-> [[1, 3], [2, 4], [3, 1], [4, 1], [5, 1]]</code>
Алгоритм получается ужасным. Не буду утомлять излишними терминами, а замечу, что по одному и тому же массиву итераторы (в количестве двух штук) пробегают много раз. А ведь достаточно одной "пробежки". Понятное дело, что такая программа не сделает вам чести. В качестве упражнения, предалагаю вам решить эту задачу другим, более оптимальным, способом.
 
Теперь рассмотрим решение этой же задачи, но с применением хеша:
 
<code>maccuB = [1,2,1,2,3,2,1,2,4,5]
maccuB.inject( <u>Hash.new{ 0 }</u> ){ |result, i|
result[i] += 1
<u>result</u>
} #-> {5=>1, 1=>3, 2=>4, 3=>1, 4=>1}</code>
Удалось избавиться от лишних методов и обойтись лишь одной "пробежкой" итератора по массиву.
* Начальный хеш был создан хитроумной комбинацией <code>Hash.new{ 0 }</code>, что в переводе на русский означает примерно следующее: "создадим пустой хеш, в котором любому несуществующему ключу будет соответствовать <code>0</code>". Это нужно, чтобы суммирование (метод <code>+</code>) не выдавало ошибку вида: "не могу сложить <code>nil</code> и число типа Fixnum". В качестве упражнения, предлагаю вам заменить комбинацию <code>Hash.new{ 0 }</code> на <code>{}</code> и посмотреть, чем это чревато.
* зачем нужно дописывать <code>result</code>? Дело в том, что комбинация <code>result[i]+=i</code> имеет в качестве результата целое число (учитывая, что массив целочисленный), а не хеш. Следовательно, параметру <code>result</code> автоматически будет присвоено целое число (см. описание итератора <code>.inject</code>). На следующей итерации мы будем обращаться к <code>result</code>, как к хешу, хотя там уже будет храниться число. Хорошо, если программа выдаст ошибку, а если нет? Проверьте это самостоятельно.
 
В качестве упражнения, предлагаю вам переписать программу без вышеописанных двух особенностей (используйте метод <code>.update</code>, который появился в версии 1.8). Решение будет опубликовано ниже.
 
{{Рамка}}'''Второй случай применимости хеша''': если требуется подсчитать число элементов массива, то целессобразнее применять хеш. Кстати, вместо подсчета количества, можно использовать конкатенацию массивов или строк. Но это уже более сложные задачи, которые будут рассмотрены позже.{{Акмар}}
 
Теперь представим, что мы работаем системными администраторами. У нас есть список [[w:DNS|DNS]]-имен и [[w:IP-адрес|IP-адреса]]. Каждому DNS-имени соответствует только один IP-адрес. Как нам это соответствие записать в виде программы? Попробуем это сделать при помощи массива:
 
<code>maccuB = [['comp1.mydomen.ru','192.168.0.3'],
['comp2.mydomen.ru','192.168.0.1'],['comp3.mydomen.ru','192.168.0.2']]</code>
Все бы ничего, но чтобы найти IP-адрес по DNS имени, придётся перелопатить весь массив в поиске нужного DNS:
 
<code>dns_name = 'comp1.mydomen.ru'
maccuB.find_all{ |<u>key, value</u>| key == dns_name }<u>[0][-1]</u>
#-> "192.168.0.3"</code>
 
В данном примере было использовано два интересных приема:
* Если в двумерном массиве заранее известное количество столбцов (в нашем случае — два), то каждому из столбцов (в рамках любого итератора) можно дать свое имя (в нашем случае: <code>key</code> и <code>value</code>). Если бы мы такого имени не давали, то вышеописанное решение выглядело бы так:
 
<code>maccuB.find_all{ |<u>array</u>| <u>array[0]</u> == dns_name }<u>[0][-1]</u>
#-> "192.168.0.3"</code>
 
{{Внимание|Без именования столбцов, внутри итератора вы будете работать с массивом (в двумерном массиве каждый элемент — массив, а любой итератор "пробегает" массив поэлементно). Это высказывание действительно, когда "пробежка" осуществляется по двумерному массиву.}}
 
* метод <code>.find_all</code> возвращает двумерный массив примерно следующего вида: <code>[ ["comp1.mydomen.ru", "192.168.0.3"] ]</code>, чтобы получить строку <code>"192.168.0.3"</code> необходимо избавиться от двумерности. Делается это при помощи метода <code>[]</code>, который вызывается два раза (понижает размерность c двух до нуля). Метод <code>[0]</code> возвращает в результате — <code>["comp1.mydomen.ru", "192.168.0.3"]</code>, а метод <code>[-1]</code> — <code>"192.168.0.3"</code>. Как раз это нам и было нужно.
 
Теперь ту же самую задачу решим, используя хеш:
 
<code>xew = {'comp1.mydomen.ru' => '192.168.0.3',
'comp2.mydomen.ru' => '192.168.0.1', 'comp3.mydomen.ru' => '192.168.0.2'}
xew[ 'comp1.mydomen.ru' ] #-> "192.168.0.3"</code>
 
Нет ни одного итератора и следовательно, не сделано ни одной "пробежки" по массиву.
 
{{Рамка}}'''Третий случай применимости хеша''': когда требуется сопоставить один набор данных с другим, то целесообразнее использовать хеш.{{Акмар}}
 
Вполне естественно, что существуют и другие "случаи применимости хеша", но вероятность столкнуться с ними в реальной работе намного меньше. Вышеописанных трех "случаев" должно хватить надолго.
 
В заключении, как и было обещанно, приводится решение задачи с использованием метода <code>.update</code>:
 
<code>maccuB = [1,2,1,2,3,2,1,2,4,5]
maccuB.inject( {} ){ |result, i| result.update( { i=>1 } ){ |key,old,new| old+new } }
#-> {5=>1, 1=>3, 2=>4, 3=>1, 4=>1}</code>
Описание метода <code>.update</code> будет дано ниже. На данном этапе, <u>попытайтесь угадать принцип работы метода <code>.update</code></u>.
 
=== Что используется в качестве ключей? ===
 
В качестве ключей ассоциативного массива можно использовать любые типы. Например, другие ассоциативные массивы, строки, числа, символы или просто обьекты любых классов.
 
{{Внимание|Если состояние объектов-ключей изменилось, то хешу необходимо вызвать метод <code>.rehash</code>.}}
 
<code>maccuB_1 = [ "а", "б" ]
maccuB_2 = [ "в", "г" ]
xew = { maccuB_1 => 100, maccuB_2 => 300 }
xew[ maccuB_1 ] #-> 100
maccuB_1[0] = "я"
xew[ maccuB_1 ] #-> nil
xew<u>.rehash</u> #-> {["я", "б"]=>100, ["в", "г"]=>300}
xew[ maccuB_1 ] #-> 100</code>
 
В данном примере ключами хеша (<code>xew</code>) являются два массива (<code>maccuB_1</code> и <code>maccuB_2</code>). Одному из них (<code>maccuB_1</code>) мы изменили нулевой элемент (с "а" на "я"). После этого доступ к значению был потерян. После выполнения метода <code>.rehash</code> все встало на свои места.
 
{{info|''Как Руби отслеживает изменение ключа в ассоциативном массиве? Очень просто: с помощью метода <code>.hash</code>, который генерирует "контрольную сумму" обьекта в виде целого числа. Например: <code>[1,2,3].hash #-> 25</code>''}}
 
=== Способы создания ассоциативного массива ===
 
При создании ассоциативного массива важно ответить на несколько вопросов:
* Какие данные имеются?
* Какого типа эти данные?
* Что будет ключом, а что значением?
 
Ответы определят способ создания хеша.
 
==== Из одномерного массива ====
 
Положим, что ''у нас в наличии индексный массив, где ключ и значение записаны последовательно''. Тогда мы можем использовать связку методов <code>*</code> и <code>Hash[]</code>:
 
<code>maccuB = [1, 4, 5, 3, 2, 2]
Hash[ <u>*maccuB</u> ] #-> {5=>3, 1=>4, 2=>2}</code>
 
Элементы, стоящие на ''нечетной'' позиции (в данном случае: 1, 5 и 2) стали ключами, а элементы, стоящие на ''четной'' позиции (то есть: 4, 3 и 2), стали значениями.
 
==== Из двумерного массива ====
 
Если повезло меньше и нам достался двумерный массив с элементами вида <code>[["ключ_1", "значение_1"], ["ключ_2", "значение_2"], ["ключ_3", "значение_3"], ... ]</code>, то его надо сплющить (<code>.flatten</code>) и тем задача будет сведена к предыдущей:
<code>maccuB = [[1,4],[5,3],[2,2]]
Hash[ *maccuB<u>.flatten</u> ]</code> #-> {5=>3, 1=>4, 2=>2}
 
Каждый нулевой элемент подмассива станет ключом, а каждый первый — значением.
 
Но может случиться так, что двумерный массив будет состоять из двух подмассивов: подмассива ключей и подмассива значений:
<code>[["ключ_1","ключ_2","ключ_3", ... ], ["значение_1","значение_2","значение_3", ... ]]</code>
 
Вспоминаем методы работы с массивами. Там был метод <code>.transpose</code> (транспонирование массива), вызов которого сведет задачу к предыдущей.
 
<code>maccuB = [[1,5,2],[4,3,2]]
Hash[ *maccuB<u>.transpose.flatten</u> ]</code> #-> {5=>3, 1=>4, 2=>2}
 
==== Нет данных ====
 
Если нет данных, то лучше записать хеш как пару фигурных скобок:
 
<code>xew = <u>{}</u>
xew[1] = 4
xew[5] = 3
xew[2] = 2
xew #-> {5=>3, 1=>4, 2=>2}</code>
 
И уже по ходу дела разобраться, что к чему.
 
==== Известен только тип значений ====
 
Сведения о типе значений использовать следует так: создать хеш, в котором будет определен элемент по умолчанию. Элементом по умолчанию должен быть нулевой элемент соотвествующего типа, то есть для строки это будет пустая строка ( <code>""</code> ), для массива — пустой массив ( <code>[]</code> ), а для числа — нуль (<code>0</code> или <code>0.0</code>). Это делается, чтобы к пустому элементу можно было что-то добавить и при этом не получить ошибку.
<source lang=ruby>xew = Hash.new( "" )
xew[ "песенка про зайцев" ] += "В темносинем лесу"
xew[ "песенка про зайцев" ] += ", где трепещут осины"
xew #-> { "песенка про зайцев"=>"В темносинем лесу, где трепещут осины" }</source>
 
Или ещё пример:
<source lang=ruby>xew = Hash.new( 0 )
xew[ "зарплата" ] += 60
xew[ "зарплата" ] *= 21
xew #-> {"зарплата"=>1260}</source>
 
Но как известно из любого правила есть исключение: использовать нулевой элемент, когда значение будет записываться умножением, нежелательно. Потому как, даже не будучи npopokом <small style="color:#777">(Ых-хы-ыыы)</small>, можно предсказать результат. Он будет равен нулю.
 
==== Всё известно и дано ====
Если вам изначально известны все ключи и значения, то и записывайте их сразу в виде хеша:
<source lang=ruby>{ "март" => 400, "январь" => 350, "февраль" => 200 }</source>
{{Рамка}}Не изобретайте [[w:велосипед|велосипед]] и поступайте как можно проще.{{Акмар}}
 
=== Методы работы с ассоциативными массивами ===
 
Когда речь пойдет о методах, которые присутствуют в ассоциативных массивах, то частенько будет возникать чувство [[w:Дежавю|дежавю]]. Во всяком случае, учить заново итераторы вам не придется. Вполне естественно, что появятся новички, но их будет немного. Тем не менее, прилежный преподаватель первым делом представляет новичков группе. Поэтому и мы начнем с тех методов, которые будут необходимы нам при работе с ассоциативными массивами, но отсутствуют у индексных.
 
==== Получение массива значений и массива ключей ====
Для получения отдельно массива ключей или значений существуют методы <code>.keys</code> и <code>.values</code>.
 
<code>{1 => 4, 5 => 3, 2 => 2}<u>.keys</u> #-> [1, 2, 5]
{1 => 4, 5 => 3, 2 => 2}<u>.values</u> #-> [4, 3, 2]</code>
 
{{Внимание|Ассоциативные массивы в Ruby неупорядоченны: массивы могут иметь любой порядок элементов.}}
 
==== Замена ключей на значения ====
 
Чтобы поменять местами ключи и значения ассоциативного массива, следует применять метод <code>.invert</code>. Этот метод возвращает асоциативный массив с ключами, замененными значениями, и значениями, замененными ключами.
 
<code>xew = {<u>"первый ключ" => 4, "второй ключ" => 5</u>}
xew.invert #-> {<u>4 => "первый ключ", 5 => "второй ключ"</u>}</code>
 
{{Внимание|Поскольку ключи в ассоциативных массивах уникальны, то ключи с одинаковыми значениями будут отброшены.}}
 
<code>xew = {"первый ключ" => <u>10</u>, "второй ключ" => <u>10</u>}
xew.invert #-> {<u>10</u> => "второй ключ"}</code>
 
Небольшая хитрость: <code>xew.invert.invert</code> возвратит нам хеш с уникальными значениями.
 
==== Обновление пары ====
 
Что вы делаете, если хотите обновить какую-то программу или игру? Правильно, устанавливаете [[w:Компьютерный сленг|апдейт]]. А вот чтобы обновить значение в ассоциативном массиве используется метод <code>.update</code>. Пример использования этого метода в боевых условиях мы уже приводили в начале раздела. Если вы помните, то мы считали ''сколько раз повторяется каждое число''. Наверняка, вы немного подзабыли его решение (у программистов есть привычка не помнить [[w:Константа|константы]]). Позволю себе его вам напомнить:
 
<code>maccuB = [1,2,1,2,3,2,1,2,4,5]
maccuB.inject( {} ){ |result, i| result<u>.update( { i=>1 } ){ |key,old,new| old+new }</u> }
#-> {5=>1, 1=>3, 2=>4, 3=>1, 4=>1}</code>
 
Страшноватая запись. Поэтому будем разбирать ее по частям.
 
<code>result.update<u>( { i=>1 } )</u>{ |key,old,new| old+new }</code>
 
Сразу после названия метода (в нашем случае <code>.update</code>) идет передача параметра. Страшная запись <code>{ i => 1 }</code> — это не что иное, как еще один хеш. Ключ его хранится в переменной <code>i</code> (счетчик итератора <code>.inject</code>), а в качестве значения выбрана единица. Зачем? Расскажу чуть позже.
 
{{info|Не обязательно писать именно <code><nowiki>{ i=>1 }</nowiki></code>. Можно "сократить" фигурные скобки и записать <code><nowiki>i=>1</nowiki></code>}}
 
{{Рамка}}'''Счетчик итератора''' — это переменная в которую итератор записывает текущий элемент последовательности.{{Акмар}}
 
Здесь вроде бы все понятно. Запись стала менее страшной, но все равно вызывает дрожь. Будем это исправлять!
 
<code>result.update( { i=>1 } )<u>{ |key,old,new| old+new }</u></code>
 
Раньше мы не встречались с такой записью. Но ничего страшного в ней нет. Это что-то типа ''поля боя''. Нам выдали вооружение и необходимо провести некий маневр. В нашем случае, [[w:Арсенал|арсенал]] у нас внушительный: <code>key</code>, <code>old</code> и <code>new</code>. Бой начинается при некоторых условиях. Наш бой начнется, когда при добавлении очередной пары (переданной в предыдущей части страшной записи) обнаружится, что такой ключ уже есть в хеше. Нам предлагается описать наши действия именно в таком случае. Что же это за действия?
 
<code>result.update( { i=>1 } ){ |key,old,new| <u>old+new</u> }</code>
 
Всего лишь сложение <code>old</code> и <code>new</code>. Ничего не говорит? Тогда расскажу, что значат переменные <code>key</code>, <code>old</code> и <code>new</code>. В переменную <code>key</code> передается значение текущего ключа, в <code>old</code> — старое значение ключа (''old'' по английски значит "старый"), а в переменную <code>new</code> — добавляемое значение ключа (''new'' по английски значит "новый").
 
Теперь переведем запись <code>old+new</code> на русский: в случае обнаружения ключа в хеше, нам необходимо сложить старое значение с новым. Если помните, то новое значение равняется единице, то есть в случае когда ключ, хранимый в <code>i</code> уже есть в хеше <code>result</code>, то к старому значению просто добавляется единица. Вот и все... а вы боялись.
 
{{info|Рекомендуется перечитать данную главу еще раз, так как вы ее немного не поняли}}
 
Интересно, сколько читателей сможет прочитать эту строку и не зациклиться на предыдущей?
 
==== Размер ассоциативного массива ====
 
Ну вот, с новичками мы познакомились, теперь можно переходить к старым знакомым. Помните, как мы находили размер массива? Вот и с хешами точно также:
 
<code>xew = {5=>1, 1=>3, 2=>4, 3=>1, 4=>1}
xew<u>.size</u> #-> <u>5</u></code>
 
{{info|Стоит уточнить, что если в индексных массивах под размером понимается количество элементов, то в ассоциативноя массиве это количество пар вида <code><nowiki>ключ => значение</nowiki></code>. В остальном же это наш старый добрый <code>.size</code>}}
 
==== Удаление пары по ключу ====
 
О том, как добавлять элементы в массив мы знаем, а вот про удаление — нет! Необходимо это исправить. Чем мы сейчас и займемся.
 
<code>xew = {<u>5=>1</u>, 1=>3, 2=>4, 3=>1, 4=>1}
xew<u>.delete( 5 )</u> #-> <u>1</u>
xew #-> {1=>3, 2=>4, 3=>1, 4=>1}
xew.delete( 5 ) #-> nil</code>
 
Как вы, наверно, уже догадались, удалением пары по ключу занимается метод <code>.delete</code>. Ему передается ключ от пары, которую следует удалить.
 
{{info|
* Метод <code>.delete</code> возвращает значение, которое соответствовало ключу в удаляемой паре
* Если в хеше отсутствует пара с передаваемым ключем, то метод <code>.delete</code> возвращает <code>nil</code>
* Напоминаем, что <code>nil</code> — это символ пустоты}}
 
==== Удаление произвольной пары ====
 
Многие программисты удивляются, когда узнают, что ассоциативные массивы имеют метод <code>.shift</code>. Связано это удивление с тем, что у индексных массивов он ''удаляет первый элемент, возвращая его во время удаления''. А вот как понять, какая пара является первой? И что такое ''первый в неупорядоченной последовательности пар''?
 
{{info|Ответ кроется в отсутствии метода-напарника <code>.pop</code>, так как если нельзя удалить последний элемент, то ''под <code>.shift</code> понимается удаление произвольной пары''. Вот такое вот нехитрое доказательство}}
 
Давайте посмотрим его в действии:
 
<code>xew = {5=>3,1=>6,3=>2}
xew.shift #-> <u>[5,3]</u>
xew #-> {1=>6, 3=>2}</code>
 
Обратите внимание, что метод <code>.shift</code> возвращает удаляемую пару в виде индексного массива <code>[ ключ, значение ]</code>.
 
{{Внимание|Не стоит обольщаться по поводу того, что метод <code>.shift</code> возвращает первую пару. Помните, что ассоциативные массивы — неупорядоченны}}
 
{{Рамка}}Однажды нерадивому студенту был задан вопрос: как упорядоченны ключи в ассоциативном массиве? На что он дал радостный ответ «по возрастанию» и получил заслуженного [[w:2 (число)|«гуся»]]. Не повторяйте его ошибку! Помните, что пары в ассоциативных массивах неупорядоченны.{{Акмар}}
 
==== Преобразовать в индексный массив ====
 
Чуть ранее уже говорилось, что в большинстве случаев индексные массивы удобней ассоциативных.
 
{{Рамка}}Некоторые программисты утверждают, что при больших объемах данных лучше использовать двумерный индексный массив. Получается примерно то же, что и хеш (лишь поиск элемента по ключу осуществить сложнее), но обычно программа работает быстрей.
 
Мнение авторов таково, что у программиста на Руби есть более благородные пути времяпровождения, чем заниматься такой вот псевдооптимизационной ерундой.{{Акмар}}
 
Чтобы преобразовать ассоциативный массив в индексный, надо использовать метод <code>to_a</code>. Его используют все, кто не может запомнить методов работы с хешами.
 
<source lang=ruby>xew = {"гаечный ключ" => 10, "разводной ключ" => 22}
xew.to_a #-> [ ["гаечный ключ", 10], [разводной ключ", 22 ] ]</source>
 
Способ преобразования таков. Сперва пара и преобразуется в массив:
<code>{<u>[</u>"гаечный ключ" => 10<u>]</u>, <u>[</u>"разводной ключ" => 22<u>]</u>}</code>
 
Затем «стрелку» заменяем на запятую
<code>{["гаечный ключ"<u>,</u> 10], ["разводной ключ"<u>,</u> 22]}</code>
 
и фигурные скобки выпрямляем, так что теперь их можно заправить в стэплер.
<code><u>[</u>["гаечный ключ", 10], ["разводной ключ", 22]<u>]</u></code>
 
==== Упорядочение хеша ====
 
Да, множество пар в хеше неупорядоченно. Но это можно исправить, разве что результат потом будет не хешем, а двумерным массивом.
 
<code>xew = {"гаечный ключ" => 4, "разводной ключ" => 10}
xew<u>.sort</u> #-> [ ["гаечный ключ", 4], ["разводной ключ", 10] ]</code>
 
{{info|Сначала хеш упорядочивается по ключам, а потом, в случаях равнозначных ключей, — по значениям.}}
 
В методе <code>.sort_by</code> передаются два значения:
 
<code>xew = {"гаечный ключ" => 4, "разводной ключ" => 10}
xew.sort_by{ |<u>key,value</u>| value } #-> [ ["гаечный ключ", 4], ["разводной ключ", 10] ]</code>
 
Здесь мы упорядочили хеш по значению.
 
==== Поиск максимальной/минимальной пары ====
 
Максимальная пара в хеше ищется точно также, как и максимальный элемент в массиве
 
<code>xew = {"гаечный ключ" => 10, "разводной ключ" => 22}
xew.max #-> <u>["разводной ключ", 22]</u>
xew.min #-> <u>["гаечный ключ", 10]</u></code>
 
но с небольшими особенностями:
* результат поиска &mdash; массив из двух элементов вида <code>[ключ, значение]</code>
* сначала поиск происходит по ключу, а в случае совпадения ключей &mdash; по значению
 
Несколько больше возможностей приобрели методы .max_by и min_by
 
{{Внимание|но, к сожалению, у меня не установлена версия 1.9 и нет способа проверить возможность развертки массива во время передачи в блок. Дальнейшая информация может быть неверной}}
 
<code>xew = {"гаечный ключ" => 10, "разводной ключ" => 22}
xew.max_by{ |<u>key,value</u>| value } #-> ["разводной ключ", 22]
xew.min_by{ |<u>array</u>| array[0] } #-> ["гаечный ключ", 10]</code>
 
Также, как и в методе <code>.sort_by</code> есть возможность по разному получать текущую пару: в виде массива или двух переменных.
 
=== Логические методы ===
 
Работа логических методов похожа на допрос с пристрастием. Помните, как в детективах во время теста на детекторе лжи, главный герой восклицал: "Отвечать только да или нет!" Если перевести это на язык Ruby, то это будет звучать примерно так: "Отвечать только true или false!"
 
В детективах набор вопросов стандартен:
* знали ли вы мистера Х?
* вы были на месте преступления?
* убивали ли мистера Х
* ...
На Ruby примерно тоже самое:
* ты пустой?
* есть ли такой элемент?
* ты массив?
* уверен, что не строка?
 
Но давайте рассмотрим их подробней.
 
==== Хеш пустой? ====
 
Зададим вопрос "Хеш пустой?", но используя известный нам [[w:Лексикон|лексикон]]. Для начала спросим "Пустой хеш тебе не [[w:Брат|брат]]-[[w:Близнецы|близнец]]?"
<source lang=ruby>nycTou_xew = {}
noJIHbIu_xew = { "гаечный" => 20, "замочный" => "английский", "разводной" => 34 }
 
nycTou_xew == {} #-> true
noJIHbIu_xew == {} #-> false</source>
 
Можно спросить по другому &mdash; "Размер у тебя не нулевой?"
<source lang=ruby>nycTou_xew = {}
noJIHbIu_xew = { "гаечный" => 20, "замочный" => "английский", "разводной" => 34 }
 
nycTou_xew.size.zero? #-> true
noJIHbIu_xew.size.zero? #-> false</source>
Но давайте будем задавать правильные вопросы
<source lang=ruby>nycTou_xew = {}
noJIHbIu_xew = { "гаечный" => 20, "замочный" => "английский", "разводной" => 34 }
 
nycTou_xew.empty? #-> true
noJIHbIu_xew.empty? #-> false</source>
 
а то еще примут нас за приезжих...
 
{{info|Обратите внимание, что метод <code>.empty?</code> полностью повторяет такой же метод у индексных массивов}}
 
==== Есть такой ключ? ====
 
Если вам нужно узнать у хеша ответ на вопрос "Есть у тебя такой ключ?", но вы не знаете как это правильно спросить, то скорее всего вы зададите вопрос в два этапа: "какие ключи у тебя есть?" и "есть среди них такой ключ?"
 
<code>kapmaH = { "гаечный" => 20, "замочный" => "английский", "разводной" => 34 }
kapmaH<u>.keys.include?( "гаечный" )</u> #-> true</code>
 
В данном примере у нас в <code>kapmaH</code>`e нашелся "гаечный" ключ.
 
Но лучше задавать вопрос напрямую, это покажет ваше прекрасное знание языка.
 
<code>kapmaH = { "гаечный" => 20, "замочный" => "английский", "разводной" => 34 }
kapmaH<u>.key?( "гаечный" )</u> #-> true</code>
 
или в стиле индексных массивов
 
<code>kapmaH = { "гаечный" => 20, "замочный" => "английский", "разводной" => 34 }
kapmaH<u>.include?( "гаечный" )</u> #-> true</code>
 
Это несколько сократит первоначальное предложение, но тогда можно перепутать хеш с массивом.
 
{{info|Этот же вопрос можно задать методами: <code>.member?</code> и <code>.has_key?</code>}}
 
==== Есть такое значение? ====
 
Давайте подумаем, как задать вопрос "Есть такое значение?" хешу. Скорее всего, мы опять зададим вопрос в два этапа: "какие значения есть?" и "есть ли среди них нужное нам?"
 
<code>kapmaH = { "гаечный" => 20, "замочный" => "английский", "разводной" => 34 }
kapmaH<u>.values.include?( "гаечный" )</u> #-> <u>false</u> - ой, забыл сменить
kapmaH<u>.values.include?( "английский" )</u> #-> <u>true</u></code>
 
Но [[w:Коренные народы|аборигены]] говорят иначе и задают вопрос напрямую
 
<code>kapmaH = { "гаечный" => 20, "замочный" => "английский", "разводной" => 34 }
kapmaH<u>.value?( "английский" )</u> #-> true</code>
 
{{info|Задать вопрос "Есть такое значение?" можно не только при помощи метода <code>.value?</code>, но и при помощи более длинного <code>.has_value?</code>}}
 
=== Итераторы ===
 
У ассоциативных массивов есть следующие итераторы:
* <code>.find_all</code> &mdash; поиск всех элементов, которые удовлетворяют логическому условию
* <code>.map</code> &mdash; изменение всех элементов по некоторому алгоритму
* <code>.inject</code> &mdash; сложение, перемножение и агрегация элементов массива
 
Набор итераторов точно такой же, как и у индексных массивов &mdash; сказывается их родство. Вот только ведут себя они несколько иначе:
* результатом является двумерный массив (как после метода <code>.to_a</code>)
* в качестве счетчика (переменной в фотографии) передается массив вида <code>[ключ, значение]</code>
* можно развернуть массив вида <code>[ключ, значение]</code> в две переменные
* в итераторе <code>.inject</code> развернуть массив не получится
 
Рассматривать заново работу каждого итератора в отдельности скучно. Поэтому мы будем рассматривать работу всех итераторов сразу.
 
<code>xew = {"гаечный ключ" => 4, "разводной ключ" => 10}
xew.find_all{ |<u>array</u>| <u>array[1]</u> < 5 }
#-> [ ["гаечный ключ", 4] ]
xew.map { |<u>array</u>| "#{<u>array[0]</u>} на #{<u>array[1]</u>}" }
#-> ["гаечный ключ на 4", "разводной ключ на 10"]
xew.inject(0){ |result, <u>array</u>| result + <u>array[1]</u> }
#-> 14</code>
 
Обратите внимание на то, что в качестве счетчика передается массив из двух элементов. В наших примерах счетчик итератора мы назвали <code>array</code>. В своих программах вы вольны называть его как угодно.
 
{{info|Есть подозрение, что перед работой любого из итераторов вызывается метод <code>.to_a</code>. Уж больно работа итераторов в хешах напоминает работу с двумерным массивом}}
 
Теперь посмотрим, как можно развернуть <code>array</code> в две переменные. Делается это простой заменой <code>array</code> на <code>key, value</code>
 
<code>xew = {"гаечный ключ" => 4, "разводной ключ" => 10}
xew.find_all{ |<u>key, value</u>| <u>value</u> < 5 }
#-> [ ["гаечный ключ", 4] ]
xew.map { |<u>key, value</u>| "#{<u>key</u>} на #{<u>value</u>}" }
#-> ["гаечный ключ на 4", "разводной ключ на 10"]
xew.inject(0){ |result, <u>key, value</u>| result + <u>value</u> }
#-> Ошибка в методе '+' невозможно сложить nil и число типа Fixnum</code>
 
Обратите внимание, что развертка массива прошла успешно только в первых двух итераторах. В третьем возникла ошибка. Давайте выясним, откуда там взялся <code>nil</code>. Дело в том, что развернуть массив не удалось и теперь он стал называться не <code>array</code>, а <code>key</code>. Переменная <code>value</code> осталась "не у дел" и ей присвоилось значение <code>nil</code>. Чтобы это исправить, достаточно поставить круглые скобки:
 
<code>xew.inject(0){ |result, <u>(key, value)</u>| result + <u>value</u> }
#-> 14</code>
 
<!-- В рамках данного учебника, итераторы, не возвращающие измененного объекта, рассматриваться не будут.
Это связано с тем, что задачи решенные при помощи методов: .each, .times, each_index; больше похожи на адаптацию
к решениям на языка типа Си, Паскаль и т. д. Мы пойдем другим путем! =)
Не стоит вызывать внутри блока метод вывода на экран. Это тоже "попахивает" ошибкой или отладкой. -->
 
<!-- по поводу puts согласен, а each и each_pair - важные штуки, которые как-то жалко упускать. each это же столп Enumerable :-) -->
<!-- Вот когда будем примеси рассказывать, то там и будем про .each втирать. А сейчас только самые важные методы, которые позволят
решать задачи в функциональном стиле.-->
<!-- Верно, мы ведь повествуем по спирали. -->
<!-- Я тему раскрыл? Этот кусок можно удалять?
Ассоциативный массив, как и индексный массив, имеет метод <code>.map</code>, который передает блоку ключ и соответствующее ему значение. При этом в блок на самом деле передается массив с ключом и значением, но Ruby "разворачивает" их в две переменные при передаче блоку.
 
Итератор <code>.map</code>, в свою очередь, возвращает индексный массив с результатами блока — по элементу массива на каждый ключ:
 
<code>xew = {"гаечный ключ" => 4, "разводной ключ" => 10}
xew.map { | key, value |
"#{key} на #{value}"
} #-> ["гаечный ключ на 4", "разводной ключ на 10"]
xew<u>.map</u> #=> [ ["гаечный ключ", 4], ["разводной ключ", 10] ]</code>
-->
{{info|Итератор <code>.map</code>, вызванный без аргументов, аналогичен методу <code>.to_a</code>: просто раскладывает хеш в двумерный массив.}}
 
=== Хитрости ===
 
Одному программисту надоело писать <code>xew["key"]</code> и он захотел сделать так, чтобы можно было написать <code>xew.key</code>.
 
<code>class Hash
def method_missing( id )
self[ id.id2name ]
end
end
xew = {"hello" => "привет","bye" => "пока"}
xew.hello #-> "привет"
xew.bye #-> "пока"</code>
 
Естественно, что ключи в таком хеше могут содержать только латиницу, нижнее подчеркивание и цифры (везде, кроме первого символа). Иначе, говоря удовлетворять всем требованиям, которые мы предъявляем к именам методов и именам переменных.
 
=== Задачи ===
# Дан массив слов. Необходимо подсчитать сколько раз встречается каждое слово в массиве.
 
== Подробнее о строках ==
 
{{info|Строка &mdash; это упорядоченная последовательность символов, которая располагается между ограничительными символами}}
 
Строковый тип является самым популярным в любом языке программирования. Ведь без него невозможно написать любую программу (особенно учитывая, что любая программа &mdash; это строка). При выводе на экран или записи в файл, любой тип данных преобразовывается к строке (явно или неявно). Это значит, что в конечном итоге все сводится к строковому типу. Кстати, и ввод данных тоже осуществляется в виде строки (и только потом преобразовывается в другие типы).
 
{{Начало цитаты}}Студенты 4-го курса МЭТТ ГАИ поступили на подготовительные курсы в МГИУ. Там им начали преподавать основы программирования на Ruby. И одна из заданных им задач была: "Дано число, необходимо поменять порядок цифр на обратный". Задача сложная, но наши студенты об этом не знали и решили ее преобразованием к строке: <code>ucxogHoe.to_s.reverse</code>. Преподаватели были поражены и впредь запретили им использовать преобразования к строке в своих программах. И все потому, что это сильно упрощало решение и давало студентам огромное преимущество перед остальными слушателями курсов.{{Конец цитаты}}
 
Язык Ruby унаследовал работу со строками из языка Perl (признанного лидера по работе со строками). В частности такой мощный инструмент как «правила» (rules).
 
{{info|Раньше «правило» называлось «регулярным выражением», но в виду того, что на данный момент механизм «правил» вышел далеко за пределы регулярной грамматики, то решено его было переименовать (дабы не смущать умы образованной общественности)}}
 
Но наследование не подразумевает бездумного копирования. В частности, «правила», в рамках Ruby, получили объектно-ориентированную реализацию, что позволяет применять к ним различные методы. Помимо «правил», присутствует великое множество методов работы со строками. Причем некоторые из них являются нашими старыми знакомыми (<code>+</code>, <code>*</code>, <code>[]</code> и т.д.). Работают они несколько иначе, но некоторая параллель с массивами все же присутствует.
Следует упомянуть два очень интересных момента:
* Cтроки – это универсальный тип данных, т.к. в строку можно преобразовать любой другой тип данных. А также, строку можно преобразовать в любой другой тип данных (ведь изначально любой код программы – это строка).
* Cтроки очень удобно преобразовывать в массив и обратно (методы <code>.join</code> и <code>.split</code>). Поэтому работа со строками практически такая же удобная, как и с массивами.
{{Внимание|Если работа со строками обходится без преобразования в массив, то программа либо очень простая, либо бесполезная}}
 
=== Способы создания строки ===
Строка создается при помощи ограничительных символов. Для этих целей чаще всего используются " (лапка) и ' (типографский апостроф/минута/одиночная кавычка). Их смысл различен. Строка в минутах гарантирует, что в ней будет содержаться текст такой же, как в коде программы, без изменений. Строка в лапках будет проходить предварительное преобразование. Будут раскрыты конструкции «вставка» и «специальный символ».
 
{{Начало цитаты}}
Зажатые в лапках работать хотят,<br />
а строки в минутах лениво сопят.
{{Конец цитаты}}
 
Давайте будем называть строки в минутах «ленивыми», а строки в лапках — «работящими».
 
{{info|«Вставка» &mdash; это хитрая конструкция, которая вставляется между ограничительными символами (внутрь строки). Она состоит из комбинации решетки и двух ушек ( <code><u>#{</u> 'здесь был Вася' <u>}</u></code> ). Внутри нее можно писать не только <code>'Здесь был Вася'</code>, но и любой программный код. Результат программного кода будет преобразован к строке и вставлен вместо «вставки»}}
 
{{Начало цитаты}}
«Вставка» жизнью заправляет:<br />
Код программный выполняет,<br />
Тихо результат считает,<br />
Вместо «вставки» подставляет.
{{Конец цитаты}}
 
{{Внимание|«Вставка» работает только в момент создания строки. После создания придется придумывать другие способы подстановки данных в строки}}
 
Специальный символ начинается с знака <code>\</code> ([[w:Обратная косая черта|обратная косая черта]]). Самые популярные из них: <code>\n</code> (переход на новую строку), <code>\t</code> ([[w:Табуляция|табуляция]]), <code>\\</code> (обратная косая черта) и \" ([[w:Кавычки|двойная кавычка]]).
 
{{Внимание|Хотя специальный символ и пишется, как два знака, но на деле это всего один символ. Доказать это можно выполненением простенького кода: <code>"\n".size #-> 1</code>}}
 
==== Для чего нужны работящие и ленивые строки? ====
 
Скорее всего вы будете редко вспоминать про то, что существуют работящие и ленивые строки. Тем более, что это различие действительно только на момент создания строки. Рядовой программист пользуется либо работящими, либо ленивыми строками. Давайте посмотрим, как выглядит код программиста, который использует только ленивые строки:
 
<code>moe_4ucJIo = 18
mou_maccuB = [1,2,3,4]
puts 'Мое число = '<u> + </u>moe_4ucJIo<u>.to_s + </u>', а мой массив длины '<u> + </u>mou_maccuB.size<u>.to_s</u></code>
 
Обратите внимание, что перед сцеплением (умные дяди называют это конкатенацией) необходимо все данные преобразовывать к строке методом <code>.to_s</code>. «Вставка» позволяет этого избежать. Вот как будет выглядеть та же самая программа с использованием «вставки»:
 
<code>moe_4ucJIo = 18
mou_maccuB = [1,2,3,4]
puts "Мое число = <u>#{moe_4ucJIo}</u>, а мой массив длины <u>#{mou_maccuB.size}</u>"</code>
 
Программа стала не только меньше, но и лучше читаться. Исчезли многочисленные сцепления.
 
{{info|Если внутри «вставки» надо создать строку, то экранировать кавычки не стоит. Внутренности вставки не являются частью строки, а значит «живут» по своим законам.}}
 
<code>mou_maccuB = [1,2,3,4]
puts "Повторенье -- мать ученья. Мой массив = #{mou_maccuB.join(<s>\"</s>,<s>\"</s>)}"</code>
 
Программа вызовет ошибку, т.к. внутри «вставки» было использовано экранирование кавычек. Правильный пример будет выглядеть так:
 
<code>mou_maccuB = [1,2,3,4]
puts "Повторенье -- мать ученья. Мой массив = #{mou_maccuB.join(<u>"</u>,<u>"</u>)}"</code>
 
{{Внимание|Необходимости в экранировании строк внутри «вставки» — нет!}}
 
<!--
Но у этого способа есть небольшой недостаток: если внутри «вставки» надо создать строку, то необходимо использовать либо ленивые строки, либо использовать специальный символ <code>\"</code> (двойная кавычка).
 
<code>moe_4ucJIo = 18
mou_maccuB = [1,2,3,4]
puts "Мое число = #{moe_4ucJIo}, а мой массив = #{mou_maccuB.join(<u>','</u>)}"
puts "Повторенье -- мать ученья. Мой массив = #{mou_maccuB.join(<u>\",\"</u>)}"</code>
 
Замечу, что ленивые кавычки смотрятся симпотичней. Лично меня пугает запись <code><u>\",\"</u></code>. А вас?
-->
 
=== Методы работы со строками ===
 
Методы строк умеют:
* преобразовывать входные данные в красивый вид
* красиво оформить выходные данные
* дезертировать в массивы
 
Допустим, вы нашли максимальный элемент массива. И вам надо вывести результат на экран.
Вы можете поступить вот так:
<source lang=ruby>maccuB = [4, 4, 2, 5, 2, 7]
puts maccuB.max #-> 7</source>
 
Несмотря на правильность решения, вывод результата выглядит некрасиво.
Гораздо профессиональней будет написать вот такую программу:
 
<source lang=ruby>maccuB = [4, 4, 2, 5, 2, 7]
puts "Максимальный элемент maccuB'a = #{maccuB.max}"
#-> Максимальный элемент maccuB'a = 7</source>
 
Заметили, насколько привлекательней стал вывод результата, когда мы задействовали строки?
 
{{info|Заниматься оформлением выходных данных стоит только непосредственно перед выводом результата. В остальное время, использование оформительских элементов будет только мешать}}
 
Всем известно, что числа внутри компьютера хранятся в двоичной системе. Но вот парадокс: получить двоичное представление числа иногда очень сложно. В частности, для того, чтобы получить двоичную запись числа необходимо создать строку и записывать результат в нее. Это значит, что результатом преобразования в другую систему счисления (из десятичной) будет строка. Давайте посмотрим, как эта задача решается:
 
<code>ucxogHoe_4ucJIo = 123
puts "В двоичном виде -> %b" <u>%</u> ucxogHoe_4ucJIo
#-> В двоичном виде -> 1111011</code>
 
Мы задействовали метод <code>%</code> (аналог <code>sprintf</code> в Си), который осуществляет форматирование строки. Эту же задачу можно решить несколько иначе.
 
<code>ucxogHoe_4ucJIo = 123
puts "В двоичном виде -> <u>#{</u> ucxogHoe_4ucJIo<u>.to_s(2) }</u>"
#-> В двоичном виде -> 1111011</code>
 
Со вторым вариантом вы могли уже встречаться в разделе, посвященном числам (подраздел Хитрости).
 
{{info|Методы <code>sprintf</code> и <code>printf</code> в Ruby также присутствуют, но по непонятным причинам используются крайне редко. Чаще всего они заменяются на методы <code>%</code> и <code>puts</code>}}
 
==== Арифметика строк ====
 
Кто бы мог подумать, но строки можно складывать, умножать и если постараться, то и делить. Естественно, что это не арифметические операции чисел, а методы со своим особенным поведением. Рассмотрим сложение, которое в строках работает как сцепление (конкатенация):
 
<code>cynpyr = "Саша"
cynpyra = "Маша"
cynpyr<u> + </u>" плюс "<u> + </u>cynpyra<u> + </u>" = семья!"
#-> "Саша плюс Маша = семья!"</code>
 
Вот такое вот романтическое сцепление сердец!
 
Переходим к умножению. Умножают строки только на целое число. Строка, которую умножают просто копируется указанное число раз. Давайте напишем речь для кукушки, чтобы она не сбилась и не накукукала нам слишком мало.
 
<code>ocTaJIocb_JIeT = 100
"Ky-ky! "<u> * </u>ocTaJIocb_JIeT
#-> "Ky-ky! Ky-ky! Ky-ky! Ky-ky! Ky-ky! Ky-ky! Ky-ky!</code> … <code>Ky-ky! Ky-ky! "</code>
Теперь за свое будущее можно не беспокоиться! Хотя нам может попасться неграмотная кукушка…
 
С делением совсем другая история. Сейчас его в языке просто нет, но этот недостаток уже ощущают многие. Смысл деления состоит в том, чтобы преобразовать строку в массив, разбив ее по разделителю. Давайте преобразуем речь кукушки в массив и посмотрим, сколько же лет она нам насчитала:
 
<code>pe4b_kykywku = "Ky-ky! Ky-ky! Ky-ky! Ky-ky! Ky-ky! Ky-ky! Ky-ky! ... Ky-ky! Ky-ky! "
<u>(</u> pe4b_kykywku<u> / " " ).size</u> #-> 100</code>
 
{{info|
* В нашем примере метод <code>.size</code> считает число элементов массива, который получился в результате деления
* Скобки нужны для того, чтобы вызвать <code>.size</code> от результата деления, а не от <code>" "</code>}}
 
Деление в примере работает также как и метод <code>.split</code>, о котором речь пойдет чуть позже. А чтобы оно заработало у вас, необходимо добавить небольшой код в начало программы:
 
<source lang=ruby>class String
alias / :split
end</source>
 
Этот код как раз и говорит о том, что деление и <code>.split</code> — одно и тоже.
 
{{info|Деление стало нужно, когда метод <code>*</code> для массивов получил возможность работать, как <code>.join</code> (преобразовать массив в строку, расположив элементы через разделитель). В виду того, что <code>.join</code> и <code>.split</code> работают вместе точно также, как умножение и деление, то появилась идея заставить работать деление как <code>.split</code>}}
 
==== Преобразование в массив или путешествие туда и обратно ====
 
Случилось так, что итераторы в строках работают настолько неуклюже, что рассмотрение их в рамках учебника будет пропущено. Следовательно, для того, чтобы задействовать механизм итераторов, мы будем преобразовывать строки в массив. После того, как итераторы нам станут не нужны, то мы вернемся к строкам.
 
<code>
"Ky-ky"<u>.split</u>(<u>'-'</u>) #-> ["Ky","ky"]
"Ky-ky"<u>.split</u>(<u>'y'</u>) #-> ["K","-k"]</code>
{{Внимание|Обратите внимание, что разделитель из результата исчезает}}
 
За преобразование строки в массив отвечает метод <code>.split</code>. Ему передается разделитель, по которому будет происходить деление строки на элементы. В нашем случае это <code>'-'<code>. Теперь вернем все на место. Для этого мы будем использовать метод <code>.join</code> из арсенала массивов:
 
<code>["Ky","ky"].join(<u>'-'</u>) #-> "Ky-ky"
["Ky","ky"].join(<u>','</u>) #-> "Ky,ky"
["Ky","ky"]<u>.join</u> #-> <u>"Kyky"</u>
["Ky","ky"]<u>.to_s</u> #-> <u>"Kyky"</u></code>
{{info|
* Разделитель будет вставлен между элементами исходного массива
* Метод <code>.join</code> вызванный без разделителя дает такой же результат, как и метод <code>.to_s</code>}}
 
Чуть ранее мы упоминали, что можно использовать умножение вместо <code>.join</code>. Давайте посмотрим, как это выглядит:
 
<code>["Ky","ky"] <u>* '-'</u> #-> "Ky-ky"
["Ky","ky"] <u>* 3</u> #-> ["Ky","ky","Ky","ky","Ky","ky"]</code>
{{Внимание|Если умножать массив на целое число, то будет размножение массива (прямо как умножение в строках), а не преобразование в строку}}
 
==== Длина строки ====
 
Длина строки ищется точно также, как и длина массива или хеша, то есть методом <code>.size</code>:
 
<code>"Во дворе дрова, а в дровах трава!"<u>.size</u> #-> 33
"Три".size #-> 3</code>
{{Внимание|Следует помнить, что пробел является символом, хотя и не отображается на экране}}
{{Внимание|Ограничительные лапки или апострофы в количество символов не входят! Они предназаначены лишь для того, чтобы Ruby понял, где начинается и заканчивается строка}}
 
==== Получение подстрок ====
 
Получение подстрок работает точно также, как и получение подмассива. С тем лишь отличием, что нумерация идет не по элементам, а по символам. Это логично, особенно, если учесть, что для строки элементом является символ.
 
<code>cTpoka = "Во дворе дрова, а в дровах трава!"
cTpoka[27<u>..</u>-1] #-> "трава<u>!</u>"
cTpoka[27<u>...</u>-1] #-> "трава"
cTpoka[9...14] #-> "дрова"
cTpoka[9..13] #-> "дрова"</code>
{{info|Отрицательная нумерация тоже работает, то есть последний элемент имеет индекс -1, предпоследний -2 и так далее}}
{{Внимание|Помните, что три точки в диапазоне дают подстроку без крайнего правого элемента. Кто-то шлагбаумом балуется!}}
 
Чтобы получить один единственный символ, необходимо получить подстроку длиной 1.
 
<code>cTpoka = "Во дворе дрова, а в дровах трава!"
cTpoka[3..3] #-> "д"
cTpoka[<s>3</s>] #-> 228</code>
 
{{Внимание|Для получения подстроки или символа из строки необходимо всегда указывать диапазон. Даже если в диапазоне всего один элемент}}
 
Если мы указываем не диапазон, а число, то метод <code>[]</code> выдает нам не символ, а его целочисленный код.
 
<code>cTpoka = "Во дворе дрова, а в дровах трава!"
cTpoka[3] #-> 228
cTpoka[3]<u>.chr</u> #-> "д"</code>
 
{{info|Для преобразования целочисленного кода обратно в символ, используется метод <code>.chr</code>}}
 
====Строка-перевертыш====
 
Иногда хочется перевернуть строку задом наперед. Причины могут быть разные. Например, вы ищите палиндром (число, которое можно перевернуть без ущерба для его значения). Занимается этим благородным делом метод <code>.reverse</code>. Давайте сформируем сообщение для Иных по другую сторону Сумрака:
 
<code>"Ночной дозор! Всем выйти из Сумрака!"<u>.reverse</u>
#-> "!акармуС зи итйыв месВ !розод йончоН"</code>
 
Вот примерно такой бред надо прокричать, чтобы вас поняли. Кстати, попробуйте быстро проговорить полученный текст.
 
{{Внимание|Не стоит путать метод <code>.reverse</code> для массивов с методом <code>.reverse</code> для строк. В массивах меняется порядок элементов, а в строках — символов}}
 
====Меняю шило на мыло!====
 
Для того, чтобы заменить <code>"шило"</code> на <code>"мыло"</code> используется не газета "Из рук в руки", а методы <code>.sub</code> и <code>.gsub</code>:
 
<code>"<u>шило</u> в мешке не утаишь".sub("шило","мыло") #-> "<u>мыло</u> в мешке не утаишь"</code>
 
Естественно, что менять можно не только шило и мыло, но и другие данные. Например, возраст. Девушка Ирина утверждает, что ей 18, но я-то знаю, что ей 26. Давайте восстановим истину (в ущерб своему здоровью):
 
<code>"Ирине <u>18</u> лет".sub("18","26") #-> "Ирине <u>26</u> лет"</code>
 
Заметили, что мы используем только метод <code>.sub</code>? Давайте теперь рассмотрим работу метода <code>.gsub</code> и его отличие от <code>.sub</code>. На этот раз мы будем исправлять текст, авторы которого забыли про правило "ЖИ, ШИ — пиши через И"
 
<code>cTpoka = "жыло-было шыбко шыпящее жывотное"
cTpoka.sub("жы","жи") #-> "<u>жи</u>ло-было шыбко шыпящее <u>жы</u>вотное"
cTpoka.gsub("жы","жи") #-> "<u>жи</u>ло-было шыбко шыпяшее <u>жи</u>вотное"
cTpoka.gsub("жы","жи").gsub("шы","ши")
#-> "<u>жи</u>ло-было <u>ши</u>бко <u>ши</u>пящее <u>жи</u>вотное"</code>
 
{{Внимание|Метод <code>.sub</code> производит только одну замену, а <code>.gsub</code> — все возможные}}
 
{{Начало цитаты}}
Одну ошибку лишь исправил<br />
Ленивый метод <code>.sub</code><br />
Все остальные устранил<br />
Трудолюбивый <code>.gsub</code>
{{Конец цитаты}}
 
{{info|
* Название метода <code>.sub</code> произошло от английского <u>sub</u>string (подстрока)
* Название метода <code>.gsub</code> корнями уходит в язык Perl. В нем, для осуществления всевозможных замен, использовалась конструкция <code>s///<u>g</u></code>, где модификатор <code>/g</code> означал все возможные замены (от английского <u>g</u>lobal — всеобщий, глобальный)}}
 
====Сканируем текст на оШЫбки====
 
Давайте, найдем и посчитаем о<b>ШЫ</b>бки. Искать мы будем методом <code>.scan</code>
 
<code>cTpoka = "<u>жы</u>ло-было <u>шы</u>бко <u>шы</u>пящее <u>жы</u>вотное"
cTpoka.scan("шы") #-> ["шы","шы"]
cTpoka.scan("шы").size #-> 2
cTpoka.scan("жы").size #-> 2
cTpoka.scan("жы").size + cTpoka.scan("шы").size #-> 4</code>
 
{{info|Метод <code>.scan</code> находит все указанные подстроки и возвращает их в виде массива строк. В данном примере, метод <code>.size</code> считает количество элементов массива, возвращаемого <code>.scan</code>}}
 
Ужас, в одном предложении целых четыре ошибки. Будем отчислять!
 
{{Начало цитаты}}
Нашел о<b>ШЫ</b>бку метод <code>.scan</code>,<br />
В массив ее запомнил.<br />
Учителям он свыше дан!<br />
Зачем его я вспомнил?!
{{Конец цитаты}}
 
====Правила работы со строками====
 
Правила — это образцы к которым можно примерять строки. Правила обладают своим собственным языком, который позволяет описывать одну, две, сотню и вообще любое количество строк. Это своеобразная упаковка для множества строк в одну компактную запись.
 
Правила в Ruby ограничиваются символами <code>/</code> (наклонная черта). Примеры правил:
 
<source lang=ruby>/(жы|шы)/
/\w+@[\w\.]+\w+/i</source>
 
Страшно? А мне нет. На самом деле работа с правилами очень проста. Главное привыкнуть и попрактиковаться.
 
Правила состоят из:
* '''Символьных классов'''. Перечисление символов, которые может содержать строка
* '''Квантификаторов'''. Количество символов
* '''Альтернатив'''. Пречисление всевозможных вариантов
* '''Группировок'''. Возможность выделить несколько групп, которые могут обрабатываться отдельно
* '''Модификаторов'''. Изменение поведения правила. Например, игнорирование регистра символов
 
{{info|Правила в рамках учебника будут описаны очень сжато. Многие тонкости освещены не будут, поэтому для освоения "фигур высшего пилотажа" необходимо прочитать специализированную литературу. Например, [http://www.books.ru/shop/books/82357 книгу "Регулярные выражения"]}}
 
=====Символьный класс=====
 
Символьный класс — просто конечный набор символов. Он ограничивается квадратными скобками и содержит перечисление символов, которые можно вместо него подставить. Заменяется он всего на один символ, входящий в этот класс. Примеры символьных классов:
 
<code>/<u>[абвгде]</u>/ #-> простое перечисление символов
/<u>[а-яА-Я]</u>/ #-> все русские буквы
/<u>[0-9a-z]</u>/ #-> цифры и строчная латиница
/<u>[^0-9]</u>/ #-> все символы, кроме цифр</code>
 
{{info|
* Можно использовать - (дефис) для указания диапазонов символов
* Если первый символ класса (идущий сразу после открывающейся квардратной скобки) ^ (крышка), то это означает символ, который отсутствует в данном классе
* Некоторые популярные классы имеют короткую запись}}
 
'''Краткие записи популярных символьных классов'''
{| style="margin:0.5em 0 0.5em 1em;" class="standard" border=1
|-
! Короткая<br />запись || Полная<br />запись || Описание
|-
| <code>\s</code> || <code>[\f\t\n\r ]</code> || пробельный символ
|-
| <code>\S</code> || <code>[^\f\t\n\r ]</code> || любой символ, кроме пробельного
|-
| <code>\d</code> || <code>[0-9]</code> || цифра
|-
| <code>\D</code> || <code>[^0-9]</code> || любой символ, кроме цифры
|-
| <code>\w</code> || <code>[a-zA-Z0-9]</code> || латиница или цифра
|-
| <code>\W</code> || <code>[^a-zA-Z0-9]</code> || любой символ, кроме латиницы или цифры
|-
| <code>.</code> || <code>[^\n\r]</code> || любой символ, кроме перевода строки
|}
 
{{info|Взгляните на примеры правил! Правда, они стали понятней? По крайней мере второе...}}
 
=====Квантификатор=====
 
Квантификатор показывает сколько раз может повторяться предыдущий символ (группа, альтернатива или еще какая нечисть). Квантификатор ограничивается парой ушек (фигурными скобками). Примеры квантификаторов:
 
<code>/\w<u>{3}</u>/ #-> три латинских буквы или цифры
/\d<u>{1,3}</u>/ #-> одна, две или три цифры
/[а-яА-Я]<u>{3,}</u> #-> русское слово длинной три символа и больше</code>
{{info|
* Квантификатор с одним параметром называется '''точным''' и указывает точное количество повторений
* Квантификатор с двумя агрументами называется '''конечным''' и указывает конечный диапазон в котором варьируется количество повторений
* Квантификатор без второго параметра (но с запятой) называется '''бесконечным''' и ограничивает количество повторений лишь снизу
* Некоторые популярные квантификаторы имеют короткую запись}}
 
'''Краткие записи популярных квантификаторов'''
{| style="margin:0.5em 0 0.5em 1em;" class="standard" border=1
|-
! Короткая<br />запись || Полная<br />запись || Описание
|-
| <code>*</code> || <code>{0,}</code> || любое количество
|-
| <code>+</code> || <code>{1,}</code> || один и более
|-
| <code>?</code> || <code>{0,1}</code> || есть или нет
|}
 
{{info|Снова посмотрите на примеры правил. Теперь вам они понятны? Если нет, то перечитайте две предыдущие главы — в них основа правил}}
 
=====Альтернатива=====
 
Альтернатива нужна, когда необходимо объединить несколько правил в одно. При этом совпадение засчитывается, когда есть совпадение хотя бы с одним правилом. Желательно альтернативу заключать внутрь группировки (круглые скобки). Правила, входящие в альтернативу, разделяются | (палкой, которая и является альтернативой). Примеры альтернатив:
 
<code>/<u>(жы|шы)</u>/ #-> или "жы", или "шы"
/<u>(\w+|[а-яА-Я]+)</u>/ #-> или слово на латинице, или русское</code>
 
{{info|Вместо альтернативы можно задействовать логические итераторы <code>.any?</code> и <code>.all?</code> внутри <code>.inject</code>. Получается более гибкая конструкция}}
 
{{Внимание|В данном примере продемонстрирована альтернатива с группировкой. В принципе альтернатива может существовать и без нее, но так возникает меньше ошибок у начинающих}}
 
=====Группировка=====
 
Группировка используется, когда необходимо обрабатывать результат частями. Например, при обработке ссылок в [[w:HTML|HTML]]-документе удобно отдельно обрабатывать текст ссылки и [[w:URL|URL]]. Группировка также как и альтернатива, заключается в круглые скобки. Более того, альтернатива обрабатывается как группировка. Доступ к результату совпадения каждой группировки осуществляется посредством специальных переменных <code>$1, $2, ..., $9</code>. Подробнее группировки будут рассмотрены в подразделе "Правильная замена". Пример использования группировки:
 
<source lang=ruby>"2+7*3".gsub(/(\d+)\*(\d+)/){ $1.to_i * $2.to_i } #-> "2+21"</source>
 
Почти калькулятор!
 
{{info|Существует много видов группировок. Например, <code>(?:…)</code> — группировка без сохранения результата в «долларовую переменную» или <code>(?!…)</code> — негативная группировка. В любом случае они ограничиваются парой круглых скобок}}
 
=====Фиксирующая директива=====
 
Фиксирующие директивы — это символы, которые привязывают правило к некоторому признаку. Например, к концу или началу строки.
 
<code>/<u>^</u>\d+/ #-> строка начинается с числа
/\w+<u>$</u>/ #-> последнее слово на латинице или число
/<u>^$</u>/ #-> пустая строка</code>
 
Насколько видно из примеров,
* <code><u>^</u></code> — привязка к началу строки
* <code><u>$</u></code> — привязка к концу строки
 
{{Внимание|Фиксирующих директив гораздо больше двух. Об остальных читайте в специализированной литературе}}
 
=====Модификатор=====
 
Модификатор предназначен для изменения поведения правила. Он размещается сразу же после правила (после последней наклонной черты). Пример использования модификатора:
 
<code>/(hello|world)/<u>i</u> #-> или "hello", или "world". Причем независимо от регистра
/\s+/<u>mix</u> #-> несколько подряд идущих пробельных символов</code>
Бывают следующие модификаторы:
* <b>m</b>ultiline &mdash; перенос строки считается простым символом
* <b>i</b>gnorcase &mdash; поиск без учета регистра
* e<b>x</b>tended &mdash; игнорировать пробельные символы
 
{{Внимание|Игнорирование регистра работает только для латиницы}}
 
{{info|
* Можно применять любое количество модификаторов и в любом порядке
* Обратите внимание, что модификаторы образуют слово <b>mix</b>}}
 
{{info|Теперь можно не бояться страшных правил}}
 
==== Правильное разбиение ====
 
Разбиение называется «правильным» тогда, когда в качестве аргумента метода <code>.split</code> используется правило. Например, можно разбить текст по знакам препинания. Для этого необходимо выполнить следующий код.
 
<code>"Раз, два, три!".split(<u>/[,\.?! ]+/</u>) #-> ["Раз","два","три"]</code>
Обратите внимание, что в результирующем массиве знаки припинания отсутствуют.
 
==== Правильная замена ====
 
С правильной заменой не все так просто. Дело в том, что методы <code>.sub</code> и <code>.gsub</code> совместно с правилами становятся итераторами, которые последовательно обрабатывают каждое совпадение с правилом. Чтобы это увидеть в действии, давайте решим задачу исправления ошибок:
 
<code>"<u>Жы</u>ло-было <u>шы</u>бко <u>шы</u>пящее <u>жы</u>вотное".gsub(/(ж|ш)ы/){ $1 + 'и' }
#-> "<u>Жы</u>ло-было <u>ши</u>бко <u>ши</u>пящее <u>жи</u>вотное"</code>
 
Опаньки, а первое слово не исправилось! Видимо дело в том, что слово "Жыло" начинается с прописной буквы. Сейчас исправим:
 
<code>"<u>Жы</u>ло-было шыбко шыпящее жывотное".gsub(/(<u>Ж|Ш|</u>ж|ш)ы/){ $1 + 'и' }
#-> "<u>Жи</u>ло-было шибко шипящее животное"</code>
 
Вот, теперь гораздо лучше. Как мы этого добились? Давайте разберемся. Начнем с регулярного выражения:
 
<code>/(Ж|Ш|ж|ш)ы/</code>
 
Оно состоит из двух частей:
* альтернативы с группировкой — (Ж|Ш|ж|ш)
* символа — ы
 
В альтернативе мы указали буквы с которых начинается неправильный слог. Символ просто добавляется к букве из альтернативы.
 
Зачем была использована группировка? Для пояснения причины, рассмотрим код в ушках:
 
<code>{ $1 + 'и' }</code>
Вот для того, чтобы можно было использовать переменную <code>$1</code> (результат первой группировки) мы и задействовали группировку. В данном случае, в <code>$1</code> сохраняется первая буква слога, которая в результате исправления оШЫбки не меняется.
 
{{info|
* Для того, чтобы получить доступ к результату первой группировки, надо обратиться к переменной <code>$1</code> (один доллар), ко второй <code>$2</code> (два доллара) и так далее до переменной <code>$9</code> (девять долларов)
* Переменные <code>$1-$9</code> заимствованы из языка Perl}}
 
Можно ли было решить эту же задачу иначе? Конечно можно!
 
<code>"Жыло-было шыбко шыпящее жывотное".gsub(/<u>([ЖШжш])</u>ы/){ $1 + 'и' }
#-> "Жило-было шибко шипящее животное"</code>
 
На этот раз мы просто задействовали символьный класс вместо альтернативы, который описывает первую букву слога с оШЫбкой.
 
Есть еще пару интересных моментов, которые вам необходимо знать. Во время предыдущего примера вас могли посетить следующие вопросы:
* А как получить весь текст, который совпал с правилом?
* Неужели необходимо делать всеобщую группировку?
 
Ответ на эти вопросы одназначный — нет! Достаточно придумать название переменной (которая будет содержать совпавший текст) и правильно описать внутри ушек:
 
<code>"Раз, два, три!".gsub(/[а-я]+/){ <u>|cJIoBo|</u> cJIoBo.reverse }
#-> "заР, авд, ирт!"</code>
 
Нашей переменной мы дали название <code>cJIoBo</code>, что вполне согласуется с нашим <code>geJIom</code>.
 
==== Правильный поиск ====
 
Вот здесь метод <code>.scan</code> может развернуться в полную силу. Хотите получить массив всех русских слов в тексте? Запросто:
 
<code>"Раз, два, три!"<u>.scan(/[А-Яа-я]+/)</u>
#-> ["Раз","два","три"]</code>
 
Хотите получить все знаки препинания? Нет ничего проще:
 
<code>"Раз, два, три!"<u>.scan(/[,\.;:! ]+/)</u>
#-> [<u>", "</u>,<u>", "</u>,<u>"!"</u>]</code>
 
{{Внимание|Если необходимо в метод .scan передавать правило с группировкой, то желательно использовать группировку без сохранения результата, т.е. <code>(?:…)</code>. Иначе результатом метода <code>.scan</code> будет совпадение с группировкой, а не с правилом}}
 
Например, ниже записана программа, которая занимается поиском адресов электронной почты.
 
<code>cTpoka = "495-506-13 56 nata@rambler.ru(34) 1.5.1232 12.14.56 31.декабря.9999"
cTpoka.scan(/(?:[-a-z_\.])*@(?:[-a-z])*(?:\.[a-z]{2,4})+/) #-> ["nata@rambler.ru"]</code>
 
Выполните ее, посмотрите результат, а потом замените любую из группировок (?:…) на (…) и снова взгляните на рузультат.
 
Ну со <code>.scan</code> должно быть все понятно. А вот то, что метод <code>[]</code> начинает тоже правильно искать — пока нет.
 
<code>"Раз, два, три!"<u>[/[А-Яа-я]+/]</u> #-> <u>"Раз"</u></code>
 
{{Внимание|Если методу <code>[]</code> передать в качестве параметра правило, то он вернет либо совпадение с правилом, либо <code>nil</code>}}
 
Очень полезно использовать <code>[]</code> в ситуациях, когда надо узнать ответ на вопрос "Есть хотя бы одна подстрока, которая удовлетворяет правилу?" или получить первое (или единственное) совпадение с правилом.
 
{{info|Существует древнее поверье, что если использовать одно и тоже правило для <code>.scan</code> и <code>.split</code>, то получаются две части текста, из которых реально получить исходный.
<code>TekcT<u>.scan</u>( npaBuJIo ) + TekcT<u>.split</u>( npaBuJIo )<nowiki> = </nowiki>TekcT</code>
Это значит, что если метод <code>.split</code> использует правило, описывающие все знаки припинания, то результатом будет текст без знаков припинания. А вот если это же правило будет использовать метод <code>.scan</code>, то в результате мы получим все знаки препинания без текста}}
 
{{Внимание|Рекомендуется использовать метод <code>[]</code> вместо метода <code><nowiki>=~</nowiki></code> (заимствованного из Perl), так как <code>[]</code> более функционален}}
 
====Жадность====
 
Речь пойдет о жадности среди квантификаторов. Возьмем некоторый квантификатор <code>{n,m}</code> и посмотрим как он работает.
 
{{info|Нежадные квантификаторы иногда называют щедрыми}}
 
Сперва он начинает искать последовательность длины <var>m</var> (вот так щедрость) и если правило не срабатывает, он начинает уменьшать длинну последовательности вплоть до <var>n</var>. Так работают обычные (щедрые) кванторы.
 
Но иногда щедрые кванторы не могут справиться с задачей. Например, в файле на языке [[w:HTML|HTML]] мы осуществляем поиск ссылок (тег <code>&lt;a&gt;</code>). Правило с щедрым квантором найдет начало первой ссылки и конец последней. Весь остальной текст оно примет за текст ссылки. Понятно, что работать верно оно будет только в двух случаях: когда в тексте нет ссылок или когда ссылка только одна.
 
Для решения вышеописанной проблемы и был придуман так называемый жадный квантификатор. От щедрого он отличается обратным ходом обработки, то есть длину последовательности он не уменьшает от <var>m</var> к <var>n</var>, а наоборот, увеличивает от <var>n</var> до <var>m</var>. Научить жадности квантификатор можно знаком вопроса <code>?</code> после любого (щедрого) квантификатора.
 
<code>"Раз, два, три!".scan(/[А-Яа-я]+<u>?</u>/)
#-> ["Р","а","з","д","в","а","т","р","и"]
"Жуй жвачку, жывотное!".gsub(/([жЖшШ]?<u>?</u>)ы/){ $1 + 'и' }
#-> "Жуй жвачку, животное!"</code>
 
{{Внимание|С рождения квантификаторы щедры. Жадность — обретаемый признак.}}
 
=== Хитрости ===
====Перенос по словам====
Несколько лет назад (еще при жизни http://ruby-forum.ru) решали мы интересную задачу: как реализовать автоматический перенос на новую строку (wrap). Для тех, кто не застал те времена, уточню задание: дан текст, необходимо, вставить переносы таким образом, чтобы каждая из полученных строк была меньше n (для определенности <code>n=80</code>). Недавно я осознал, что не могу решить эту задачу тем способом, который был нами тогда найден. Я его просто не помнил... Решение нашлось быстро, достаточно было вспомнить, что на англ. языке эта задача именуется коротким и емким словом wrap.
 
<source lang=ruby>class String
def wrap(col = 80)
gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}})/,"\\1\\3\n")
end
end</source>
Немного о структуре кода... метод <code>.wrap</code> реализован для экземпляров класса <code>String</code>. Также стоит обратить внимание на то, что внутри правила (регулярного выражения) возможна "вставка" (как в "рабочих строках"). Используется сей метод следующим образом:
 
<source lang=ruby>p "wrapping text with regular expressions".wrap( 10 )
# -> "wrapping\ntext with\nregular\nexpression\ns\n"</source>
 
Теперь давайте разберемся с правилом. Чтобы не смущать неокрепшие умы, заменим вставку на 80. Правило станет короче и менее страшным.
 
<source lang=ruby>(.{1,80})( +|$\n?)|(.{1,80})</source>
Очевидно, что оно состоит из четырех частей:
* '''(.{1,80})''' — строка длиной от 1 до 80 символов (любых). Результат группировки записывается в <code>$1</code> (один доллар) или <code>"\\1"</code>
* '''( +|$\n?)''' — пробелы или конец строки. Результат группировки записывается в <code>$2</code> (два доллара) или <code>"\\2"</code>. Обратите внимание на запись <code>$\n?</code>, которая означает "конец строки (<code>$</code>), после которого может идти перенос (<code>\n</code>)". Обратите внимание, что <code>$2</code> мы не используем и поэтому можно использовать <code>(?: )</code> (группировку без сохранения результата)
* '''|''' — или
* '''(.{1,80})''' — строка длиной от 1 до 80 символов (любых). Результат группировки записывается в <code>$3</code> (три доллара) или <code>"\\3"</code>
 
В результате работы этого правила произойдет сопоставление с группировками 1 и 2 или с группировкой 3. В первом случае, будет обрабатываться строка, слова в которой по длине не превышают 80. Во втором случае, строка будет принудительно усечена до 80 символов. Другими словами, мы пытаемся сделать перенос по словам, но если у нас не получается, то мы будем делать перенос так, как у нас получится.
 
Представленное решение не идеально. В частности, слова с дефисом хорошо бы переносить именно по дефису (при этом оставляя его на первой строке). Про остальные изыски (перенос по слогам и т.д) вы можете догадаться самостоятельно. Эта задача может решаться бесконечно, предусматривая все большее количество различных вариантов. Плюс ко всему возможны комбинации с другими задачами (найти длину самого длинного слова и осуществить перенос по этой длине).
 
Источник (англоязычный): http://macromates.com/blog/archives/2006/06/28/wrapping-text-with-regular-expressions/
 
==== Методы преобразования к строке ====
Руби сам преобразует типы для некоторых простых операций. Например, при включении строки в другую он воспользуется имеющимся у обьекта методом <code>.to_s</code>:
 
<source lang=ruby>class Container
def to_s
"контейнер"
end
end
 
cont = Container.new
 
p "Это #{cont}" #-> "Это контейнер"</source>
 
Если нужно, чтобы ваши обьекты упорядочивались и сравнивались с обычными строками, следует применять примесь <code>Comparable</code> и единственный специальный метод <code>to_str</code>. Наличие этого метода у вашего объекта — знак для Ruby, что для сравнения следует применять не встроенный в <code>String</code> метод, а ваш.
 
<source lang=ruby>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</source>
 
=== Задачи ===
# Дана строка слов, разделёных пробелами. Вывести длиннейшее слово.
# Дана строка, содержащая кириллицу, латиницу и цифры. Вывести все слова, длина которых равна средней.
# Найти в строке первое целиком кириллическое слово.
# Дан текст (строка с переносами). Найти все слова, содержащие лишь три буквы "о".
#* Только для русских слов.
#* Для французских и русских слов.
#* Для любого регистра буквы "о".
# Найти в тексте время в формате "часы:минуты:секунды".
# Найти все слова без повторяющихся букв (например, "Лисп" или "Ruby", но не "Паскаль" или "Java").
#* Только для русскоязычных слов.
#* Не учитывайте цифры в словах.
# Найти в тексте слова, содержащие две прописные буквы и исправить.
#* Решите задачу для слов и в кириллице, и в латинице.
# Найти в тексте даты формата "день.месяц.год".
#* Найдите дату, где день ограничен числом 31, а месяц 12. Год ограничивайте четырёхзначными числами.
#* Распознавайте месяц в виде "31.марта.2001".
# Дан текст. Найдите все URL адреса и вычлените из них только корневой домен (например, из http://ru.wikibooks.org/wiki/Ruby сделайте http://ru.wikibooks.org ).
 
== Подробнее о методах ==
 
Все функции в Ruby являются методами, то есть свойственны обьектам. При программировании на это можно не обращать внимания, поскольку любая программа на Руби уже является определением класса. У методов могут быть обязательные или необязательные параметры. Методы разграничиваются фигурными скобками или ключевыми словами <code>def</code> и <code>end</code>.
 
=== Создание метода ===
Благодаря тому, что указание класса-носителя метода необязательно, на Ruby можно программировать в функциональном стиле, не заботясь о создании класса-"носителя" для каждой группы методов. Метод создается с помощью ключевых слов <code>def ... end</code>
 
<source lang=ruby>def sum(a, b)
return a + b
end
sum(10, 2) #-> 12</source>
 
Ruby по умолчанию возвратит из метода результат последнего выполненного выражения, поэтому в конце метода или в условных конструкциях слово return можно опускать. Поскольку методы могут быть переопределены в процессе выполнения программы, можно "на ходу" переписать метод так:
 
<source lang=ruby>def sum(a, b)
a + b
end
sum(10, 2) #=> 12</source>
 
<!-- Про case и if планируется рассказывать в самом конце, а программу is_odd можно переписать как !(number%2).zero?
 
Условное выражение и выражение <code>case</code> также возвращают свой результат, поэтому если метод проверяет условия, удобно написать его примерно так:
 
<source lang=ruby>
def is_odd(number)
case number % 2
when 1
true
else
false
end
end
is_odd(4) #-> false
is_odd(3) #-> true</source>
 
И return, и промежуточное присвоение можно опустить. Подобные свойства Ruby позволяют делать программы невероятно сжатыми без существенной потери читаемости.
-->
 
==== Указание значений по умолчанию ====
 
У методов могут быть необязательные аргументы; для этого им нужно присвоить значение, которое следует применять "по умолчанию"
 
<source lang=ruby>def sum(a, b = 5)
a + b
end
sum(10, 2) #-> 12
sum(10) #-> 15</source>
 
==== Методы с восклицательным и вопросительным знаком ====
 
В Ruby при создании методов можно применять простейшую пунктуацию. Два стандартных приема применения такой пунктуации - восклицательный и вопросительный знак в конце метода. Методы с вопросительным знаком традионно работают как ''предикаты'', то есть возвращают <code>true</code> или <code>false</code>. Пример методов-предикатов, — методы массива.
Например, в [[w:Java|Java]] подобные методы начинались бы со слова <code>is</code>: <code>isVolatile()</code>, <code>isEnabled</code>
 
Обычно программист, чтобы проверить, пуст ли массив, посмотрит его длину:
 
<source lang=ruby>arr = []
if arr.length == 0
puts "empty"
else
puts "not empty"
end</source>
 
У массива в Ruby есть метод-предикат <code>.empty?</code>, возвращающий <code>true</code> если массив пуст, и метод-предикат <code>.any?</code>, возвращающий <code>true</code> если массив содержит хотя бы один элемент.
 
<source lang=ruby>arr = []
if arr.empty?
puts "empty"
else
puts "not empty"
end</source>
 
Если вы реализуете программу, которой будут пользоваться другие, считается хорошим тоном реализовывать методы-предикаты.
 
Еще одна их прелесть — сочетание с модификаторами выражения:
 
<source lang=ruby>arr = [1,2 ,3]
p "Array has something" if arr.any?</source>
 
Методы с восклицательным знаком на конце меняют обьект, к которому привязаны.
 
<source lang=ruby>string = " Some string with spaces "
string.strip! #-> "Some string with spaces" - возвращает результат операции...
 
string #-> "Some string with spaces" #=> ...и меняет состояние объекта-адресата</source>
 
==== Методы присвоения ====
 
Другие особые варианты пунктуации - знак равенства и арифметические знаки.
 
Знак равенства в конце названия метода означает, что этот метод присваивает свойству объекта значение:
 
<source lang=ruby>class Bottle
def capacity
@capacity
end
def capacity=(new_cap)
@capacity = new_cap
end
end
 
bytilka = Bottle.new
bytilka.capacity = 10 #-> 10, автоматически преобразуется в вызов метода capacity=</source>
 
==== Операторы ====
 
Операторы (умножение, деление, возведение в степень и так далее - вплоть до сравнения!) - тоже методы. Например:
 
<source lang=ruby>class BeHuK
def+(another)
12 + another
end
end
 
meTeJIka = BeHuK.new
meTeJIka + 10 #-> 22</source>
 
Это применяется, например, во встроенном в Ruby объекте <code>Time</code>. При прибавлении к нему целого числа он возвращает новый объект <code>Time</code> с добавленным количеством секунд:
 
<source lang=ruby>t = Time.now #-> Sun Jun 11 20:29:51
t + 60 #-> Sun Jun 11 20:30:51 - на минуту позже</source>
 
То же самое характерно для имеющегося в стандартной библиотеке класса <code>Date</code>, но, в отличие от <code>Time</code>, он считает дни вместо секунд
 
<source lang=ruby>require 'date'
d = Date.today #-> Sun Jun 11
d + 1 #-> Mon Jun 12 - на день позже</source>
 
==== "Поглощение" аргументов метода ====
 
Можно "свернуть" аргументы с помощью звездочки - тогда метод получит массив в качестве аргумента
 
<source lang=ruby>def sum(*members)
members[0] + members[1]
end
sum(10, 2) #-> 12</source>
 
Поскольку теперь наш метод принимает неограниченное количество элементов, мы можем пользоваться ими как массивом и в теле функции
 
<source lang=ruby>def sum(*members)
initial = 0
members.collect{ | item | initial += item }
initial
end
sum(10, 2) #-> 12
sum(10, 2, 12, 34) #-> 58</source>
 
Можно разделить аргументы на обязательные и необязательные, просто пометив последний аргумент "звездочкой". Если методу будут переданы только обязательные аргументы, в переменной "со звездочкой" в теле метода будет пустой массив.
 
Звездочкой полезно пользоваться и когда нужно передать методу аргументы, но не хочется указывать их по отдельности. Следуя тому же примеру:
 
<source lang=ruby>array_to_sum = [10, 2, 12, 34]
sum(*array_to_sum) #-> 58</source>
 
=== Подробнее о блоках ===
<!-- Кто-то говорил, что это неверно. Блоки блоками, а closures — это замыкания. -->
Понятие [[w:Блок (программирование)|блока]] <!--([[w:Замыкание (программирование)|замыканий]])--> довольно просто: это часть программы, при создании захватывающая переменные окружающей среды. По сути блок есть анонимный метод.
 
Ruby позволяет создавать анонимные методы и передавать их функциям - такие анонимные методы называются **блоками**. Очень большое количество функций Ruby основано
на использовании блоков - например, итераторы (такие как <code>each</code> и <code>map</code>). Блок - это фактически "функция в функции" - программист определяет
операцию, которую необходимо выполнить, но непосредственно ее выполнение осуществляет метод, которому блок передается.
 
==== Зачем они нужны ====
 
Блоки позволяют избавиться от очень большого количества операций, которые для каждого программиста являются привычными - а именно...
 
* Поддержание индекса в цикле
* Забота об итераторах как отдельных обьектах
* Закрытие ресурса после его использования
* Забота о контексте, в котором выполняется операция
 
==== Как создать блок ====
 
Блок передается методу через конструкцию <code>do... end</code> или фигурные скобки. Общепринятым является использовать фигурные скобки, если вызов блока умещается на одну строку программы. Для демонстрации работы блока, мы будем использовать метод <code>.map</code>. Этот метод принимает блок и выполняет его строго заданное число раз.
 
При передаче блока методу блок следует за <u>скобками аргументов</u>.
 
<code>puts (1..3).map<u>()</u>{ "Bay!" } # выводит Bay! три раза</code>
 
Поскольку при отсутствии аргументов скобки необязательны, простейшая запись такова:
 
<source lang=ruby>puts (1..3).map{ "Bay!" } # выводит Bay! три раза</source>
 
Важно помнить, что блок использует методы и переменные, указанные при его создании, то есть блок ''захватывает контекст'', но переменные определенные в блоке остаются для него локальными!
 
<source lang=ruby>puts (1..3).map{ word = 'Bay!'; word } #-> выводит Bay! три раза
# поскольку блок знает про переменную word и она определена в нем
puts word # вызывает сообщение об ошибке - вне блока об этой переменной ничего не известно</source>
 
Как уже упоминалось, если блок многострочный целесообразней пользоваться формой с <code>do ... end</code>
 
<code>puts (1..3).map <u>do</u>
random_number = rand()
"Bay - случайный номер!\n"+
random_number.to_s
<u>end</u></code>
 
==== Блоки принимают аргументы ====
 
Другое замечательное свойство блоков — они, как и функции, могут принимать аргументы. В таком случае метод, которому передан блок, сам "решает", что этот блок получит в качестве аргумента. Например, уже продемонстрированный метод <code>.map</code> еще и передает блоку аргумент, который можно захватить следующим образом:
 
<source lang=ruby>puts (1..3).map do | index |
index
end</source>
 
В данном случае при каждом выполнении блока переменная <code>index</code> будет устанавливаться на положение итератора, начиная с 1!
Аргументы метода указываются после открывающей фигурной скобки или после слова <code>do</code> через запятую, и разграничиваются двумя вертикальми чертами. Чтобы не перепутать черту со строчной латинской L, принято "отбивать" аргументы блока от вертикальной черты пробелами.
 
<source lang=ruby>method { | argument | .. }</source>
 
==== Свои методы с блоками ====
 
Ключевое слово <code>yield</code> в методе открывает раздвижные двери, впускающие аргумент[ы] в блок.
 
<code>def pa3_u_gBa
<u>yield "u pa3"</u>
<u>yield "u gBa"</u>
end
pa3_u_gBa { | cJloBa | puts "!!! " + cJloBa } #-> !!! u pa3
#-> !!! u gBa</code>
 
При этом строка будет передаваться блоку в переменную <code>cJloBa</code> при каждом выполнении.
 
Если блок обязателен, следует пометить его как последний аргумент метода и в начале аргумента добавить [[w:амперсанд|амперсанд]]:
 
<code>def twice(<u>&block</u>)
yield "u pa3"
yield "u gBa"
end
twice #-> Ошибка LocalJumpError - отсутствует блок</code>
 
Последнее утверждение не совсем верно. Даже совсем не верно. Указания переменной блока недостаточно для контроля наличия входного блока. Дело в том, что в случае, если блок не вызывается, то и ошибки не будет:
 
<code>def func(<u>a</u>,<u>&block</u>)
return a if a
yield "u pa3"
yield "u gBa"
end
 
func true #-> true
func false #-> LocalJumpError: no block given</code>
 
Более того, вызов функции pa3_u_gBa без указания блока также приведет к ошибке.
Таким образом, гораздо лучше вместо введения обязательного параметра задавать блок по-умолчанию:
 
<source lang=ruby>def func(a,&block)
return a if a
block ||= lambda{ | cJloBa | puts "!!! " + cJloBa }
block.call("u pa3")
block.call("u gBa")
end
 
func true #-> true
func false #-> !!! u pa3
#-> !!! u gBa
 
func(false) { | cJloBa | puts "??? " + cJloBa } #-> ??? u pa3
#-> ??? u gBa</source>
 
Здесь lambda - пустая функция, а block.call - явный способ вызова блока кода на выполнение.
 
Блок можно также передать другому методу, просто указав его как последний аргумент с амперсандом:
 
<code>def writing_to(file, <u>&block</u>)
File.open(file, 'w', <u>&block</u>)
end</code>
 
==== Некоторые применения блоков ====
 
Блоки — одна из главных особенностей Ruby. Уметь ими пользоваться — ключ к очень коротким и очень понятным программам, делающим очень много.
 
Типичное применение блока — когда после выполнений некой операции нужно "вынести мусор": закрыть открытый ресурс или отсоединиться от сети. Предположим, что мы пишем метод для интернет-системы. При этом мы хотим выполнить несколько операций, но чтобы их выполнить, нужно подключить пользователя к Сети. После того, как операции завершились, надо его так же незаметно отключить.
 
<source lang=ruby>connected { download_email }</source>
 
В данном случае мы пишем только блок с <code>download_email</code>, все заботы по открытию (а главное — закрытию) соединения возьмет на себя метод <code>connected</code>:
 
<source lang=ruby>def connected
connect_to_internet
result = yield
disconnect
result
end</source>
 
В данном случае мы сохраняем то, что вернул блок в метод, закрываем соединение и возвращаем результат блока как свой собственный.
Чаще всего о методах, принимающих блоки, можно говорить как о деепричастном обороте — например "соединившись", "внутри_транзакции", "с файлом", "трижды".
 
Если воспользоваться встроенной проверкой исключений, то метод принимает такой вид:
 
<source lang=ruby>def connected
connect_to_internet
begin
result = yield
ensure
disconnect
end
result
end</source>
 
Тогда даже если метод вызовет ошибку, соединение все равно будет закрыто.
 
=== Методы, которых не было ===
 
Экспериментально замечено, что во время сессии у студентов в разы повышается способность к изобретениям различного рода. Иногда, удается направить эту энергию в мирное русло: некоторые студенты во время сдачи зачета начинают <u>придумывать свои методы<u>. Естественно, что "придуманные методы" они реализовать не могут, но с этим замечательно справляются их преподаватели. Некоторым методам даже дают имена студентов, которые приложили свое незнание к их созданию. Многие из таких методов включают в последующие версии языка.
 
==== Ширяевский .size ====
 
Студент МЭТТ Ширяев Денис, на одном из зачетов предложил использовать метод <code>.size</code> в качестве итератора. Он использовал его для подсчета количества элементов массива, удовлетворяющих условию. По сути, он предложил укоротить связку <code>.find_all{ ... }.size</code>. Вот как будет выглядеть программа подсчета количества четных элементов массива:
<source lang=ruby>maccuB = [1,2,3,4,5,6]
maccuB.size{ |i| (i%2).zero? } #-> 3</source>
 
Чтобы заставить работать данную программу, необходимо перед использованием итератора <code>.size</code> написать следующий код, который будет реализовывать эту функциональность:
<source lang=ruby>class Array
def size( &block )
block ? inject( 0 ){ |count,elem| (yield elem ) ? count + 1 : count } : length
end
end</source>
 
Метод реализован только для массивов, но возможно его добавление к хешам или строкам.
 
==== Случайное число из диапазона ====
 
Студенты часто возмущаются, почему чтобы получить случайное число от 3 до 6 нужно писать нечто невнятное вида:
<source lang=ruby>3 + rand( 4 )</source>
 
Откуда чего берется? Почему нельзя написать проще? Например вот так:
<source lang=ruby>(3..6).rand</source>
 
Действительно, почему? Давайте добавим такую функциональность к классу Range:
<source lang=ruby>class Range
def rand
first + Kernel.rand( last - first + ( exclude_end? ? 0 : 1 ) )
end
end</source>
 
Для проверки можно выполнить следующий код:
<source lang=ruby>p Array.new(100){ (3..6).rand }.uniq.sort #-> [3,4,5,6]</source>
 
Что и требовалось реализовать! Кстати, данная реализация имеет один изъян: для строковых диапазонов, метод <code>Range#rand</code> будет выдавать ошибку. Решается проблема достаточно просто. Надо реализовать <code>Array#rand</code> (получение случайного элемента массива), а внутри <code>Range#rand</code> вызывать связку <code>.to_a.rand</code>. Теперь тоже самое, но на Ruby:
<source lang=ruby>class Array
def rand
self[ Kernel.rand( size ) ]
end
end
 
class Range
def rand
to_a.rand
end
end</source>
 
Для проверки выполним следующий код:
<source lang=ruby>p Array.new(100){ ('a'..'c').rand }.uniq.sort #-> ["a","b","c"]</source>
 
Странно, но видимо все работает!
 
=== Способы расширения библиотеки методов ===
==== Как добавить метод к массиву/строке/венику? ====
 
Важно помнить, что в Ruby все типы являются обьектами, даже сами классы. Каждый класс до конца выполнения программы остается открытым, а это значит, что в любой тип можно добавить собственные методы (или изменить поведение существующих). Каждый класс можно определять постепенно, в нескольких частях программы:
 
<code>class BeHuK
def mecTu
end
end
BeHuK.instance_methods #-> [..., "mecTu", ...]
class BeHuK
def noMblTb_B_yHuta3e(yHuTa3)
end
end
BeHuK.instance_methods #-> [..., "mecTu", ..., "noMblTb_B_yHuta3e", ...]</code>
 
{{Внимание|метод <code>.instance_methods</code> возвращает массив, который содержит имена методов, которые можно вызвать.}}
 
Добавленные методы становятся доступны немедленно, в том числе для уже созданнных экземпляров типа. Стоит помнить, что методы в Ruby — на самом деле "сообщения", и у каждого метода есть "приемник", то есть объект, которому сообщение отправлено. Метод по умолчанию ищет другие методы в экземпляре класса, поскольку приемником для него является <code>self</code>.
 
Простейший пример — добавление метода классу <code>String</code>, выводящий только согласные буквы из строки:
 
<code>str = "Crazy fox jumps over a lazy dog"
class String
def consonants
cons = []
self.scan /[BCDFGHJKLMNPRSTbcdfghjklmnprst]/ do | m |
cons << m
end
cons.uniq.join
end
end
str.consonants #-> "Crfjmpsldg"</code>
 
{{Внимание|операция расширения класса (добавление нового метода к существующему) по сути не отличается от создания нового класса.}}
 
У обьектов в Ruby есть методы класса и методы экземпляра — в нашем примере <code>consonants</code> — это именно методы экземпляра. При создании нового класса или изменении существующего создать метод класса можно, начав его имя с имени класса или с <code>self</code> и точки:
 
<code>str = "Crazy fox jumps over a lazy dog"
class String
<u>def self.consonants_from(str)</u>
cons = []
str.scan /[BCDFGHJKLMNPRSTVWZbcdfghjklmnprstvwz]/ do | m |
cons << m
end
cons.uniq.join
end
end
String.consonants_from(str) #-> "Crfjmpsvlzdg"</code>
 
Одним из специфических свойств Ruby является то, что классы сами по себе — экземпляры класса <code>Class</code>, и с ними можно работать как с обычными объектами. Специальный синтаксис для доступа к методам класса в Ruby не нужен. Классы можно хранить в переменных, передавать методам и так далее.
 
{{Внимание|В контексте класса <code>self</code> - это сам класс.}}
 
Проиллюстрируем это простым примером. Как мы знаем, у класса <code>File</code> есть метод open. Создадим метод у класса <code>File</code>, дающий нам доступ к временному файлу, создаваемому в момент выполнения кода. Это такой же метод, но открывающий только файлы из директории <tt>/tmp</tt>:
 
<code>class File
<u>def self.temporary(&block)</u>
# определим директорию в которой в данный момент запущена программа
# методы dirname и expand_path в данном случае - File.dirname и File.expand_path
my_dir = self.dirname(self.expand_path(__FILE__))
base = basename(__FILE__, '.rb') # -> имя файла с программой без расширения .rb
stamp = "#{base}_#{Time.now.to_i}.tmp" # -> системное время в секундах и расширение tmp
# File.join соединит фрагменты пути обратным слешем в Windows и прямым слешем на UNIX
path = join(my_dir, stamp)
self.open(path, 'w', &block)
end
end
File.temporary { |f| f << "Some info" } # #<File:/Tests/(irb)_1151198720.tmp (closed)></code>
 
{{info|Для управления временными файлами в Руби существует класс Tempfile - помимо других достоинств он гарантирует, что созданные временные файлы по завершении программы
будут удалены. Так что даже в этом случае [[w:велосипед|велосипед]] изобретать не стоит!}}
 
Если к классу надо добавить много методов сразу, то при описании класса можно выйти на уровень его обьекта-класса. Это свойство в Ruby называется '''eigenclass''' ({{нем|eigen}} = "свой, особый"). Подозревая, что многие из читателей незнакомы с математическим понятием [[w:Собственные вектора, значения и пространства|собственного значения/вектора/пространства]], мы кратко и по-программистски назовём eigenclass '''эйгенклассом'''.
 
Добавим к классу <code>File</code> метод <code>myself</code>, который дает быстрый доступ к текущему файлу с исходным кодом:
 
<code>class Manager
<u>class << self</u>
def create
...
end
def manage
...
end
end
end</code>
 
Если нужно добавить метод только к конкретному экземпляру, нужно выйти на его эйгенкласс:
 
<code>str = "Crazy fox jumps over a lazy dog"
other_string = "Three black witches"
def <u>str.vowels</u>
vowels = []
scan /[AEIOUYaeiuoy]/ do | m |
vowels << m
end
vowels.uniq.join
end
end
str.vowels #-> "ayoue"
other_string.vowels #-> NoMethodError: у <u>этой строки</u> нету метода `vowels'...</code>
 
Возможность добавлять и изменять устройство уже существующих классов — одно из основных свойств Ruby, обеспечивающих великую гибкость языка. Часто бывает, что метод возвращает не тот результат, который нам нужен — тогда при его изменении все программы, обращающиеся к данному методу будут получать измененный результат.
 
==== Программист-разрушитель ====
 
Как ни странно, изредка программисту приходится взять на себя позицию разрушителя — удалить существующий метод или константу. Метод <code>undef</code> позволяет сделать это:
 
<code>class BeHuK
def mecTu
"МетёмЪ!"
end
end
class BeHuk_6epe3oBblu < BeHuK
def xJlecTaTb(cnuHa)
end
def noMo4uTb_B_Ta3uke(ta3uk)
end
<u>undef</u> mecTu
end
venik = BeHuK.new
beriozovy_venik = BeHuk_6epe3oBblu.new
venik.mecTu #-> "МетёмЪ!"
beriozovy_venik.mecTu #-> Ошибка NoMethodError - такого метода нет, хоть он и был унаследован</code>
 
Уничтожение класса несколько сложнее, но тоже возможно
 
<source lang=ruby>Object.send(:remove_const, :BeHuK)</source>
 
После этого BeHuK будет сущствовать только для объекта-экземпляра
 
<source lang=ruby>class BeHuK
end
 
meTeJIka = BeHuK.new
 
Object.send(:remove_const, :BeHuK)
 
BeHuK # => Ошибка NameError: неизвестная константа BeHuK
 
meTeJIka.class #=> BeHuK, все еще существует для экземпляра</source>
 
Это свойство Ruby крайне полезно, если нужно создать класс, наследующий от другого, но при этом имеющий другого родителя. Например:
 
<source lang=ruby># в чужой программе...
class Connection < Socket
# ... много-много методов connection...#
end
 
conn = Connection.new()
 
# в нашей программе...
Object.send(:remove_const, :Сonnection)
 
class Connection < EncryptedSocket
# ... такие-же методы как у connection, но работающие с шифрованным соединением...#
end
 
# в итоге чужая программа будет использовать созданный нами Connection!</source>
 
{{Внимание|Полная замена чужих классов довольно опасна, но бывают ситуации, когда эта методика спасает.}}
 
'''История из жизни'''
{{Начало цитаты}}При разработке своего Rails-приложения мной применялся класс <code>OrderedHash</code>, который работает как стандартный хеш, но при этом имеет упорядоченные ключи. Это позволяет, к примеру, удобно сгруппировать новости по датам, сохраняя порядок дат.''
 
''В какой-то момент моя программа перестала работать''. Почему? В Rails был, для внутренних нужд, добавлен другой класс <code>OrderedHash</code>, но при этом он не соответствовал моему (и даже не соответствовал обычному <code>Hash</code> — некоторых методов в нем просто не было! Благодаря <code>remove_const</code> мне удалось просто выгрузить их класс и заменить его своим. А тесты в комплекте чужой библиотеки позволили удостовериться, что я ничего не испортил и она с моим "внедренным" классом функционирует нормально.{{Конец цитаты}}
{{Подпись|[[Участник:Julik|Julik]] 01:52, 25 июня 2006}}
 
=== Как написать свой итератор? ===
=== Как написать свой класс? ===
=== Наследовать или перемешать? ===
=== Как сделать свою библиотеку методов? ===
 
== Матрицы и вектора ==
Давно хотел написать историю про то, как использовать [[w:Матрица (математика)|матрицы]] и [[w:Вектор (математика)|вектора]] в Ruby, но руки дошли только сейчас. Мое знакомство с реализацией матричного исчисления в Ruby началось еще в мою бытность студентом [http://www.msiu.ru МГИУ]. И надо сказать, что это знакомство было неудачным. Дело в том, что простейшая программа вычисления определителя матрицы
<source lang=ruby>require 'matrix'
p Matrix[[1,-2,3],[3,4,-5],[2,4,1]].det #-> -50</source>
давала неверный результат (правильный ответ = 62). Как выяснилось позднее, эта проблема связана со спецификой целочисленной арифметики в Ruby (<одна вторая в Руби - нуль>). Предположив это, я решил, что проблема легко решится, если я преобразую элементы матрицы к дробному типу (классу Float):
<source lang=ruby>require 'matrix'
p Matrix[[1.0,-2.0,3.0],[3.0,4.0,-5.0],[2.0,4.0,1.0]].det #-> 62.0</source>
И в некоторых случаях это меня действительно выручало. Но не всегда... стоило появиться делению на 3, как появлялись ненужные остатки и погрешности. И чем больше исходная матрица, тем больше вероятность появления таких остатков. В некоторых случаях это было несущественным, а в остальных приходилось прибегать к услугам специальных математических пакетов (например,[[w:Maxima|Maxima]]). Было жутко неудобно (я тогда писал курсовой, который решал все варианты контрольной работы по предмету <Математическое программирование>. Да простит меня преподаватель, который так и не понял секрета тотальной успеваемости группы) и обидно за такую "кривую реализацию".
 
На этом бы история и закончилась (как позже я узнал, на этом она заканчивалась для многих), но мне в руки попалась книга Programming Ruby 2ed с описанием возможностей стандартной библиотеки версии 1.8.2. Именно там (на стр. 671) я наткнулся на описание библиотеки mathn. Уникальность ее состоит в том, что она существенно расширяет возможности стандартных чисел, добавляя к ним [[w:Рациональное число|рациональные числа]] (класс <tt>Rational</tt>) и [[w:Комплексное число|комплексные числа]] (класс <tt>Complex</tt>).
{{info|Проще говоря, появляется возможность делить числа без погрешностей (класс <tt>Rational</tt>) и возможность извлекать квадратный корень из отрицательного числа (класс <tt>Complex</tt>)}}
Одновременно с этим она добавляет матричное и векторное исчисления (правда, почему-то в книге дополнительно подключали <tt>complex</tt> и <tt>matrix</tt>). И после этого матричное счисление начинает работать "из коробки" и еще как работать!!! Хотите обратую матрицу? Пожалуйста! Хотите определитель? Нет ничего проще! Обидно только одно... программу к тому времени я уже написал и эти возможности мне были не нужны.
 
Чуть позднее, один из моих студентов написал мне письмо с просьбой объяснить как <работать с матрицами в Руби>? При этом он задал всего три вопроса:
# Как создать новую матрицу?
# Как послать в матрицу 2-мерный массив?
# Как поменять элемент матрицы?
Для того, чтобы начать наше знакомство с матрицами, я отвечу сперва на них.
 
=== Как создать новую матрицу? ===
Самый простейший способ создать матрицу - использовать метод <батарейка> (метод [] выглядит как индикатор заряда батареи на сотовом телефоне):
<source lang=ruby>require 'mathn'
Matrix[[1, -2, 3], [3, 4, -5], [2, 4, 1]]</source>
 
Этот способ создания мы уже с вами видели раньше. Обратите внимание, что для использования матриц необходимо подключать библиотеку <tt>mathn</tt>.
 
=== Как послать в матрицу 2-мерный массив? ===
Немножко перефразируем вопрос: <как преобразовать двумерный массив в матрицу>? Пусть ответ будет неожиданным, но это делается при помощи того-же метода <батарейка>:
<source lang=ruby>require 'mathn'
maccuB = [[1, -2, 3], [3, 4, -5], [2, 4, 1]]
Matrix[ *maccuB ]</source>
 
Оператор * преобразует <tt>maccuB</tt> в список параметров, что позволяет существенно упросить процесс создания матрицы. Более того, это единственно удобный метод ее создания. Все остальные методы создания матрицы (например, метод <tt>.new</tt>) работают не столь изящно и интуитивно.
 
=== Как изменить элемент матрицы? ===
И вот тут всплывает <недоделанность> матриц. Метода для изменения элемента матрицы в них нет! Для того, чтобы изменить элемент матрицы, надо преобразовать матрицу в массив, изменить элемент массива и преобразовать массив в матрицу. Примерно вот так (меняем элемент с индексом <tt>0,0</tt>):
<source lang=ruby>require 'mathn'
maTpuLLa = Matrix[[1,-2,3],[3,4,-5],[2,4,1]]
maccuB = maTpuLLa.to_a
maccuB[0][0] = 0
p Matrix[ *maccuB ] #-> Matrix[[0, -2, 3], [3, 4, -5], [2, 4, 1]]</source>
 
==== Исправляем оплошность разработчиков ====
Для начала, рассматриваем поближе библиотеку <tt>matrix</tt> (исходник или описание в <tt>fxri</tt>) и выясняем, что для получения значения элемента используется метод <батарейка> с двумя аргументами. Вполне закономерно использовать метод <батарейка равно> (то есть []=) для изменения элемента. Сейчас мы его и реализуем:
<source lang=ruby>require 'mathn'
class Matrix
def []=( i, j, value )
@rows[i][j] = value
end
end
 
maTpuLLa = Matrix[[1,-2,3],[3,4,-5],[2,4,1]]
maTpuLLa[0,0] = 5
p maTpuLLa #-> Matrix[[5, -2, 3], [3, 4, -5], [2, 4, 1]]</source>
 
Ну вот как-то примерно так... Почему не могли этого сделать разработчики, я так и не понял. Скорее всего по идеологическим соображениям (<не дело, чтобы матрицы вели себя как простые массивы>).
=== Методы работы с матрицами ===
Из методов работы с матрицами наиболее важные это: вычисление определителя (<tt>.det</tt>), вычисление обратной матрицы (<tt>.inv</tt>), поиск минора матрицы (<tt>.minor</tt>), преобразование матрицы в массив (<tt>.to_a</tt>), умножение (<tt>*</tt>), сложение (<tt>+</tt>), вычитание (<tt>-</tt>) и деление (<tt>/</tt>) матриц. Без остальных методов можно обойтись. Поэтому изучите их самостоятельно.
=== Зачем нужны вектора? ===
Во-первых, для Ruby вектор - это объект класса <tt>Vector</tt>. Подключается он одновременно с матрицами (класс <tt>Matrix</tt>). Во-вторых, очень похож на массив, но с одним существенным отличием (cобственно это отличие и определяет полезность вектора): массивы и вектора по разному складываются и вычитаются. Давайте рассмотрим небольшой пример:
<source lang=ruby>require 'mathn'
maccuB = [1,2,3,4,5]
BekTop = Vector[ *maccuB ]
p BekTop + BekTop #-> Vector[2, 4, 6, 8, 10]
p maccuB + maccuB #-> [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]</source>
 
{{info|Обратите внимание, что <tt>BekTop</tt> создается точно также, как и матрица. По сути, вектор - это матрица, которая состоит лишь из одной строки. А матрица, в свою очередь - это массив векторов}}
 
Итак, в чем соль? Дело в том, что для сложения векторов необходимо сложить их соответствующие координаты. В случае же с массивами происходит конкатенация (сцепление) массивов. Вот именно в этой роли вектора не имеют себе равных. В остальных случаях, замечательно работает массив.
 
=== Хитрости ===
 
=== Задачи ===
 
==== Решение систем линейных уравнений методом Гаусса ====
Как я уже рассказывал раньше, первая моя программа на Ruby (декабрь 2002 года) -- реализация симплексного алгоритма для решения задач оптимизации. Весь фокус в том, что я эту программу нашел! Это 11 Кбайт и 363 строчки (140 + 223) программного кода. Кстати, если найду подходящее для нее место, то обязательно надо будет опубликовать.
 
Но суть не в этом. После того, как я ее нашел, у меня появилась идея-фикс: реализовать эту программу заново, но с высоты текущего уровня. Вот только для этого надо вспомнить хотя бы что-то по поводу симплексного алгоритма (чтение моего старого программного кода мне помогло мало). Первое, что пришло мне на ум -- там был метод Гаусса (правда, насколько я помню, не в чистом виде). Вот как раз его мы сейчас и реализуем.
<source lang=ruby>require 'mathn'
ypaBHeHue = [Vector[1,2,1,1],Vector[1,5,6,2],Vector[1,5,7,10]]</source>
 
Почему был выбран именно массив векторов, а не матрица или двумерный массив? Дело в том, что в методе Гаусса приходится выполнять такие векторные операции, как: вычитание векторов и деление вектора на [[w:Скаляр|скаляр]]. Поэтому смысла создавать матрицу (векторных операций не предусмотрено) или двумерный массив (придется реализовывать эти операции) -- нет! Кстати, вполне возможно создать массив векторов и из двумерного массива (что мы и сделаем в следующем примере). Итак, приступим к реализации прямого хода метода Гаусса:
<source lang=ruby>require 'mathn'
ypaBHeHue = [[1,2,1,1],[1,5,6,2],[1,5,7,10]].map{ |array| Vector[ *array ] }
# Прямой ход метода Гаусса
ypaBHeHue[0] /= ypaBHeHue[0][0]</source>
 
И вот тут нас ждет неприятный сюрприз: у векторов не реализован метод /. Смотрим в файл <tt>matrix.rb</tt> (который мирно лежит в директории со стандартными библиотеками). Действительно нет! Метод * (умножить) есть, а разделить -- нет. Ну и ладно, мы -- программисты не гордые... сами напишем!
<source lang=ruby>require 'mathn'
 
class Vector
def /( arg )
self * (1/arg)
end
end
 
ypaBHeHue = [[1,2,1,1],[1,5,6,2],[1,5,7,10]].map{ |array| Vector[ *array ] }
# Прямой ход метода Гаусса
ypaBHeHue[0] /= ypaBHeHue[0][0]</source>
 
Во! Теперь работает (только со скалярами, но нам больше и не надо). Заканчиваем реализовывать прямой проход метода Гаусса:
<source lang=ruby>require 'mathn'
 
class Vector
def /( arg )
self * (1/arg)
end
end
 
ypaBHeHue = [[1,2,1,1],[1,5,6,2],[1,5,7,10]].map{ |array| Vector[ *array ] }
# Прямой ход метода Гаусса
ypaBHeHue[0] /= ypaBHeHue[0][0]
ypaBHeHue[1] -= ypaBHeHue[0]*ypaBHeHue[1][0]
ypaBHeHue[2] -= ypaBHeHue[0]*ypaBHeHue[2][0]
 
ypaBHeHue[1] /= ypaBHeHue[1][1]
ypaBHeHue[2] -= ypaBHeHue[1]*ypaBHeHue[2][1]
 
ypaBHeHue[2] /= ypaBHeHue[2][2]
 
p ypaBHeHue #-> [Vector[1,2,1,1], Vector[0,1,5/3,1/3], Vector[0,0,1,8]]</source>
 
Судя по всему, программа работает. Кстати, обратите внимание, что результирующие вектора содержат рациональные дроби). Теперь добавим обратный ход метода Гаусса:
<source lang=ruby>require 'mathn'
 
class Vector
def /( arg )
self * (1/arg)
end
end
 
ypaBHeHue = [[1,2,1,1],[1,5,6,2],[1,5,7,10]].map{ |array| Vector[ *array ] }
# Прямой ход метода Гаусса
ypaBHeHue[0] /= ypaBHeHue[0][0]
ypaBHeHue[1] -= ypaBHeHue[0]*ypaBHeHue[1][0]
ypaBHeHue[2] -= ypaBHeHue[0]*ypaBHeHue[2][0]
 
ypaBHeHue[1] /= ypaBHeHue[1][1]
ypaBHeHue[2] -= ypaBHeHue[1]*ypaBHeHue[2][1]
 
ypaBHeHue[2] /= ypaBHeHue[2][2]
 
# Обратный ход метода Гаусса
ypaBHeHue[1] -= ypaBHeHue[2]*ypaBHeHue[1][2]
ypaBHeHue[0] -= ypaBHeHue[2]*ypaBHeHue[0][2]
 
ypaBHeHue[0] -= ypaBHeHue[1]*ypaBHeHue[0][1]
 
p ypaBHeHue #-> [Vector[1,0,0,19], Vector[0,1,0,-13], Vector[0,0,1,8]]</source>
 
Ну вот и все... вроде как решение получили. Но было бы замечательно, если бы выводилось не все уравнение, а только столбец свободных членов. Задачка простенькая, но важная. Давайте ее решим:
<source lang=ruby>p ypaBHeHue.map{ |vector| vector.to_a }.transpose[-1] #-> [19,-13,8]</source>
 
Теперь задачу можно считать решенной! Жаль только, что программа работает только для уравнения 3х4. Надо бы добавить несколько итераторов, чтобы они самостоятельно определяли размерность уравнения. Для этого нужно проследить чередование индексов и записать для них итераторы (хоть я и недолюбливаю <tt>.each</tt>, но для данного случая он пришелся как нельзя кстати). Расписывать преобразование я не буду, так как не вижу в этом большой надобности. Поэтому, смотрите сразу то, что у меня получилось:
<source lang=ruby>require 'mathn'
 
class Vector
def /( arg )
self * (1/arg)
end
end
 
ypaBHeHue = Matrix[[1,2,1,1],[1,5,6,2],[1,5,7,10]].row_vectors
 
# Прямой ход метода Гаусса
(0...ypaBHeHue.size).each{ |i|
ypaBHeHue[i] /= ypaBHeHue[i][i]
(i+1...ypaBHeHue.size).each{ |j| ypaBHeHue[j] -= ypaBHeHue[i]*ypaBHeHue[j][i] }
}
 
# Обратный ход метода Гаусса
(1...ypaBHeHue.size).to_a.reverse.each{ |i|
(0...i).each{ |j| ypaBHeHue[j] -= ypaBHeHue[i]*ypaBHeHue[j][i] }
}
 
p ypaBHeHue.map{ |vector| vector[-1] } #-> [19,-13,8]</source>
 
Обратите внимание, что <tt>ypaBHeHue</tt> задается через матрицу (которая, не без помощи метода <tt>.row_vectors</tt>, преобразуется в массив векторов). Также обратите внимание, что получить последний столбец можно посредством итератора <tt>.map</tt> и метода "батарейка".
 
Кстати, реализация симплексного алгоритма подразумевает еще несколько интересных задач:
# Если какой либо элемент главной диагонали нулевой, то необходимо переставить строки и/или столбцы, чтобы это исправить.
# Преобразование целевой функции и системы уравнений из текстового формата (<tt>2*x1-x2-x4 > min</tt> и <tt>x1-2*x2-2*x3-x4 <= 10</tt>) в <tt>YAML</tt>
# Первичная обработка целевой функции (если она стремится к min, то необходимо умножить вектор целевой функции на -1) и уравнений системы (если равенство не строгое, то преобразовать его в строгое, путем добавления новой неизвестной: <tt>x1-2*x2-2*x3-x4 &lt;= 10 > x1-2*x2-2*x3-x4+x5 = 10</tt>).
# Поиск ведущего элемента.
# Смена ведущего элемента.
# Вывод всего уравнения в читабельном виде. Лучше всего в виде таблицы.
Буду рад, если кто-то опубликует их решение.
 
== Работа с файлами ==
 
Файлы в программах играют роль хранилищ, в которые можно записать любые объекты. В отличие от привычных нам объектов, файлы позволяют хранить данные даже тогда, когда программа завершила свою работу. Именно поэтому они могут использоваться для передачи данных между разными программами или разными запусками одной и той же программы.
 
{{info|Среди профессиональных программистов принято сохранять настройки программы в файлы, чтобы их мог исправить пользователь или конфигурационная программа}}
 
{{info|В своих операционных системах фирма Microsoft ввела понятие "двоичный файл", но оно порождает больше проблем, чем удобств. Особенно при создании кроссплатформенных приложений}}
 
Как организована работа с файлами? В самом общем случае работа с файлами состоит из следующих этапов:
# Открытие файла. Сущность этого этапа состоит в создании объекта класса <code>File</code>.
# Запись или чтение. Вызываются привычные нам методы вывода на экран и не совсем привычные &mdash; ввода.
# Закрытие файла. Во время закрытия файла происходят действия с файловой системой. С объектом, который создается при открытии файла ничего не происходит, но после этого он указывает на закрытый файл и производить операции чтения/записи при помощи него уже нельзя.
 
Существует масса способов реализации работы с файлами:
# Чтение при помощи класса <code>IO</code>. Класс <code>IO</code> имеет множество методов, которые позволяют производить чтение из текстовых файлов (с "двоичными файлами" лучше так не работать). Если нужно считать весь текстовый файл, то лучше пользоваться методами класса <code>IO</code>.
# Перенаправление потоков. Существует три предопределенных переменных: <code>$stdio</code>, <code>$stdin</code> и <code>$stderr</code>. Если им присвоить объект класса <code>File</code> (создаваемый во время открытия файла), то весь вывод пойдет в файл, который присвоили переменной <code>$stdout</code>. Весь ввод будет браться из файла, который присволи переменной <code>$stdin</code>, а все ошибки будут сохраняться в файле, который присвоили переменной <code>$stderr</code>. Если нужно работать только с одним файлом на чтение и одним файлом на запись, то обычно используют этот способ. Также очень удобно использовать перенаправление потока ошибок (переменная <code>$stderr</code>) для программ, которые работают в составе пакетных файлов и используют только интерфейс командной строки.
# Универсальный способ. Используется в ситуациях, когда нельзя использовать предыдущие два способа.
 
Подведем небольшой итог:
# Если нужно считать весь файл целиком, то надо использовать методы класса IO.
# Если нужно работать только с одним файлом на чтение и только одиним файлом на запись, то надо использовать перенаправление потока.
# Если нельзя применить два вышеперечисленных способа, то надо использовать универсальный способ работы с файлами.
 
=== Чтение при помощи класса IO ===
 
Для чтения файла целиком используется метод <code>.read</code>. Он считывает весь файл в виде строки. Во время его использования не стоит задумывать об открытии/закрытии файла, так как эти операции скрыты внутри метода.
 
<code>config = IO.read(<u>'config.yaml'</u>)
config.class #-> <u>String</u></code>
 
{{Внимание|Имя файла &mdash; это строка. Не стоит усложнять себе жизнь и думать, что имя файла имеет некий "божественный" тип данных. Будьте проще и люди к вам потянутся}}
 
В примере можно увидеть, как считывается файл <code>config.yaml</code> в переменную <code>config</code>. Вторая строчка примера показывает, что, при использовании метода <code>.read</code>, файл считывается в виде строки (класс <code>String</code>). Теперь к переменной <code>config</code> можно применять любые методы из богатого строкового арсенала.
 
В качестве упражнения предлагаю вам считывать и выводить на экран любые файлы, которые вы только найдете у себя на диске.
 
{{Внимание|При считывании "двоичных файлов" в операционных системах фирмы Microsoft использовать данный способ нельзя, так как файл будет считан не до конца. Следует использовать универсальный способ работы с файлами}}
 
=== Перенаправление потока ===
 
Очень часто программист проектирует программу таким образом, чтобы ввод данных осуществлялся с клавиатуры. После сотни-другой циклов откладки программист так устает вводить данные, что создает файл, который содержит все необходимую входную информацию и перенаправляет поток ввода с клавиатуры на этот самый файл. Добавляя всего одну строчку в начало своей программы:
<code>$stdin = File<u>.open('входные данные.txt')</u></code>
 
После этого, он сильно бьет себя по лбу и выкрикивает нечто вроде «Я идиот!» И все от осознания того, что процесс откладки давно можно было упростить таким способом.
 
А вот другая история. Программист пишет и отлаживает программу, которая все необходимые данных выводит на экран. Но в конечном итоге программа должна запускаться без участия человека и ее вывод нужно сохранять в файл (для дальнейшей обработки). Переписывать всю программу лень и поэтому в начало своей программы он вставляет парочку волшебных строчек:
<code>$stdout = File.open('выходные данные.txt',<u>'w'</u>)
$stderr = File.open('сообщения об ошибках.txt',<u>'a'</u>)</code>
 
{{info|Вторым параметром метода <code>.open</code> передается модификатор доступа, то есть кодовое слово по которому метод <code>.open</code> может предположить то, что вы будете делать с этим файлом. В нашем примере мы использовали модификатор <code>w</code> (от англ. write - писать), который говорит о том, что мы будем только писать в файл. Причем каждый раз файл будет перезаписываться. При помощи модификатора <code>a</code> (от англ. append - добавлять) мы указали, что мы будем добавлять данные в файл, а не перезаписывать, как в случае с <code>w</code>}}
 
После этого весь вывод на экран и сообщения об ошибках записываются в соотвествующие файлы. Для того, чтобы посмотреть пример в действии, предалагаю вам выполнить следующую программу:
<code>$stdout = File.open('выходные данные.txt','w')
$stderr = File.open('сообщения об ошибках.txt','a')
puts 'Очень важные данные'
puts 'которые будут сохранены в файл'
<u>raise 'Принудительно вызываем ошибку'</u></code>
 
{{info|Метод <code>raise</code> — для принудительного вызова ошибки.}}
 
Теперь можете смело экспериментировать! Для начала, попробуйте поменять модификаторы <code>w</code> и <code>a</code> местами.
 
=== Универсальный способ работы с файлами ===
Универсальным способом я назвал способ с использованием метода <tt>File.open</tt>. Дело в том, что при помощи него можно осуществлять не только считывание, запись и перезапись, но и их закрытие файлов (чего нельзя сделать при использовании способа с переменными <tt>$stdout, $stdin и $stderr</tt>). Это позволяет несколько раз (за время выполнения программы) осуществлять операции открытия-закрытия файла. В виду того, что эта возможность нужна далеко не всегда, то и используется этот способ только тогда, когда использование всех предыдущих невозможно. Чтение из файла <tt>'входные данные.txt'</tt> при помощи универсального метода будет выглядеть следующим образом:
 
<code>cTpoka = File.open('входные данные.txt','r'){ |file| file.read }</code>
 
Модификатор доступа (<tt>'r'</tt>) указывать необязательно, так как он устанавливается по умолчанию. Поэтому следующий код тоже верен:
 
<code>cTpoka = File.open('входные данные.txt'){ |file| file.read }</code>
 
Если необходимо записать данные, то нужно использовать модификатор доступа 'a' (добавление к концу файла) или 'w' (добавление в файл с его предварительной очисткой). Само собой разумеется, что запись данных в файл осуществляется методами puts, write и т.д.
 
<code>File.open('выходные данные.txt','w'){ |file| file.write cTpoka }
File.open('выходные данные.txt','a'){ |file| file.puts cTpoka }</code>
 
Блок метода <tt>.open</tt> (т.е. фигурные скобки) нужен для того, чтобы при выходе их блока автоматически осуществлять закрытие файла.
 
=== Хитрости ===
 
=== Задачи ===
 
== Сети ==
===Как написать [[w:Троянская программа|троян]]?===
Однажды, один из студентов (<tt>Geka</tt>) попросил меня рассказать о том, как создать простейшее клиент-серверное приложение на Ruby. Перед тем, как подойти ко мне он уже облазил несколько форумов, но у него все равно осталось много вопросов. Отчаявшись он наконец подошел ко мне...
 
{{Начало цитаты}}
В ходе противостояния греков и троянцев была разработана и впервые реализована операция по дезинформации противника. Греки построили огромного деревянного коня, в котором разместили небольшое войско, и поставили его под ворота Трои. В итоге сооружение было перемещено в город и греки одержали победу. Концепция "троянского коня" оказалась настолько действенной, что до сих пор используется всеми разведывательными службами мира. Кроме того, этот метод широко используется хакерами в целях получения нужной информации о своих "жертвах".
{{Конец цитаты}}
 
====Построение серверной части====
На руках у него уже была серверная часть программы, которая позволяла манипулировать удаленной файловой системой (как потом оказалось, ему ее презентовал <tt>Invalid</tt>). Давайте на нее посмотрим...
 
<code>require 'socket'
server = TCPServer.new('localhost', 3000)
while (srv = server.accept)
str = srv.gets.chomp.split(' ')
cmd = str[0]
arg = str[1]
case cmd
when ".."
Dir.chdir("..")
srv.print "OK!"
when "ls"
srv.print Dir.entries(".").join("\n")
when "cd"
begin
Dir.chdir( arg )
srv.print "OK!"
rescue
srv.print "No such file or directory - #{ arg }"
end
when "md"
Dir.mkdir( arg )
srv.print "OK!"
when "rmd"
begin
Dir.rmdir( arg )
srv.print "OK!"
rescue
srv.print "No such file or directory - #{ arg }"
end
when "shutdown"
break
else
srv.print "Bad Command!"
end
<u>srv.close</u>
end</code>
 
Программка конечно хорошая, но сдается мне, что она не работает. Первое на что следует обратить внимание - это закрытие соединения после выполнения каждой команды. При работе с этим сервером через [[w:Telnet|telnet]] будут возникать определенные проблемы. Давайте предельно упростим специализированный код этой программы и посмотрим на структуру клиент-серверного приложения.
 
<code><u>require 'socket'</u>
<u>TCPServer.open('localhost', 3000){ |server|</u>
<u>if (session = server.accept)</u>
session.print "Do6po no}i{aJIoBaTb Ha cepBep\r\n"
session.print "BbI mo}i{eTe Ha6paTb komaHgbI: ls <dir> | cd <dir> | shutdown\r\n"
<u>loop{</u>
<u>cmd, arg = *session.gets.chomp.split</u>
<u>case cmd</u>
when "ls"
begin
session.print Dir[ arg || "*" ].map{ |str| str + "\r\n" }
rescue
session.print "HeT Takoro qpau'JIa uJIu gupekTopuu - #{ arg.inspect }\r\n"
end
when "cd"
begin
Dir.chdir( arg )
session.print "OK!\r\n"
rescue
session.print "HeT Takoro qpau'JIa uJIu gupekTopuu - #{ arg.inspect }\r\n"
end
when "shutdown"
session.close
break
else
session.print "HeBepHa9 komaHga!\r\n"
<u>end</u>
<u>}</u>
<u>end</u>
<u>}</u></code>
 
{{info|Для того, чтобы соединиться с этим сервером, необходимо выполнить команду <tt>telnet</tt> и набрать <tt>o localhost 3000</tt>. После успешного соединения можете набирать команды <tt>ls</tt>, <tt>cd</tt> или <tt>shutdown</tt>}}
 
Во-первых, был добавлен бесконечный цикл <code>loop</code>, чтобы сессия сохранялась до тех пор, пока не поступит команда <tt>shutdown</tt>. При этом было сокращено количество поддерживаемых команд (убраны команды удаления и создания директорий), чтобы нас не обвинили в создании и распространении деструктивного кода.
 
После небольших манипуляций с кодом стал виден базовый каркас сервера. Он состоит из следующих блоков:
* <code>require <u>'socket'</u></code>
Для того, чтобы работать с классом <tt>TCPServer</tt> (и не только с ним) необходимо подключить библиотеку <tt>socket</tt>.
* <code>TCPServer.open(<u>'localhost',3000</u>){ |server| ... }</code>
Этот программный код создает сервер, который будет прослушивать порт <tt>3000</tt>. В качестве порта может использоваться любой другой (например, <tt>31337</tt>). Менять имя хоста (<tt>localhost</tt>) не нужно, если только у Вас не несколько сетевых интерфейсов. Если у Вас их все таки несколько, то ничего по поводу смены хоста Вам объяснять не надо. Вы и так все, скорее всего, знаете ...
* <code>if (<u>session = server.accept</u>) ... end</code>
При помощи такой нехитрой комбинации ловится соединение с сервером. Обратите внимание, что в примере студента использовалась конструкция <code>while</code>. Ее пришлось упразднить, т.к. большой необходимости в ней не было. Как только вызов <code>server.accept</code> возвращает значение, то это означает, что к серверу подсоединился клиент. В переменную <code>session</code> записывается указатель на соединение. С ним-то мы и будем работать дальше...
* <code>cmd, arg = <u>*</u>session.gets.chomp.split</code>
Данный код интересен тем, что в программе студента для его реализации задействовано аж три строчки. И все лишь потому, что он не знал, что такое оператор <tt>*</tt> (звездочка). Этот оператор преобразует массив в список параметров. Тем самым он освобождает нас от нуждого присваивания. Сам же код получает от клиента строку, которая интерпретируется им как "команда и аргумент, разделенные пробелом". Обратите внимание, что работа идет с переменной <code>session</code>, а не <code>server</code>.
* <code>case <u>cmd</u> ... end</code>
Ветвление <code>case</code> необходимо для обработки команд. В зависимости от значения переменной <code>cmd</code> будут выполнены те или иные действия.
* Остальной код описывать не буду, так как каркасом клиент-серверного приложения он не является, а реализует функциональность конкретного приложения.
 
{{info|Для того, чтобы прекратить работу сервера необходимо нажать комбинацию клавиш <tt>Ctrl+C</tt> или <tt>Ctrl+Break</tt>. Команду отключения данный сервер не поддерживает. Команда <tt>shutdown</tt> относится к клиенту}}
 
Несмотря на то, что данный сервер вполне рабочий, у него есть один существенный недостаток - он не работает для нескольких клиентов. Для того, чтобы это реализовать необходимо обрабатывать каждое соединение с клиентом в отдельном потоке. Кто изучал мультипроцессорное программирование, тот понимает о чем речь. Вот только не стоит сразу кидаться в книжный магазин за необходимой литературой. В составе дистрибутива Ruby уже есть замечательная библиотека <tt>gserver</tt>, которая как раз и занимается тем, что реализует обработку запросов клиентов в отдельных потоках. Для демонстрации ее работы перепишем предыдущую программу под <tt>gserver</tt>.
 
<code><u>require 'gserver'</u>
<u>class Tpo9H < GServer</u>
<u>def serve( session )</u>
session.print "Do6po no}i{aJIoBaTb Ha cepBep\r\n"
session.print "BbI mo}i{eTe Ha6paTb komaHgbI: ls <dir> | cd <dir> | shutdown\r\n"
loop{
cmd, arg = *session.gets.chomp.split
case cmd
when "ls"
begin
session.print Dir[ arg || "*" ].map{ |str| str + "\r\n" }
rescue
session.print "HeT Takoro qpau'JIa uJIu gupekTopuu - #{ arg.inspect }\r\n"
end
when "cd"
begin
Dir.chdir( arg )
session.print "OK!\r\n"
rescue
session.print "HeT Takoro qpau'JIa uJIu gupekTopuu - #{ arg.inspect }\r\n"
end
when "shutdown"
session.close
break
else
session.print "HeBepHa9 komaHga!\r\n"
end
}
end
end
<u>tpo9H = Tpo9H.new( 31337 )</u>
<u>tpo9H.audit = true</u>
<u>tpo9H.start</u>
<u>tpo9H.join</u>
 
Программа претерпела множество изменений, но зато теперь поддерживаются несколько одновременно работающих клиентов. Давайте рассмотрим подробнее изменения, которые пришлось внести:
* <code>require '<u>gserver</u>'</code>
Вместо библиотеки <tt>socket</tt> мы подключаем библиотеку <tt>gserver</tt>. Для нас это означает то, что работать с классом <tt>TCPServer</tt> напрямую мы не будем. Использовать мы будем класс <tt>GServer</tt>.
* <code>class <u>Tpo9H < GServer</u></code>
Этот код создает класс Tpo9H, который наследует функциональность от класса GServer. Можно было бы конечно просто расширить класс GServer, но так хотелось "хакерское" название класса, что я не смог удержаться и произвел наследование.
* <code>def serve( <u>session</u> )</code>
Метод <tt>serve</tt> (название какое-то незаконченное... но на англ. означает "обслуживать") используется классом <tt>GServer</tt> как обработчик сессии с клиентом. При выходе из метода, сессия автоматически закрывается. Обратите внимание, что переменная <tt>session</tt> является параметром метода. Весь код обработки сессии взят из предыдущей программы без изменений.
* <code>tpo9H = Tpo9H.new( <u>31337</u> )</code>
Создается экземпляр класса Tpo9H. В качестве порта для "прослушки" указан <tt>31337</tt>. Почему не <tt>3000</tt>? Потому, что захотелось! При желании можете его сменить на любой другой.
* <code>tpo9H<u>.audit = true</u></code>
Если пропустить эту строчку, то сервер будет работать молча. Настолько молча, что Вы не сможете понять работает он или нет? Надпись об удачном запуске сервера будет выглядеть примерно так:
[Mon Oct 23 23:33:12 2006] Tpo9H 127.0.0.1:3000 start
Если она не появилась, то появится какая-то другая, которая известит Вас об синтаксической ошибке, которую Вы допустили при переписывании кода.
* <code>tpo9H<u>.start</u></code>
Этой командой мы стартуем сервер.
* <code>tpo9H<u>.join</u></code>
Класс <tt>GServer</tt> (и его наследники) прослушивает порт в фоновом режиме. Но если программа завершается, то завершатся и все нити (Thread's). Поэтому надо чем-то занять программу на время работы сервера. Вот и было решено ожидать завершения прослушивающей нити сервера. Эта команда останавливает выполнение программы (за исключением нитей в фоне) до окончания прослушивания экземпляром созданного класса.
 
====Построение клиентской части====
Как мы уже видели выше, серверная часть полностью определяет функциональность всего клиент-серверного приложения. Клиентская часть же лишь принимает от пользователя запросы, пересылает их серверу и получает ответ, который передает пользователю.
 
Хотя Geka и имел в своем распоряжении клиентскую программу, ее мы рассматривать не будем, т.к. с тех пор его сервер претерпел некоторые изменения и прежняя клиентская часть не может работать с нынешней серверной. Мы напишем новую клиенскую часть "с нуля".
 
Для соединения с сервером по протоколу [[w:TCP/IP|TCP/IP]] используется класс <tt>TCPSocket</tt> из библиотеки <tt>socket</tt>. Для того, чтобы наша задача была конкретней, мы заставим сервер (при помощи нашего клиента) выполнить следующие команды: <tt>ls</tt>, <tt>cd ..</tt>, <tt>ls</tt> и <tt>shutdown</tt>. Вывод результата выполнения этих команд мы будем осуществлять на экран, но пользователь не получит возможности изменять эту последовательность действий (кроме как исправив программу). Он увидит только результат. Логика здесь следующая: зачем заставлять пользователя вводить команды, если это может делать программа? Если же пользователю нужна другая последовательность команд, то пусть использует telnet или правит клиентскую часть под свои нужды.
 
Итак, давайте наберем клиентский код, который будет соединяться с нашей серверной частью. Скорее всего он будет выглядеть вот так:
 
<code>require 'socket'
TCPSocket.open('localhost',31337){ |client|
2.times{ puts client.gets.chomp }
client.puts "ls"
puts client<u>.read</u>
client.puts "cd .."
puts client.gets
client.puts "ls"
puts client<u>.read</u>
client.puts "shutdown"
}</code>
 
Все замечательно, но программа не работает. Не вся, конечно... Она выводит приглашение к диалогу и все, дальше виснет. Это связано с тем, что используется метод .read, который считывает весь поток целиком, пока не встретит символ EOF. Его, то наш сервер как раз и не передает. Не будем пока спешить и править сервер, а применим один нечестный прием: будем использовать не метод <code>.read</code>, а метод <code>.sysread( <u>n</u> )</code>. Почему прием нечестный? Да все потому, что метод <code>.sysread( n )</code> считывает <tt>n</tt>-первых сиволов из потока. Так как мы не знаем сколько нам надо считать символов, то мы зададим в качестве <tt>n</tt> очень большое число. Например, <tt>5000</tt>. Если символов в потоке меньше, чем <tt>5000</tt>, то <code>.sysread( n )</code> считает столько, сколько есть. Эту особенность мы и используем. Разве можно сказать, что такой прием честный?
 
<code>require 'socket'
TCPSocket.open('localhost',31337){ |client|
2.times{ puts client.gets.chomp }
client.puts "ls"
puts client<u>.sysread(5000)</u>
client.puts "cd .."
puts client.gets
client.puts "ls"
puts client<u>.sysread(5000)</u>
client.puts "shutdown"
}</code>
 
Уже лучше... по крайней мере программа работает. Но давайте поразмышляем над ситуацией, которая произошла с методом <code>.read</code>. Если немного подправить сервер и выдавать после каждой передачи этот символ, то программа с <code>.read</code> могла бы с успехом работать. Какова здесь мораль? А мораль в том, что для успешной работы необходимо с сервера передавать сигнал, который означал бы "последняя строка, которую я передаю клиенту". Чтобы клиент не пытался читать данные с сервера, а начинал их передачу. Вполне естественно, что добавление такого сигнала означает модификацию сервера. В качестве сигнала последней строки мы будем использовать строку <tt>+ОК</tt>. Почему именно такую? Просто видел ее где-то, вот и решил использовать. Если хотите, то можете использовать свою строку. Только не забудьте об этом, когда будете писать программу-клиент. Вот модифицированный сервер:
 
<code>require 'gserver'
class Tpo9H < GServer
def serve( session )
session.puts "Do6po no}i{aJIoBaTb Ha cepBep\r\n"
session.puts "BbI mo}i{eTe Ha6paTb komaHgbI: ls <dir> | cd <dir> | shutdown\r\n"
loop{
cmd, arg = *session.gets.chomp.split
case cmd
when "ls"
begin
session.puts Dir[ arg || "*" ].map{ |str| str + "\r\n" }
rescue
session.puts "HeT Takoro qpau'JIa uJIu gupekTopuu - #{ arg.inspect }\r\n"
end
when "cd"
begin
Dir.chdir( arg )
session.puts "OK!\r\n"
rescue
session.puts "HeT Takoro qpau'JIa uJIu gupekTopuu - #{ arg.inspect }\r\n"
end
when "shutdown"
session.close
break
else
session.puts "HeBepHa9 komaHga!\r\n"
end
<u>session.puts "+OK"</u>
}
end
end
tpo9H = Tpo9H.new( 31337 )
tpo9H.audit = true
tpo9H.start
tpo9H.join
</code>
 
Была добавлена лишь одна команда (хотя, самые внимательные могут заметить, что еще и метод <code>.print</code> был заменен на <code>.puts</code>) - <code>session.puts <u>"+OK"</u></code>. Она будет выполнятся после каждой передачи данных от сервера к клиенту. Тем самым мы будем извещать клиента о том, что передача завершается. Теперь займемся переписыванием клиента. Необходимо исправить код там, где происходит чтение, чтобы он учитывал строки <tt>+OK</tt>.
 
<code>require 'socket'
TCPSocket.open('localhost',31337){ |client|
2.times{ puts client.gets.chomp }
client.puts "ls"
<u>loop{</u>
str = client.gets.chomp
if <u>str[/^\+OK/]</u>
break
else
puts str
end
<u>}</u>
client.puts "cd .."
<u>loop{</u>
str = client.gets.chomp
<u>str[/^\+OK/] ? break : puts( str )</u>
<u>}</u>
client.puts "ls"
<u>while !(str = client.gets.chomp)[/^\+OK/]</u>
puts str
end
client.puts "shutdown"
}</code>
 
Код стал выглядеть ужасней, зато стал более честным. Стоит отметить, что предложено три варианта обработки строки <tt>+OK</tt>. Правда, отличаются они лишь в деталях и функционально делают одно и то же. Давайте рассмотрим подробней решения, которые были реализованы в ходе исправления:
* <code>loop{ ... }</code>
Используя бесконечный цикл, мы получаем возможнось самостоятельно определять точку выхода из цикла. Это бывает полезно лишь на начальных стадиях реализации. Дальше лучше избавляться от бесконечного цикла и переходить сначала к циклам с предусловием, а потом (при возможности) к итераторам.
* <code>if <u>str[/^\+OK/]</u> then ... else ... end</code>
При помощи команды <code>str[/^\+OK/]</code> мы проверяем наличие строки <tt>+OK</tt> в переменной <tt>str</tt>. Если проверка прошла успешно, то происходит выход из бесконечного цикла. Если нет, то продолжается получение данных и вывод их на экран.
* <code>str[/^\+OK/] ? break : puts( str )</code>
Это всего лишь иная запись кода: <code>if str[/^\+OK/] then ... else ... end</code>. Зато более короткая...
* <code>while <u>!(str = client.gets.chomp)[/^\+OK/]</u> do ... end</code>
Условный оператор и бесконечный цикл были объеденены в цикл с предусловием. Условия выхода тоже самое, но оно совмещено с чтением и присваиванием. Выглядит жутковато, но это вполне работоспособный код.
 
Из всех предложенных вариантов вы вольны выбирать любой... но мне не нравится вообще весь клиент. Как-то он сильно разросся и теперь выглядит монстрозно. Конечно же, есть возможность загнать чтение в отдельный метод, но мы этого делать не будем. Хотя... где наша не пропадала?! Давайте вынесем код отсылки команды и получения ответа в отдельный код. Естественно, что это будет метод для класса <tt>TCPSocket</tt> (который мы будем расширять). Назовем мы его <code>.cmd</code>.
 
<code>class TCPSocket
def cmd( command, regexp = /^\+OK/ )
<u>self.</u>puts command
while !(str = <u>self.</u>gets.chomp)[ regexp ]
<u>yield str</u>
end
end
end</code>
 
Итак, теперь все экземпляры класса <tt>TCPSocket</tt> приобрели метод <code>.cmd</code>, который отсылает команды и принимает результат. Непонятными могут быть следующие моменты:
* <code><u>self</u>.puts command</code>
Переменная <code>self</code> содержит указатель на текущий объект, то есть она будет указывать на объект, который хранится в переменной <code>client</code> (так как вызываться метод будет именно для этого объекта).
* <code>yield str</code>
Данная команда передает значение переменной <tt>str</tt> в блок. Наличие этой команды подразумевает наличие блока во время вызова метода (имеется в виду <code>.cmd</code>). Блок позволит нам обрабатывать результат так, как мы хотим. Не ограничивая себя выводом результата на экран.
 
Теперь остается исправить клиент и посмотреть на него в действии:
 
<code>TCPSocket.open('localhost',31337){ |client|
2.times{ puts client.gets.chomp }
client.cmd("ls")<u>{ |str| puts str }</u>
client.cmd("cd .."){ |str| puts str }
client.cmd("ls"){ |str| puts str }
client.cmd("shutdown"){ |str| puts str }
}</code>
 
Обратите внимание на код:
 
<code>client.cmd("ls")<u>{ |str| puts str }</u></code>
 
Метод <code>.cmd</code> работает как итератор. Последовательно передавая пришедшие строки в блок для дальнейшей обработки.
 
Чуть не забыл. Данный код выдает ошибку вида:
 
client_001.rbw:18:in `cmd': private method `chomp' called for nil:NilClass (NoMethodError)
 
Это все потому, что передача команды <tt>shutdown</tt> не подразумевает ответа. А это неправильно. В очередной раз исправим сервер, чтобы избавится от этой ошибки:
 
<code>require 'gserver'
class Tpo9H < GServer
def serve( session )
session.puts "Do6po no}i{aJIoBaTb Ha cepBep\r\n"
session.puts "BbI mo}i{eTe Ha6paTb komaHgbI: ls <dir> | cd <dir> | shutdown\r\n"
loop{
cmd, arg = *session.gets.chomp.split
case cmd
when "ls"
begin
session.puts Dir[ arg || "*" ].map{ |str| str + "\r\n" }
rescue
session.puts "HeT Takoro qpau'JIa uJIu gupekTopuu - #{ arg.inspect }\r\n"
end
when "cd"
begin
Dir.chdir( arg )
<u>session.puts "CmeHa gupekTopuu Ha #{arg} npowJIa yga4Ho!\r\n"</u>
rescue
session.puts "HeT Takoro qpau'JIa uJIu gupekTopuu - #{ arg.inspect }\r\n"
end
when "shutdown"
<u>session.puts "+OK"</u>
session.close
break
else
session.puts "HeBepHa9 komaHga!\r\n"
end
session.puts "+OK"
}
end
end
tpo9H = Tpo9H.new( 31337 )
tpo9H.audit = true
tpo9H.start
tpo9H.join
 
На этом можно было бы и закончить, если бы не одно НО: метод <code>.cmd</code> уже реализован в рамках класса <tt>Net::Telnet</tt>. И не переписать наш клиент под этот класс было бы неправильно. Переписываем:
 
<code>require 'net/telnet'
client = Net::Telnet.new( 'Host' => 'localhost', 'Port' => 31337, "Prompt" => /^\+OK/n )
client.cmd("ls"){ |str| puts str }
client.cmd("cd .."){ |str| puts str }
client.cmd("ls"){ |str| puts str }
client.close</code>
 
Вот теперь, уж точно все!
{{info|Вскоре, после публикации данной главы в учебнике, <tt>Geka</tt> прислал клиентскую часть, которую он реализовал в две строчки:
 
<code><u>system</u>( "telnet " + gets )
<nowiki>loop{</nowiki> <u>system</u>( gets )<nowiki>{ |str|</nowiki> puts str <nowiki>} }</nowiki></code>
 
Способ не совсем честный, но нет причин о нем не рассказать. Данный способ использует метод <code>system</code>, который вызывает внешнюю программу (в данном случае <tt>telnet</tt>). Далее все введенное с клавиатуры уходит в программу <tt>telnet</tt>, а выдаваемое на экран берется из результата работы этой программы.
 
После запуска данной программы надо ввести имя хоста и порт. Далее, можно вводить команды, которые поддерживает сервер (в нашем случае <tt>ls</tt>, <tt>cd</tt> и <tt>shutdown</tt>).
}}
 
===Как создать сетевой блокнот?===
Идея написать подобную программу появилась после прочтения статьи [http://doci.nnm.ru/php_forall_/22.11.2006/sozdaem_svoj_onlajn_bloknot/ Создаем свой online-блокнот]. Продемонстрированная там программа предельно проста, но на ее примере очень удобно показать работу с [[w:ERB|ERB]]. А учитывая тот факт, что ERB используется в составе инструментария [[w:Ruby on Rails|Руби на Рельсах]], то ценность этой статьи становится очевидной.
====Первое приближение====
В первом приближении мы попытаемся реализовать ту же самую функциональность, что и описана в статье. Вот только [[w:PHP|PHP]] подразумевает наличие веб-сервера, который будет заниматься интерпретацией его команд. В нашем же примере мы самостоятельно поднимем веб-сервер (написанный на Ruby), чтобы не заморачиваться с настройкой стороннего.
 
Смысл всей программы следующий: надо создать страницу с окном ввода и кнопкой "Сохранить", при нажатии на которую происходит сохранение текста из окна ввода в файл <tt>notepad.txt</tt>. Ввод осуществляется через [[w:браузер|браузер]] по адресу http://localhost:8080.
 
{{info|Сервер будем запускать на порт 8080, так как стандартный для веб-серверов порт 80 у меня занял Skype. При желании, порт можно легко поменять}}
 
Теперь, собственно, сама программа:
 
<code><u>require 'webrick'</u>
server = WEBrick::HTTPServer.new( <u>:Port => 8080</u> )
server<u>.mount_proc('/')</u>{ |<u>req</u>,<u>resp</u>|
<u>File.open('notepad.txt','w')</u>{ |f| f.write( <u>req.query["text"]</u> ) } <u>if req.query["text"]</u>
<u>resp['Content-Type'] = 'text/html'</u>
resp<u>.body</u> = <u>%& </u><nowiki><html><body><center><form method="post">
<textarea name="text" rows="4" cols="40"></nowiki><u>#{IO.read('notepad.txt')}</u><nowiki></textarea>
<br /><input type="submit" name="update" value="Сохранить" />
</form></center></body></html></nowiki><u>& </u>
}
server<u>.start</u></code>
 
Рассмотрим код более подробно. В частности, хочу обратить ваше внимание на следующие фрагменты:
* <code>require '<u>webrick</u>'</code>
: Подключение библиотеки [[w:WEBrick|WEBrick]] для построения серверов (в том числе и веб-серверов).
* <code>:Port => <u>8080</u></code>
: Как уже было сказано ранее, порт <tt>80</tt> у меня занят. Поэтому приходится искать другой. Исключительной магической силой порт <tt>8080</tt> не обладает. Поэтому, при желании, его можно сменить на другой.
* <code>server.mount_proc(<u>'/'</u>)</code>
: На виртуальную корневую директорию мы вешаем процедурный сервлет. Он будет заниматься обработкой запроса на адрес http://localhost:8080/, то есть обращение к виртуальной корневой директории. Чтобы изменить запрос, на который будет откликаться сервлет, достаточно заменить строку <tt>'/'</tt> на другую, например <tt>'/notepad'</tt>. Тогда, адрес сервлета будет http://localhost:8080/notepad. Только, зачем писать больше?
* <code>{ |<u>req</u>,<u>resp</u>| ... }</code>
: Процедурному (как и любому другому) сервлету передается в блок две переменные. Переменная <tt>req</tt> содержит информацию о запросе (какой браузер запрашивает, какие переменные передаются и так далее), а при помощи переменной <tt>resp</tt> формируется ответ (какой тип информации передается, информация для отображения и так далее).
* <code>... if <u>req.query["text"]</u></code>
: Постфиксная запись условного оператора <tt>if</tt>. Блок, перед <tt>if</tt> будет выполняться только в том случае, когда сервлету будет передаваться переменная <tt>text</tt>. Метод <tt>.query</tt> возвращает ассоциативный массив в виде <tt>{имя_переменной => значение}</tt>.
* <code>File.open(<u>'notepad.txt'</u>,<u>'w'</u>){ |f| f.write( <u>req.query["text"]</u> ) }</code>
: Открываем файл <tt>notepad.txt</tt> для записи и пишем туда значение переменной <tt>text</tt> (передается сервлету в теле запроса).
* <code>resp[<u>'Content-Type'</u>] = <u>'text/html'</u></code>
: Устанавливаем тип передаваемых нами данных. Обычно тип определяется по расширению файла, но процедурный сервлет про файл, а тем более про его расширение, ничего не слышал. Вот и приходится указывать тип передаваемых данных через параметр <tt>Content-Type</tt>.
* <code>resp<u>.body</u> = <u>%& </u> ... <u>& </u></code>
: При помощи метода <tt>.body=</tt> мы передаем [[w:HTML|HTML]]-код в качестве ответа на запрос. Сам HTML-код передается в виде строки, заключенной в <tt>%& ... & </tt>. Это альтернативный способ задания строки. После символа <tt>%</tt> идет символ, который будет замыкающим (в нашем случае <tt>& </tt>). Такой способ задания строки используется в случаях, когда строка содержит много кавычек и апострофов (чтобы не заниматься их экранированием).
* <code>server<u>.start</u></code>
: При помощи метода <tt>.start</tt> мы запускаем веб-сервер.
 
{{info|Для того, чтобы прекратить работу веб-сервера, надо его просто выгрузить. Это делается при помощи комбинации клавиш <tt>Ctrl+Break</tt> или <tt>Ctrl+C</tt>}}
 
Получилось немного громоздкое описание, но это только поначалу. Дальше мы будем рассматривать только изменения, а их будет существенно меньше.
 
====Добавляем ERB====
Теперь приступим к ERB. Это шаблонная библиотека, которая позволяет осуществлять вставку в произвольный текст любого Ruby-кода. Для этого имеются два основных тега:
* <code><nowiki><% ... %></nowiki></code>
: Код внутри тега выполняется. Во время обработки он заменяется на пустую строку.
* <code><nowiki><%= ... %></nowiki></code>
: Код внутри тега выполняется. Результат последнего вычисления преобразуется в строку и вставляется вместо тега.
 
{{Внимание|Обработка кода внутри тегов ERB идет только во время обработки шаблона (вызова метода <tt>.result</tt>)}}
 
Не знаю почему, но ERB для меня это "PHP, только на Ruby". Эта фраза обладает столь магическим свойством, что после нее собеседнику все становится понятным. Вот и вам советую воспринимать ERB как "PHP на Ruby".
 
{{info|Библиотека WEBrick поддерживает PHP-скрипты благодаря [[w:CGI|CGI]]-сервлету}}
 
Давайте посмотрим, как может выглядеть наша программа в которой используется ERB-шаблон:
 
<code>require 'webrick'
<u>require 'erb'</u>
server = WEBrick::HTTPServer.new( :Port => 8080 )
server.mount_proc('/'){ |req,resp|
File.open('notepad.txt','w'){ |f| f.write req.query["text"] } if req.query["text"]
resp['Content-Type'] = 'text/html'
<u>wa6JIoH = </u>%& <nowiki><html><body><center><form method="post">
<textarea name="text" rows="4" cols="40"></nowiki><u><nowiki><%=</nowiki></u>IO.read('notepad.txt')<u><nowiki>%></nowiki></u><nowiki></textarea>
<br /><input type="submit" name="update" value="Сохранить" />
</form></center></body></html></nowiki>&
resp.body = <u>ERB.new( wa6JIoH ).result</u>
}
server.start</code>
 
Что изменилось? Изменений немного, но они все же есть:
* <code>require <u>'erb'</u></code>
: Подключаем библиотеку ERB.
* <code><u>wa6JIoH = </u>%& ... <u><nowiki><%=</nowiki></u>IO.read('notepad.txt')<u><nowiki>%></nowiki></u> ... & </code>
: Создаем переменную <tt>wa6JIoH</tt> и присваиваем ей строку, которая по совместительству является ERB-шаблоном. Внутри строки можно заметить тег <tt><nowiki><%= ... %></nowiki></tt> внутри которого осуществляется считывание файла <tt>notepad.txt</tt>. Результат считывания будет вставлен вместо тега <tt><nowiki><%= ... %></nowiki></tt> во время обработки ERB-шаблона.
* <code>resp.body = <u>ERB.new( wa6JIoH ).result</u></code>
Создаем объект ERB и передаем туда подготовленный ERB-шаблон. Обрабатываем его методом <tt>.result</tt> и результирую строку передаем методу <tt>.body=</tt>, который подставляет ее в качестве ответа на запрос.
 
И что мы получаем в результате? Подключили "лишнюю" библиотеку и создали "лишнюю" переменную? Не будем спешить с выводами. Использование библиотеки ERB позволяет вынести шаблон во внешний файл. Тем самым мы очищаем Ruby-код от HTML-кода.
 
{{Начало цитаты}}"Кесарю - кесарево!" (с) [[w:Иисус Христос|Иешуа Ганоций]]{{Конец цитаты}}
====Выносим ERB-шаблон во внешний файл====
ERB-шаблон существенно портит красоту нашего Ruby-кода. Поэтому, решение вынести шаблон во внешний файл вполне оправдано. Тем более, что это позволит нам править ERB-шаблон отдельно от программы. Более того, внесенные в шаблон изменения будут вступать в силу без перезагрузки сервера.
 
Файл с ERB-шаблоном (<tt>index.html</tt>) будет выглядеть следующим образом:
<source lang=rails><html>
<body>
<center>
<form method="post">
<textarea name="text" rows="4" cols="40"><%=IO.read('notepad.txt')%></textarea><br />
<input type="submit" name="update" value="Сохранить" />
</form>
</center>
</body>
</html></source>
 
Переменную шаблон мы убираем, а вместо нее вставим считывание файла c ERB-шаблоном (<tt>index.html</tt>).
 
<code>require 'webrick'
require 'erb'
server = WEBrick::HTTPServer.new( :Port => 8080 )
server.mount_proc('/'){ |req,resp|
File.open('notepad.txt','w'){ |f| f.write req.query["text"] } if req.query["text"]
resp['Content-Type'] = 'text/html'
resp.body = ERB.new( <u>IO.read('index.html')</u> ).result
}
server.start</code>
 
Вот, уже другое дело! Можно было бы этим и ограничиться, если бы в библиотеке <tt>WEBrick</tt> отсутствовал бы ERB-сервлет... Но он есть!
 
====ERB-шаблон превращается в ERB-сервлет====
Если ERB-шаблон подключать, как ERB-сервлет, то программа существенно упрощается за счет того, что логику, которая отвечает за формирование данных можно вынести в шаблон. Чтобы это ощутить, достаточно взглянуть на новую версию нашей программы:
 
<code>require 'webrick'
server = WEBrick::HTTPServer.new( :Port => 8080 )
server<u>.mount('/',WEBrick::HTTPServlet::ERBHandler,'index.html')</u>
server.start</code>
 
{{info|Самые внимательные читатели заметили, что строка <tt>require 'erb'</tt> волшебным образом испарилась. Связано это с тем, что реализация ERB-сервлета уже подключает библиотеку ERB}}
 
Но, чтобы добиться столь существенного уменьшения кода программы, пришлось немного изменить файл <tt>index.html</tt> (с ERB-шаблоном):
 
<code><nowiki><%</nowiki> <u>File.open('notepad.txt','w'){ |f| f.write query["text"] } if query["text"]</u> <nowiki>%>
<html><body><center><form method=post>
<textarea name="text" rows="4" cols="40"><%=IO.read('notepad.txt')%></textarea>
<br /><input type="submit" name="update" value="Сохранить" />
</form></center></body></html></nowiki></code>
 
Как можно заметить, в самое начало шаблона был добавлен тег, который осуществляет сохранение содержимого переменной <tt>text</tt> в файл <tt>notepad.txt</tt>. Код тега был перенесен из программы практически один к одному. За одним только исключением: к переменной <tt>text</tt> мы теперь обращаемся через переменную <tt>query</tt>, а не через <tt>req.query</tt>. Вот и все отличие!
 
На этом все... из чего состоит наша программа теперь?
* <tt>notepad.rb</tt>. Программа-сервер. Назвать файл можно на свое усмотрение. Главное, чтобы работал. Содержит логику, которая осуществляет конфигурирование и запуск сервера.
* <tt>index.html</tt>. ERB-шаблон. В нем содержится вся логика программы, кроме реализованной в программе-сервере.
* <tt>notepad.txt</tt>. Файл данных. В нем содержатся записи, которые мы вводим и отображаем посредством нашей программы.
 
В качестве задания для самостоятельной проработки, предлагаю вам реализовать не только ввод и редактирование, но и просмотр без возможности редактирования. Подсказка: подключайте второй сервлет.
 
=== Гнезда, которые свили не птицы ===
[[Изображение:Гнезда.png|frame|center|Примерное содержание главы]]
 
=== Как пропинговать компьютер в сети? ===
 
Открываем новую серию статей, которые будут рассказывать про использование встроенных библиотек Ruby. Первая статья будет посвящена написанию утилиты <tt>ping</tt> (в очень упрощенной форме). Смотрим в стантартную библиотеку и обнаруживаем файлик <tt>ping.rb</tt>. Смотрим в него и обнаруживаем метод <code>pingecho</code>. Метод используется следующим образом:
<source lang=ruby>require 'ping'
host = ARGV[0] || "localhost"
printf("%s alive? - %sn", host, Ping::pingecho(host, 5))</source>
Данный метод имеет один маленький недостаток. Он не отслеживает никаких ошибок кроме <code>Timeout::Error и Errno::ECONNREFUSED</code>. Меня это немного смутило и поэтому я убрал явное указание на Timeout. Получился примерно такой метод:
<source lang=ruby>require 'socket'
require 'timeout'
def ping( host, service = "echo",timeout = 5)
begin
timeout( timeout ){
TCPSocket.open( host, service){}
}
rescue Errno::ECONNREFUSED
true
rescue false
else true
end
end
p ping(ARGV[0] || "localhost")</source>
Итак, давайте разберем, что делает наш метод. Он создает соединение посредством класса TCPSocket и тут же закрывает его. Если соединение проходит слишком долго (хост не существует в сети) или произошла какая-то другая ошибка (не поддерживается протокол или еще что-то), то метод возвращает <code>false</code>. Если удаленный хост явно отверг наш запрос или принял его, то мы возвращаем <code>true</code>. Как видите, ничего сложного в этом нет.
 
=== Простейший датчик работы службы ===
 
Летним воскресным утром мне захотелось сделать что-то приятное и красивое... Я написал письмо в конференцию о моих светлых идеях, но не смог его отправить. [[w:SMTP|SMTP]]-сервер безбожно висел. И решило мое больное воображение написать программку, которая документировала бы подобные ситуации. В общем мне нужен был простейший документ (лог работы службы), который бы доказывал, что наши сетевые админы зря едят свой хлеб (а на самом деле я добрый). Для начала я определился с информацией, которая мне была нужна. А именно:
* текущее время;
* время отклика;
* баннер службы.
 
Ее я решил писать в логи следующим образом... Каждый день будет создаваться файл лога и каждый час в него будет писаться информация о работе службы. В качестве планировщика заданий использовался виндовый [http://www.nncron.ru/ Cron], который запускал мою программу в нужное время. Для начала я написал программу, которая соединяется со службой SMTP и получает от него баннер:
<source lang=ruby>require 'socket'
 
request = ""
begin_time = Time.now
t = TCPSocket.open('mail.scli.ru', 'smtp'){
request = t.gets.chomp
t.close
end_time = Time.now
File.open( Time.now.strftime('d:/logs/smtp/%Y_%m_%d.smtp'), File::APPEND | File::CREAT | File::WRONLY ){ |f|
f.puts( "#{sprintf( '%02d',Time.now.hour )} | #{end_time-begin_time} | #{request}" )
}</source>
Как и следовало ожидать, программа не работала. Она подвисала и не хотела ничего писать в файл. Висела она на строчке:
<source lang=ruby>request = t.gets.chomp</source>
Чтобы разобраться с проблемой, пришлось читать книжку. Слава богу, что под рукой оказалась книга [http://books.dore.ru/bs/f1bid1824.html TCP/IP. Учебный курс]. В ней на с.345 черным по серому была начертана схема взаимодействия SMTP протокола. Как оказалось, чтобы получить баннер от службы, надо послать команду <tt>NOOP</tt>. Переписываем наш фрагмент программы.
<code>require 'socket'
request = ""
begin_time = Time.now
t = TCPSocket.open('mail.scli.ru', 'smtp')
<u>t.puts('NOOP') </u>
request = t.gets.chomp
t.close
end_time = Time.now
File.open( Time.now.strftime('d:/logs/smtp/%Y_%m_%d.smtp'), File::APPEND | File::CREAT | File::WRONLY ){ |f|
f.puts( "#{sprintf( '%02d',Time.now.hour )} | #{end_time-begin_time} | #{request}" )
}</code>
Великолепно! Программа работает... но иногда зависает. И тогда меня посетила еще одна мысль: на соединение отводить всего одну секунду (не уложился — сам дурак). Если соединение зависало, то в файл записывалось <tt>timeout</tt>. Чтобы "отводить время на выполнение операции" нужно задействовать библиотеку <tt>timeout</tt>. Она у меня входила в состав дистрибутива. Итак, переписываем нашу программу:
<code><u>require 'timeout'</u>
require 'socket'
request = ""
beachmark = ""
begin
beachmark = Time.now
<u>timeout(1){</u>
t = TCPSocket.open('mail.scli.ru', 'smtp')
t.puts('NOOP')
request = t.gets.chomp t.close
<u>}</u>
beachmark = Time.now - beachmark
rescue
beachmark = 'timeout'
end
File.open( Time.now.strftime('d:/logs/smtp/%Y_%m_%d.smtp'), File::APPEND | File::CREAT | File::WRONLY ){ |f|
f.puts( "#{sprintf( '%02d',Time.now.hour )} | #{beachmark} | #{request}" )
}</code>
Все бы хорошо, но вот <tt>beachmark</tt> хотелось бы считать "по-взрослому", а именно при помощи пакета <tt>beachmark</tt>. И снова переписываем код:
<code>require 'timeout'
require 'socket'
<u>require 'benchmark'</u>
request = ""
<u>beachmark = ""</u>
begin
<u>beachmark = Benchmark.measure{ </u>
timeout(1){
t = TCPSocket.open('mail.scli.ru', 'smtp')
t.puts('NOOP')
request = t.gets.chomp
t.close
}
<u>}.to_s</u>
rescue
beachmark = 'timeout'
end
File.open( PATH_LOG_SMTP + Time.now.strftime('d:/logs/smtp/%Y_%m_%d.smtp'), File::APPEND | File::CREAT | File::WRONLY ){ |f|
f.puts( "#{sprintf( '%02d',Time.now.hour )} | #{beachmark} | #{request}" )
}</code>
И тут мы замечаем, что Beachmark очень многословен. Он выдает информацию в виде:
<tt>
user system total ( real )
</tt>
Нам нужен только <tt>real</tt>. Все остальное — для более детального анализа. Поэтому немного доработаем результат блока <tt>measure</tt>:
<source lang=ruby>beachmark = Benchmark.measure{
timeout(1){
t = TCPSocket.open('mail.scli.ru', 'smtp')
t.puts('NOOP')
request = t.gets.chomp
t.close
}
}.real</source>
Или можно просто вместо <tt>Benchmark.measure</tt> использовать <tt>Benchmark.realtime</tt>. Теперь надо бы разделить ошибки по таймауту и ошибки соединения. Для этого надо добавить лишь еще один блок <code>;rescue</code>. Помимо этого мне не понадобится все сообщение от службы. Мне и кода сообщения достаточно. Итак, смотрим, что получилось:
<code>require 'socket'
require 'benchmark'
require 'timeout'
request = ""
beachmark = ""
begin
timeout(1){
beachmark = Benchmark.measure{
request = TCPSocket.open('mail.scli.ru', 'smtp'){ |t|
t.puts('NOOP')
t.gets.chomp
}[0..2]
}.real
}
rescue <u>Timeout::Error </u>
beachmark = 'timeout'
rescue
beachmark = ' '
<u>request = 'error'</u>
end
File.open( Time.now.strftime('d:/logs/smtp/%Y_%m_%d.smtp'), File::APPEND | File::CREAT | File::WRONLY ){ |f|
f.puts( "#{sprintf( '%02d',Time.now.hour )} | #{beachmark} | #{request}" )
}</code>
А теперь вопрос: как переписать программу так, чтобы она могла тестировать не только [[w:SMTP|SMTP]], но и [[w:HTTP|HTTP]], [[w:FTP|FTP]], [[w:POP3|POP3]] и т.д? Но это уже для самостоятельного изучения.
 
=== Датчик обновления сайта ===
 
Ни для кого не секрет, что [[w:Сетевой администратор|админы]] — это жутко ленивый народ. Но начальство, как назло, не хочет платить деньги за просто так. Ему нужны отчеты! Представляю на Ваш суд датчик обновления новостной ленты сайта. Для начала следует как следует поставить себе задачу:
* Есть сайт и админ его частенько обновляет.
* Будем рассматривать только случай добавления новостей. Все остальные разделы можно контролировать аналогично.
* В качестве тестового сайта выберем [http://www.minjust.ru/ http://www.minjust.ru]. Но, с небольшой доработкой, программа может быть адаптирована и для любого другого сайта.
* Обновление сайта просходит примерно 1-2 раза в день. Не больше, но может быть и меньше (это зависит от ЦОС [[w:Министерство юстиции России|Минюста РФ]]). Для более частого обновления придётся существенно дорабатывать программу.
* Новости располагаются на первой странице сайта.
 
Итак, для чего нам нужен датчик? Допустим Вы админ сайта и постоянно добавляете новости. По окончании недели (месяца, года, столетия, ...) от Вас требуют отчет о проделанной работе. Вам приходится заходить на сайт и смотреть те новости, которые Вы добавили за последний период. Муторно и неэффективно. Намного приятнее постоянно вести записи о добавленных новостях (при помощи программы, конечно) и по завершении периода просто сделать соотвествующую выборку.
 
Итак, немного об алгоритме программы... Обычно новостей на главной странице строго определенное количество. На нашем тестовом сайте из ровно пять. У нас есть файл в котором мы храним дату добавления новости и заголовок новости. Разделитель у нас может быть произвольным, но в качестве примера будет использован набор символов <code>' ^_^ '</code>. Вообще для данной задачи даже разделитель не очень-то и нужен (дата состоит из строго определенного количества символов и записывается в строго определенном формате)... но универсальность превыше всего!
 
Каждый раз при запуске программы, мы скачиваем заглавную страницу сайта и выдираем оттуда даты и заголовки новостей. Потом читаем файл с подобным же набором даных. Читаем весь файл, хотя можно читать только последние n-строк. Но мы будем создавать каждый месяц новый файл и поэтому особой загрузки памяти происходить не должно. Далее сравниваем эти два набора данных и с помощью пересечения и вычитания множеств мы получаем те данные, которых до сих пор нет в файле. Как раз эти данные мы и добавляем в конец файла. ВСЕ! Просто и лаконично. Теперь код, который выполняет поставленную задачу:
<source lang="ruby">require 'net/http'
h = Net::HTTP.new( 'www.minjust.ru', 80 )
resp, data = h.get( '/index.html' )
dates = data.scan(/<DIV ALIGN="RIGHT" STYLE="font-size : x-small;">(d{4}-d{2}-d{2})</div>/).map{ |ar| ar[0] }
texts = data.scan(/<DIV CLASS="TITLE2">(.*?)</div>/m ).map{ |ar|
ar[0].gsub("n",' ').gsub("r"," ").gsub(' '*2, ' ').strip
}
File.open( Time.now.strftime('log/%Y_%m.log'), File::APPEND | File::CREAT | File::RDWR ){ |f|
from_inet = (0...5).inject([]){ |result, i| result + [ dates[i] + ' ^_^ ' + texts[i] ] }
from_file = f.readlines.map{ |str| str.chomp }.compact
f.puts( ( from_inet - (from_file & from_inet) ).sort )
}</source>
В результате мы получаем файлы, которые могут быть с легкостью использованы для составления отчетов. Будут предложения по оптимизации — пишите на форум.
 
=== Приемо-передача файлов ===
 
===Как скачать HTML-страницу===
Для скачивания [[w:HTML|HTML]]-страниц обычно используется [[w:HTTP|протокол HTTP]]. Адрес страницы задается в виде [[w:URL|URL]]. В Руби существует несколько способов скачать страницу по протоколу HTTP. Начнем с самого древнего – класса Net::HTTP (древнее только Socket'ы, но про них мы умолчим). Для определенности мы будем скачивать этот учебник и сохранять его в виде файла RubyBook.html.
 
<code>require 'net/http'
Net::HTTP.open(<u>'ru.wikibooks.org'</u>){ |http|
File.open('RubyBook.html','w'){ |file|
file.write http.get(<u>'/wiki/Ruby'</u>).body
}
}</code>
 
Недостатком использования класса Net::HTTP является то, что URL разбивается на две части: адрес сервера (<code>ru.wikibooks.org</code>) и адрес страницы (<code>/wiki/Ruby</code>). Такое разбиение удобно только в том случае, когда необходимо скачивать несколько HTML-страниц с одного сервера. А вот как раз такие ситуации возникают крайне редко. Чаще приходится запрашивать страницы с различных серверов. Для этих целей и был придуман метод <code>open</code> из библиотеки <code>open-[[w:URI|uri]]</code>. Он самостоятельно разбирает URL и производит нужный запрос (не только HTTP).
 
<code>require 'open-uri'
File.open('RubyBook.html','w'){ |file|
file.write open( '<u>http://ru.wikibooks.org/wiki/Ruby</u>' ).read
}</code>
 
{{Внимание|Обратите внимание, что необходимо указывать полный URL с указанием протокола (в данном случае <nowiki>http://</nowiki>)}}
 
====Запрос заголовка====
Во время скачивания передается не только сама страница (<b>тело сообщения</b> или на англ. body), но и техническая информация (<b>заголовок</b> или на англ. head). Мы еще не раз будем свидетелями такого поведения в протоколах сети Интернет. В заголовке содержится информация об успешности запроса (код возврата), типе передаваемых данных, кодировке, HTTP-сервере и так далее. Для примера, мы будем производить HTTP-запрос и получать ответ только в виде заголовка. Запись заголовка в файл производить не будем, так как в реальной практике этот прием практически не используется. Сначала «потренируемся на кошках», то есть на классе Net::HTTP:
 
<code>require 'net/http'
Net::HTTP.open('ru.wikibooks.org'){ |http|
<u>p</u> http<u>.head</u>('/wiki/Ruby')
}</code>
 
Как и обещалось ранее, вывод заголовка мы делаем на экран. А запрос заголовка, вместо тела сообщения, осуществляется простой заменой метода <code>.get</code> на метод <code>.head</code>. А как тогда получить заголовок и тело сообщения одновременно? Очень просто:
 
<code>require 'net/http'
Net::HTTP.open('ru.wikibooks.org'){ |http|
<u>head, body</u> = http.get('/wiki/Ruby')
}</code>
 
Для этого достаточно присвоить результат метода <code>.get</code> двум переменным. Произойдет присвоение списков и в первую переменную попадет заголовок, а во вторую тело сообщения.
 
Теперь рассмотрим, как выглядит чтение заголовков в методе <code>open</code> библиотеки <code>open-uri</code>:
 
<code>require 'open-uri'
p open( 'http://ru.wikibooks.org/wiki/Ruby' )<u>.meta</u></code>
 
Замена метода <code>.read</code> на метод <code>.meta</code> позволяет нам дотянуться до заголовка. Заголовок имеет вид ассоциативного массива (объект класса Hash), где ключем является имя параметра (по стандарту MIME), а значением – значение параметра. Читать одновременно заголовок и тело сообщения можно вот так:
 
<code>require 'open-uri'
open( 'http://ru.wikibooks.org/wiki/Ruby/' ){ |f|
p f.meta
p f.read
}</code>
 
Мы использовали свойство метода <code>.open</code> прикреплять к себе блок.
 
====Работа через прокси====
Прокси-сервер – это сервер, перенаправляющий запросы другим серверам. Обычно таковой используется для скрытия истинного [[w:IP-адрес|IP-адрес]]а или для контроля за [[w:трафик|трафик]]ом.
 
Необходимость HTTP-запроса через прокси возникает, когда соединение с целевым сервером напрямую невозможно (например, администратор сети посчитал, что соединение посредством шлюза дает ему слишком мало возможностей контроля за трафиком). Использование подобного запроса маловероятно, но необходимо знать о возможности посылки http-запроса через прокси. Начнем с класса Net::HTTP::Proxy:
 
<code>require 'net/http'
Net::HTTP<u>::Proxy('you.proxy.host',8808)</u>.start('ru.wikibooks.org'){ |http|
p http.get('/wiki/Ruby').body
}</code>
 
Добавив всего лишь небольшой фрагмент кода мы получили работу через прокси. В нашем случае использовался прокси-сервер с адресом <code>you.proxy.host</code>, который предоставляет прокси-доступ через порт <code>8808</code>.
 
Теперь посмотрим, как эта же самая функциональность можеть быть реализована с использованием метода <code>open</code> (из библиотеки <code>open-uri</code>).
<code>require 'open-uri'
p open( 'http://ru.wikibooks.org/wiki/Ruby',<u>:proxy => 'http://you.proxy.host:8808/'</u>).read</code>
 
Добавился второй параметр (ассоциативный массив с единственной парой) в вызове метода <code>open</code>. К слову сказать, во втором параметре можно указать множество параметров запроса (например, название браузера).
{{info|Реализация соединения через прокси у метода <code>open</code> более удобна, так как в зависимости от внешних факторов можно соединяться как через прокси, так и напрямую. В случае Net::HTTP и Net::HTTP::Proxy используются два разных класса и подобные трюки затруднительны (хотя и возможны).}}
 
=== Вам письмо или You have got mail ===
=== Запускаем свой веб-сервер ===
Для того, чтобы создать простейший [[w:Веб-сервер|веб-сервер]] не нужно реализовывать протокол [[w:HTTP|HTTP]] на уровне [[w:Сокет (программный интерфейс)|сокетов]]. Достаточно знать, какую библиотеку использовать. В нашем случае это будет библиотека [[w:WEBrick|WEBrick]] (http://www.webrick.org), которая уже включена в дистрибутив Руби. Возможности библиотеки настолько широкие, что для их описания потребуется создание отдельного учебника. Мы рассмотрим лишь часть из них, зато на реальном жизненном примере.
 
{{info|Библиотека <tt>WEBrick</tt> (переводится как «куча паутины») отнюдь не лучшая реализация библиотеки для построения веб-сервера. Сейчас набирает обороты библиотека [http://mongrel.rubyforge.org/ Mongrel] (переводится как «полукровка», что указывает на смешанную структуру библиотеки. В ней критичные участки кода реализованы не на Руби, а на [[w:Си (язык программирования)|Си]]. Это в лучшую сторону сказалось на скорости работы данного [[w:Симбиоз|симбиоза]]), которая имеет гораздо лучшую реализацию, но для ее использования нужно скачивать и устанавливать библиотечные файлы, так как в дистрибутив она пока не включена. Именно поэтому построение нашего первого веб-сервера будет проходить на базе <tt>WEBrick</tt>}}
 
Секретарша начальника Управления производственного планирования [[w:Завод имени Лихачёва|ЗИЛ]] попросила меня реализовать программу, которая облегчала бы ей процесс создания служебных записок. В частности, требовалось по кодам подразделений выставлять фамилии и должность начальников этих подразделений. Без программы это сущая морока, особенно, если необходимо отправлять «служебку» необходимо в несколько подразделений. У меня не было особенного настроения, но я решил сделать два дела одновременно: заслужить благодарность секретарши и попутно продемонстрировать процесс создания web-серверного приложения. Благодарность я уже получил, поэтому приступаю ко второй части.
 
Для того, чтобы проверить работает ли библиотека <tt>WEBrick</tt> я выполнил следующий код:
 
<code>require <u>'webrick'</u>
WEBrick::HTTPServer.new( <u>:DocumentRoot => 'public_html'</u>, <u>:Port => 8080</u> ).start</code>
 
После успешного запуска сервера, я запустил [[w:Mozilla Firefox|Firefox]] и набрал адрес: <tt>http://localhost:8080/</tt>. И тут я узнал, что директория <tt>public_html</tt> пуста. Горевал я по этому поводу не долго и быстро создал файл <tt>public_html/index.html</tt> примерно следующего содержания:
 
<code><nowiki><html><body><h1>Сервер работает!</h1></body></html></nowiki></code>
 
После обновления страницы на экране красовалась крупная надпись: "Сервер работает!" Все! Мы с вами написали свой первый веб-сервер.
 
{{Внимание|Для того, чтобы прекратить выполнение сервера нужно нажать комбинацию клавиш Ctrl+Break или закрыть окно терминала. Для данной реализации веб-сервера это единственный способ прекратить его работу}}
 
Настало время разобраться в том, как это все работает:
* <code>require '<u>webrick</u>'</code>
: Подключение библиотеки <tt>WEBrick</tt>. Ничего интересного и неожиданного здесь не произошло.
* <code>WEBrick::HTTPServer.new( <u>:DocumentRoot => 'public_html'</u>, <u>:Port => 8080</u> ).start</code>
:* Библиотека <tt>WEBrick</tt> реализована в виде модуля с одноименным названием, поэтому до любого класса приходится «дотягиваться» через префикс: <code>WEBrick::</code>.
:* <code>HTTPServer</code> — это название класса, который используется для создания веб-серверов.
:* Метод <code>.new</code> создает экземпляр класса <tt>WEBrick::HTTPServer</tt>. Методу передается два параметра (на самом деле — один ассоциативный массив с двумя парами):
::* <code>:DocumentRoot => '<u>public_html</u>'</code> указывает на то, что веб-сервер будет просматривать директорию <tt>./public_html</tt> для выдачи [[w:HTML|html]]-страниц (и не только) по запросу. Если этот параметр не указывать, то сервер просто не будет работать.
::* <code>:Port => <u>8080</u></code> задает порт <tt>8080</tt> для «прослушивания» сервером. Если этот параметр не указывать, то сервер будет прослушивать порт <tt>80</tt> (стандарт для веб-серверов). Но случилось так, что «слушать» <tt>80</tt>-порт он у меня отказался, так как там «обосновался» [[w:Skype|Skype]]. Вот и пришлось в срочном порядке придумывать новый. Ничего умнее, как продублировать число <tt>80</tt> мне в голову не пришло. Если фантазия у вас развита лучше, то придумайте любой другой порт.
:* Метод <code>.start</code> запускает сервер. В отличии от класса <tt>GServer</tt>, дополнительные манипуляции с методами <code>.sleep</code> и <code>loop</code> не требуются.
{{info|Очень часто необходимо осуществить передачу данных между компьютерами по сети. Для этого вполне подойдет <tt>WEBrick</tt>. Быстро напишите веб-сервер, который будет просматривать нужную директорию. Теперь, для вас это больше не проблема!}}
 
Но вернемся к заказанной мне программе. Она будет состоять из двух частей: обычная <tt>html</tt>-страница (<tt>index.html</tt>) и [[w:Сервлет|сервлет]] (<tt>/input</tt>). Страница <tt>public_html/index.html</tt> будет содержать форму в которую будут вводится исходные данные. Ее задачей будет формирование запроса для сервлета. Сервлет <tt>/input</tt> будет получать номера подразделений, а выдавать их названия и ФИО действующих начальников. Вот код <tt>public_html/index.html</tt>:
 
<source lang="html4strict"><nowiki><HTML><BODY><form </nowiki><u>action='/input' method='post'</u><nowiki>><div align=center>
<table border=0 bgcolor=#000000 width=350><tr><td><table border=0 bgcolor=#CCCCFF width=100%>
<tr><td align=right>Лит-1 - 01</td><td><INPUT </nowiki><u>TYPE="checkbox" NAME="check_01"</u><nowiki>></td>
<td><INPUT TYPE="checkbox" NAME="check_02"></td><td>02 - ГЛЦЧ</td></tr>
<tr><td align=right>ГКЦ - 04</td><td><INPUT TYPE="checkbox" NAME="check_04"></td>
<td><INPUT TYPE="checkbox" NAME="check_05"></td><td>05 - Прессовый</td></tr>
<tr><td align=right>РПЦ - 06</td><td><INPUT TYPE="checkbox" NAME="check_06"></td>
<td><INPUT TYPE="checkbox" NAME="check_09"></td><td>09 - Моторный</td></tr>
<tr><td align=right>Кузовной - 10</td><td><INPUT TYPE="checkbox" NAME="check_10"></td>
<td><INPUT TYPE="checkbox" NAME="check_11"></td><td>11 - ПСК</td></tr>
<tr><td align=right>МСК-1 - 12</td><td><INPUT TYPE="checkbox" NAME="check_12"></td>
<td><INPUT TYPE="checkbox" NAME="check_14"></td><td>14 - ПОиСА</td></tr>
<tr><td align=right>МСЦ-3 - 17</td><td><INPUT TYPE="checkbox" NAME="check_17"></td>
<td><INPUT TYPE="checkbox" NAME="check_18"></td><td>18 - МСЦ-2</td></tr>
<tr><td align=right>Нормаль - 19</td><td><INPUT TYPE="checkbox" NAME="check_19"></td>
<td><INPUT TYPE="checkbox" NAME="check_20"></td><td>20 - Арматурный</td></tr>
<tr><td align=right>АСК - 21</td><td><INPUT TYPE="checkbox" NAME="check_21"></td>
<td><INPUT TYPE="checkbox" NAME="check_22"></td><td>22 - Термический</td></tr>
<tr><td align=right>РААЗ - 25</td><td><INPUT TYPE="checkbox" NAME="check_25"></td>
<td><INPUT TYPE="checkbox" NAME="check_27"></td><td>27 - МЗАА</td></tr>
<tr><td align=right>УО - 30</td><td><INPUT TYPE="checkbox" NAME="check_30"></td>
<td><INPUT TYPE="checkbox" NAME="check_34"></td><td>34 - ЗИЛтехоснастка</td></tr>
<tr><td align=right>УКЭР - 57</td><td><INPUT TYPE="checkbox" NAME="check_57"></td>
<td><INPUT TYPE="checkbox" NAME="check_58"></td><td>58 - ПенЗА</td></tr>
<tr><td align=right>СААЗ - 61</td><td><INPUT TYPE="checkbox" NAME="check_61"></td>
<td><INPUT TYPE="checkbox" NAME="check_62"></td><td>62 - ТУ</td></tr>
<tr><td align=right>УКК - 64</td><td><INPUT TYPE="checkbox" NAME="check_64"></td>
<td><INPUT TYPE="checkbox" NAME="check_67"></td><td>67 - УМТС</td></tr>
<tr><td align=right>УСК - 74</td><td><INPUT TYPE="checkbox" NAME="check_74"></td>
<td><INPUT TYPE="checkbox" NAME="check_76"></td><td>76 - ЦИТ</td></tr>
<tr><td align=right>УСХ - 81</td><td><INPUT TYPE="checkbox" NAME="check_81"></td>
<td><INPUT TYPE="checkbox" NAME="check_82"></td><td>82 - ПЗА</td></tr>
<tr><td align=right>РЗАА - 85</td><td><INPUT TYPE="checkbox" NAME="check_85"></td>
<td><INPUT TYPE="checkbox" NAME="check_"></td><td>&nbsp;</td></tr>
<tr><td colspan=4 align=center><INPUT TYPE="submit" NAME="Сформировать шапку"></td></tr>
<tr><td align=right>Рассказов</td><td><INPUT TYPE="checkbox" NAME="check_001"></td>
<td><INPUT TYPE="checkbox" NAME="check_002"></td><td>Коновалова</td></tr>
<tr><td align=right>Принцев</td><td><INPUT TYPE="checkbox" NAME="check_003"></td>
<td><INPUT TYPE="checkbox" NAME="check_004"></td><td>Сорокин</td></tr>
<tr><td align=right>Журавлев</td><td><INPUT TYPE="checkbox" NAME="check_005"></td>
<td><INPUT TYPE="checkbox" NAME="check_006"></td><td>Корабельников</td></tr>
<tr><td align=right>Фет</td><td><INPUT TYPE="checkbox" NAME="check_007"></td>
<td><INPUT TYPE="checkbox" NAME="check_008"></td><td>Ярков</td></tr>
<tr><td align=right>Болотин</td><td><INPUT TYPE="checkbox" NAME="check_009"></td>
<td><INPUT TYPE="checkbox" NAME="check_010"></td><td>Шрамов</td></tr>
<tr><td align=right>Поленов</td><td><INPUT TYPE="checkbox" NAME="check_011"></td>
<td><INPUT TYPE="checkbox" NAME="check_012"></td><td>Копылов</td></tr>
</table></td></tr></table></div></form></BODY></HTML></nowiki></source>
 
{{info|Geka, когда увидел этот код, воскликнул: "И не лень было такой код писать?" На что я ему резонно ответил, что этот код является результатом программы-генератора, которую я не привожу здесь, чтобы не отвлекать внимание читателя от более интересной темы}}
 
Хотя код и довольно объемный, но я решил поместить его целиком, чтобы программа у вас работала точно также, как и у меня. Первое, на что следует обратить внимание, это параметры [[w:Тег (языки разметки)|тега]] <tt>FORM</tt>. Именно он занимается тем, что подготавливает данные для сервлета и переправляет их ему.
* <code>action='<u>/input</u>'</code>
: Адрес, по которому будут передаваться данные. Это может быть как [[w:CGI|CGI]]-приложение, так и сервлет. В нашем случае это сервлет <tt>/input</tt>.
* <code>method='post'</code>
: В протоколе [[w:HTTP|HTTP]] используется два метода передачи данных: POST и GET. Отличаются они тем, что POST не отображает передаваемые данные в адресной строке браузера. Возьмите за правило использовать метод POST.
Внутри тега <tt>FORM</tt> присутствует великое множество тегов <tt>INPUT</tt>, которые как раз и формируют данные. Каждый из тегов <tt>INPUT</tt> имеет два параметра: <tt>NAME</tt> и <tt>TYPE</tt>.
* <code>TYPE="<u>checkbox</u>"</code>
: Параметр <tt>TYPE</tt> указывает на тип формируемых данных. В данном случае это <tt>checkbox</tt> (переключатель). Он имеет два значения: "on" (включен) и "off" (выключен). Значение "off" обычно не передается. Получается своеобразный [[w:Булевский тип|булевский тип]].
* <code>NAME="<u>check_</u>01"</code>
: Все данные передаются в виде пары <code>имя=значение</code>. Параметром <tt>NAME</tt> задается имя. Значение же задается пользователем.
 
Вот как выглядит страница <tt>index.html</tt> после обработки браузером (<tt>Firefox</tt>):
[[Изображение:RubyBooks_Index_html.png|center|Вид страницы public_html/index.html в Firefox]]
Теперь рассмотрим серверную часть программы (сервлет <tt>/input</tt>), которая будет обрабатывать запросы (сформированные файлом <tt>public/index.html</tt>). Сервлет является частью сервера. Поэтому листинг сервера будет одновременно и листингом сервлета.
 
<source lang="ruby"><u>bosses = </u>{
'check_01' => "\tНачальнику Лит-1\tБокову Ю.В.",
'check_02' => "\tНачальнику ГЛЦЧ\tНазарову А.В.",
'check_04' => "\tНачальнику ГКЦ\tЛасунину Б.Д.",
'check_05' => "\tНачальнику Прессового корпуса\tЯшнову Ю.М.",
'check_06' => "\tНачальнику РПЦ\tШепелеву Е.И.",
'check_09' => "\tДиректору МАП\tБагатурии Р.С.",
'check_10' => "\tНачальнику Кузовного корпуса\tАшмарину А.Г.",
'check_11' => "\tНачальнику ПСК\tАнаньву А.С.",
'check_12' => "\tНачальнику МСК-1\tМиролюбову В.П.",
'check_14' => "\tДиректору ПОиСА\tСаттарову М.Д.",
'check_17' => "\tНачальнику МСЦ-3\tБородуле П.Н.",
'check_18' => "\tНачальнику МСЦ-2\tГрищенкову А.И.",
'check_19' => "\tНачальнику цеха \"Нормаль\"\tАфонину А.Н.",
'check_20' => "\tНачальнику Арматурного цеха\tДавыдову В.И.",
'check_21' => "\tНачальнику АСК\tБорисюку В.Д.",
'check_22' => "\tНачальнику Термического цеха\tВерташову Н.А.",
'check_25' => "\tДиректору ЗАО РААЗ\tСавчуку В.Ф.",
'check_27' => "\tДиректору ЗАО МЗАА\tСоловьеву Н.И.",
'check_30' => "\tНачальнику УО\tЮру А.Е.",
'check_34' => "\tДиректору ООО \"ЗИЛтехоснастка\"\tТимофееву Г.П.",
'check_57' => "\tГлавному конструктору АМО ЗИЛ-начальнику УКЭР\tРыбину Е.Л.",
'check_58' => "\tДиректору ЗАО ПенЗА\tГудкову В.И.",
'check_61' => "\tДиректору ЗАО СААЗ\tНовикову В.А.",
'check_64' => "\tНачальнику УКК\tМинину Д.С.",
'check_62' => "\tНачальнику ТУ\tУстинкину В.В.",
'check_67' => "\tНачальнику УМТС\tМелешкину В.Д.",
'check_74' => "\tНачальнику УСК\tТарабрину В.В.",
'check_76' => "\tДиректору ЦИТ\tИгнатьеву В.П.",
'check_81' => "\tНачальнику УСХ\tТурчину Н.В.",
'check_82' => "\tДиректору ЗАО ПЗА\tПлешакову И.В.",
'check_85' => "\tДиректору ОАО РЗАА\tДобрынину Ю.Г.",
'check_001' => "\tНачальнику УК\tРассказову А.А.",
'check_002' => "\tНачальнику ЭУ\tКоноваловой С.Н.",
'check_003' => "\tИсполнительному директору АМО ЗИЛ\tПринцеву И.В.",
'check_004' => "\tДиректору по экономике и финансам АМО ЗИЛ\tСорокину А.В.",
'check_005' => "\tДиректору по производству АМО ЗИЛ\tЖуравлеву В.С.",
'check_006' => "\tКоммерческому директору АМО ЗИЛ\tКорабельникову Е.В.",
'check_007' => "\tДиректору по дочерним и зависимым обществам\tФету О.Л.",
'check_008' => "\tГлавному инженеру АМО ЗИЛ\tЯркову Г.А.",
'check_009' => "\tДиректору по качеству АМО ЗИЛ\tБолотину Ю.М.",
'check_010' => "\tДиректору ЗАО \"Торговый дом ЗИЛ\"\tШрамову В.П.",
'check_011' => "\tДиректору ООО ВТФ \"ЗИЛ-экспорт\"\tПоленову А.Ю.",
'check_012' => "\tДиректору Прессового производства\tКопылову Ю.П."
}
require 'webrick'
server = WEBrick::HTTPServer.new( :Port => 8080 )
server.mount_proc('/'){ |req,resp| resp.body = IO.read('public_html/index.html') }
server.mount_proc('/input'){ |req,resp|
resp.body = %!<nowiki><html><body><div align=center><form action='/' method='post'>
<textarea rows='5' cols='60'>#{req.query.map{ |key,value| bosses[ key ] }.compact.join("\n") }
</textarea><br /><input type='submit' value='Повторим?'></form></div></body></html></nowiki>!
}
server.start</source>
 
Начинаем разбираться с кодом веб-сервера:
* <code>bosses = { ... }</code>
: Создается ассоциативный массив, который будет использоваться при обработке данных. Ключем является имя «переключателя», а значением — строка, на которую надо этот ключ заменить. Путем такой замены мы будем формировать результат.
* <code>require 'webrick'</code>
: Подключаем библиотеку <tt>WEBrick</tt>. По-хорошему ее надо было подключать перед инициализацией ассоциативного массива. Но не хотелось разбивать на части код, относящийся к реализации веб-сервера.
* <code><u>server = </u>WEBrick::HTTPServer.new( :Port => 8080 )</code>
: Создаем экземпляр класса WEBrick::HTTPServer. На этот раз мы его сохраняем в переменную <code>server</code>. Это необходимо для подключения сервлетов. Параметр <code>:DocumentRoot</code> мы не указываем, так как файл <tt>public_html/index.html</tt> мы тоже сделаем сервлетом. Поэтому подключать для этого всю папку <tt>public_html</tt> просто глупо.
* <code>server<u>.mount_proc('/')</u>{ |<u>req,resp</u>| <u>resp.body = </u>IO.read('<u>public_html/index.html</u>') }</code>
:* При помощи метода <code>.mount_proc('/')</code> мы закрепили сервлет на корневую директорию, то есть при запросе http://localhost:8080/ будет вызываться именно он.
:* Параметры <code>req</code> и <code>resp</code> означают запрос сервлету (<code>req</code>) и ответ сервлета (<code>resp</code>), то есть сервлет получает переменную <code>req</code> и в качестве результата должен сформировать переменную <code>resp</code> (<code>resp.body = </code>).
:* В качестве результата сервлет возвращает содержимое файла <tt>public_html/index.html</tt>. По хорошему, надо было бы считать этот файл при запуске сервера, чтобы сэкономить время обработки сервлета.
* <code>server.mount_proc('<u>/input</u>'){ |req,resp| ... }</code>
: Таким образом мы создаем сервлет <tt>/input</tt>, который будет обрабатывать запросы. Его тело мы рассмотрим в несколько заходов:
:* При помощи <code>resp.body = ...</code> мы формируем выходные данные (строка с HTML-кодом).
:* HTML-код: <code><nowiki><html><body><div align=center><form action='/' method='post'><textarea rows='5' cols='60'></nowiki><u>#{..}</u><nowiki></textarea><br /><input type='submit' value='Повторим?'></form></div></body></html></nowiki></code>; просто создает окружения для наших выходных данных. В частности видно, что наши выходные данные будут помещаться в тег <tt>TEXTAREA</tt>. Это сделано для того, чтобы удобней было копировать данные. Вот примерно так это будет выглядеть в браузере:
[[Изображение:RubyBooks_Input.png|center|Пример работы сервлета /input]]
:* Код обработки данных: <code>req.query.map{ |key,value| bosses[ key ] }.compact.join("\n")</code>; получает список передаваемых параметров в виде ассоциативного массива (метод <code>.query</code>) и заменяет имена переменных на значения из ассоциативного массива <code>bosses</code>. Далее идет преобразование в строку при помощи метода <code>.join("\n")</code>.
* <code>server.start</code>
: Запуск сервера.
 
 
 
Вот почти такую программу я и презентовал секретарше Анюте. «Почти» потому, что фамилии должны идти строго в определенном порядке (чего нельзя добиться в ассоциативном массиве), но для того, чтобы посмотреть реализацию веб-сервера — продемонстрированной версии программы должно быть достаточно. Смело меняйте код и создавайте свои веб-сервера!
 
====Сервлетки в рубиновом соусе====
Мы уже неоднократно упоминали понятие [[w:Сервлет|сервлет]], но особенно на нем не останавливались. Давайте сейчас рассмотрим типовые сервлеты, которые имеются в стандартной поставке <tt>WEBrick</tt>
* '''Файловый сервлет'''
: Реализует взаимосвязь запроса с реальным файлом (или директорией). В самом первом примере мы использовали файловый сервлет, когда передавали <code>:DocumentRoot => 'public_html'</code> в качестве параметра методу <code>.new</code>. Это было равнозначно созданию файлового сервлета на корневой директории веб-сервера. Функциональность файлового сервлета описана в классе WEBrick::HTTPServlet::FileHandler.
* '''CGI-сервлет'''
: Реализует взаимосвязь запроса с <tt>CGI</tt>-приложением, то есть считывается первая строка и вытаскивается оттуда путь к интерпретатору. Далее производится запуск интерпретатора, которому параметром передается вызванный файл. Строка для <tt>CGI</tt>-приложений на Ruby будет выглядеть приемерно так:
::* <code>#!<u>/usr/bin/ruby</u></code>
::: для операционных систем семейства Unix
::* <code>#!<u>c:/ruby/bin/ruby.exe</u></code>
::: для операционных систем семейства Windows
: Кстати, я столкнулся с тем, что под Windows CGI-сервлеты отказывались работать. Это связано с тем, что <tt>WEBrick</tt> считает, что для того, чтобы запустить скрипт достаточно просто его вызывать (<tt>./sctipt_name</tt>) и он сам запустится. Понятное дело, что в Windows такое работать не будет. Поэтому мне пришлось немножко переписать часть библиотеки WEBrick, которая отвечает за запуск CGI-программ. Для того, чтобы мои изменение стали доступны и вам, я написал небольшую программку, которая устанавливает себя куда надо:
<source lang=ruby>File.open(Config::CONFIG['rubylibdir']+'/webrick/httpservlet/cgi_runner.rb','w'){|file|
file.puts IO.read($0).split('#'*10).last
}
exit
##########
#
# cgi_runner.rb -- CGI launcher.
#
# Author: IPR -- Internet Programming with Ruby -- writers
# Copyright (c) 2000 TAKAHASHI Masayoshi, GOTOU YUUZOU
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
# reserved.
# Copyright (c) 2007 Rubynovich
#
# $IPR: cgi_runner.rb,v 2.0 2007/02/23 18:53:15 Rubynovich Exp $
 
STDIN.binmode
STDOUT.reopen(open(STDIN.sysread(STDIN.sysread(8).to_i), "w"))
STDERR.reopen(open(STDIN.sysread(STDIN.sysread(8).to_i), "w"))
 
ENV.clear
ENV.update( Marshal.restore(STDIN.sysread(STDIN.sysread(8).to_i)) )
 
if IO.read(ENV["SCRIPT_FILENAME"],50) =~ /^#!(.*)$/
exec($1,ENV["SCRIPT_FILENAME"])
else
exec(ENV["SCRIPT_FILENAME"])
end</source>
: Немного поясню. Первая строчка -- это программа, которая записывает нижеследующий код в папку с библиотеками.
: Функциональность CGI-сервлета описана в классе WEBrick::HTTPServlet::CGIHandler
* '''ERB-сервлет'''
: Реализует взаимосвязь запроса с ERB-шаблонами, в которые вставляются предварительно подготовленные данные.
* '''Процедурный сервлет'''
: Этим сервлетом мы уже пользовались, когда писали <code>.mount_proc</code>. Он обеспечивает взаимосвязь запроса и процедуры обработки (описанной внутри блока метода <code>.mount_proc</code>).
 
Для того, чтобы было проще пользоваться всеми видами сервлетов WEBrick я написал небольшую программку, которая создает метод mount_file (для файлового сервлета), mount_erb (для ERB-сервлета) и mount_cgi (для CGI-сервлета). Как вы могли заметить mount_proc уже существует (собственно, его название и послужило прототипом для остальных). Вот эта программа:
 
<source lang=ruby>require 'webrick'
module WEBrick
class HTTPServer
['ERB','CGI','File'].each{ |handler|
class_eval( "def mount_#{handler.downcase}(point,file)\nmount(point,HTTPServlet::#{handler}Handler,file)\nend" )
}
end
end</source>
 
Теперь можно использовать эти сервлеты следующим образом:
 
<source lang=ruby>require 'webrick'
module WEBrick
class HTTPServer
['ERB','CGI','File'].each{ |handler|
class_eval( "def mount_#{handler.downcase}(point,file)\nmount(point,HTTPServlet::#{handler}Handler,file)\nend" )
}
end
end
 
server = WEBrick::HTTPServer.new( :Port => 8090 )
server.mount_file('/','html')
server.mount_cgi('/hello','cgi-bin/hello.exe' )
server.mount_cgi('/hello.cgi','cgi-bin/hello.rb' )
server.start</source>
 
Ну вот вроде и все, что можно сказать по сервлетам.
 
==Приложения==
;[[/Задачник]]:Сборник задач
;[[/Справочник]]:Справочник по базовым классам
;[[/Лицензия]]:Перевод Лицензии Руби
;[[/Жаргон]]:Придумываем слова с рубинами и рельсами
;[[/Фольклор]]:Народное творчество любителей Руби
;[[/Избранное с RubyNews]]:К разграблению. Полезные статьи перемещаются в основную часть учебника, затем это приложение удалим.
;[[/Идеология]]:программирования вообще и на Руби в частности
 
==Дальнейшее чтение==
=== Русскоязычные ресурсы ===
* [http://ror.ru Все о Ruby on Rails] Рускоязычный ресурс по Ruby.
* [http://ruby.inuse.ru RubyInUse] Территория общения русскоязычных рубистов.
* [http://rubyclub.com.ua/ Форум Ruby on rails]. Русскоязычный форум написанный и посвященный Ruby on Rails, здесь всегда отвечают на ваши вопросы, для зарегистрированных доступна тематическая PDF библиотека.
* [http://acm.mipt.ru/twiki/bin/view/Ruby/WebHome Учебные материалы МФТИ]. Коллекция учебных материалов по Ruby на сайте [[w:МФТИ|МФТИ]]
 
=== Русскоязычная литература ===
* [http://www.shokhirev.com/mikhail/ruby/ltp/title.html Learn To Program]. Перевод Михаила В. Шохирева.
* [http://www.linuxshare.ru/docs/devel/languages/ruby/ruby.html Ruby &mdash; руководство пользователя]. Перевод Александра Мячкова.
* [http://www.ruby.linux.by/man-ruby_lections.html Конспект лекций по высокоуровневому программированию]. Шипиев Р.Н.
* [http://sabanin.ru/wp-content/articles/Rolling_with_Ruby_on_Rails_rus_translation.pdf Rolling with Ruby on Rails]. Перевод Андрея Горбатова (pdf).
* [http://ruby.rostovlinux.ru/rubylinks.html Самоучитель Ruby]. Параллельный проект по написанию учебника.
* [http://www.main.msiu.ru/small-portals/programmers/library/ruby/faq/faq.html ЧАсто задаваемые ВОпросы]. (требуется авторизация) FAQ по Ruby.
* [http://www3.msiu.ru/~roganova/First_A_2005-2006/ruby_notes.pdf Программирование на Ruby]. Учебное пособие по языку Ruby, Роганова Н.А., Тузов А.С.
* [http://www3.msiu.ru/~roganova/First_A_2005-2006/index.html Лекции по программированию]. Роганова Н.А.
 
===Иноязычная литература===
* [http://poignantguide.net/ruby/ ''Why’s (Poignant) Guide to Ruby'']{{ref-en}} — эта книга достойна чтения, ''даже если Вам не нужно знание Руби''. Просто шедевр. Распространяется бесплатно. <small>Там ещё богатая подсветка текста; эх, нам бы такую.</small>. [[Why's Poignant Guide To Ruby RUS|Частичный перевод здесь]].
* ''Programming Ruby'' Дэйва Томаса{{ref-en}}. Многие пытались, но не перевели на русский. Первая редакция книги содержится в пакете [http://rubyinstaller.rubyforge.org/wiki/wiki.pl «Установка за один щелчок»] для Windows. Владельцы других [[w:Операционная система|ОС]] смогут найти её в Сети (например, в виде [http://www.rubycentral.com/book/ набора вебстраниц]). Вторую редакцию книги можно купить на [http://www.amazon.com/gp/product/0974514055/sr=8-1/qid=1148659457/ref=pd_bbs_1/102-3466370-6596946?%5Fencoding=UTF8 Amazon.com] в бумажном или электронном виде.
 
* [http://ruby.on-page.net Ruby.on-page.net]{{ref-en}} — самый простой справочник по Ruby
* [http://www.meshplex.org/wiki/Ruby/Ruby_on_Rails_programming_tutorials Full Ruby on Rails Tutorial]
 
Где-то на странице (обычно слева, снизу от меню) должен быть список ссылок на параллельные версии этого викиучебника на других языках. [[de:Ruby-Programmierung]] [[en:Programming:Ruby]] [[eo:Lernolibro_pri_Rubeno]] [[pl:Ruby]]
 
[[Категория:Языки программирования]]