Академический Документы
Профессиональный Документы
Культура Документы
Материал из Викиучебника.
Человек создан для творчества, и я всегда знал, что люблю творить. Увы, я обделён
талантом художника или музыканта. Зато умею писать программы.
Я хочу, чтобы компьютер был моим слугой, а не господином, поэтому я должен уметь
быстро и эффективно объяснить ему, что делать.
Йокихиро Мацумото
Программы должны быть написаны так, чтобы их могли читать люди, и лишь иногда так,
чтобы их могли выполнять машины.
Абельсон и Сассман
Содержание
1 Основные свойства Ruby
2 Начало работы
2.1 Первая программа
2.2 Полигон для испытания программ
2.2.1 fxri: полигон и справочник
2.2.2 Полигон в сети
2.3 Комментарии и демонстрация результата
2.4 Вывод на экран
2.5 Переменные
2.5.1 Переменные указывают на объект
3 Типы данных
3.1 Числа
3.2 Логический тип
3.2.1 Логические операции
3.2.2 Методы сравнения
3.3 Массивы
3.4 Строки
3.5 Ассоциативные массивы
3.6 Диапазоны значений
3.7 Классы и объекты
4 Подробнее о числах
4.1 Целые числа
4.2 Дробные числа
4.3 Семейный портрет чисел
4.4 Арифметические операции
4.4.1 Поразрядная арифметика
4.4.2 Операции с присвоением
4.5 Методы явного преобразования типов
4.6 Случайное число
4.7 Хитрости
4.8 Задачи
5 Подробнее о массивах
5.1 Способы создания массива
5.2 Диапазоны
5.3 О двумерных массивах
5.4 Методы работы с массивами
5.4.1 Получение размера массива
5.4.2 Поиск максимального/минимального элемента
5.4.3 Упорядочение
5.4.4 Обращение массива
5.4.5 Сложение/вычитание массивов
5.4.6 Объединение и пересечение массивов (как множеств)
5.4.7 Удаление дубликатов
5.4.8 Сплющивание массивов
5.4.9 Транспонирование двумерного массива
5.4.10 Размножение массивов
5.4.11 Функциональность стека
5.4.12 Функциональность очереди и списка
5.4.13 Мнемограмма для методов стека/очереди/списка
5.4.14 Создание своих классов, работающих как массивы
5.5 Логические методы
5.5.1 Есть элемент в массиве?
5.5.2 Массив пустой?
5.6 Итераторы
5.6.1 Изменение всех элементов массива
5.6.2 Отбор элементов по признаку
5.6.3 Суммирование/произведение/агрегация элементов
5.6.4 Разбиение надвое
5.7 Логические итераторы
5.7.1 Все ли элементы удовлетворяют условию?
5.7.2 Хотя бы один элемент удовлетворяет условию?
5.8 Хитрости
5.9 Задачи про массивы
5.9.1 Одномерные
5.9.1.1 Одномерные целочисленные
5.9.2 Двумерные
5.9.2.1 Двумерные целочисленные
6 Подробнее об ассоциативных массивах
6.1 Что используется в качестве ключей?
6.2 Способы создания ассоциативного массива
6.2.1 Из одномерного массива
6.2.2 Из двумерного массива
6.2.3 Нет данных
6.2.4 Известен только тип значений
6.2.5 Всё известно и дано
6.3 Методы работы с ассоциативными массивами
6.3.1 Получение массива значений и массива ключей
6.3.2 Замена ключей на значения
6.3.3 Обновление пары
6.3.4 Размер ассоциативного массива
6.3.5 Удаление пары по ключу
6.3.6 Удаление произвольной пары
6.3.7 Преобразовать в индексный массив
6.3.8 Упорядочение хеша
6.3.9 Поиск максимальной/минимальной пары
6.4 Логические методы
6.4.1 Хеш пустой?
6.4.2 Есть такой ключ?
6.4.3 Есть такое значение?
6.5 Итераторы
6.6 Хитрости
6.7 Задачи
7 Подробнее о строках
7.1 Способы создания строки
7.1.1 Для чего нужны работящие и ленивые строки?
7.2 Методы работы со строками
7.2.1 Арифметика строк
7.2.2 Преобразование в массив или путешествие туда и обратно
7.2.3 Длина строки
7.2.4 Получение подстрок
7.2.5 Строка-перевертыш
7.2.6 Меняю шило на мыло!
7.2.7 Сканируем текст на оШЫбки
7.2.8 Правила работы со строками
7.2.8.1 Символьный класс
7.2.8.2 Квантификатор
7.2.8.3 Альтернатива
7.2.8.4 Группировка
7.2.8.5 Фиксирующая директива
7.2.8.6 Модификатор
7.2.9 Правильное разбиение
7.2.10 Правильная замена
7.2.11 Правильный поиск
7.2.12 Жадность
7.3 Хитрости
7.3.1 Перенос по словам
7.3.2 Методы преобразования к строке
7.4 Задачи
8 Подробнее о методах
8.1 Создание метода
8.1.1 Указание значений по умолчанию
8.1.2 Методы с восклицательным и вопросительным знаком
8.1.3 Методы присвоения
8.1.4 Операторы
8.1.5 "Поглощение" аргументов метода
8.2 Подробнее о блоках
8.2.1 Зачем они нужны
8.2.2 Как создать блок
8.2.3 Блоки принимают аргументы
8.2.4 Свои методы с блоками
8.2.5 Некоторые применения блоков
8.3 Методы, которых не было
8.3.1 Ширяевский .size
8.3.2 Случайное число из диапазона
8.4 Способы расширения библиотеки методов
8.4.1 Как добавить метод к массиву/строке/венику?
8.4.2 Программист-разрушитель
8.5 Как написать свой итератор?
8.6 Как написать свой класс?
8.7 Наследовать или перемешать?
8.8 Как сделать свою библиотеку методов?
9 Матрицы и вектора
9.1 Как создать новую матрицу?
9.2 Как послать в матрицу 2-мерный массив?
9.3 Как изменить элемент матрицы?
9.3.1 Исправляем оплошность разработчиков
9.4 Методы работы с матрицами
9.5 Зачем нужны вектора?
9.6 Хитрости
9.7 Задачи
9.7.1 Решение систем линейных уравнений методом Гаусса
10 Работа с файлами
10.1 Чтение при помощи класса IO
10.2 Перенаправление потока
10.3 Универсальный способ работы с файлами
10.4 Хитрости
10.5 Задачи
11 Сети
11.1 Как написать троян?
11.1.1 Построение серверной части
11.1.2 Построение клиентской части
11.2 Как создать сетевой блокнот?
11.2.1 Первое приближение
11.2.2 Добавляем ERB
11.2.3 Выносим ERB-шаблон во внешний файл
11.2.4 ERB-шаблон превращается в ERB-сервлет
11.3 Гнезда, которые свили не птицы
11.4 Как пропинговать компьютер в сети?
11.5 Простейший датчик работы службы
11.6 Датчик обновления сайта
11.7 Приемо-передача файлов
11.8 Как скачать HTML-страницу
11.8.1 Запрос заголовка
11.8.2 Работа через прокси
11.9 Вам письмо или You have got mail
11.10 Запускаем свой веб-сервер
11.10.1 Сервлетки в рубиновом соусе
12 Приложения
13 Дальнейшее чтение
13.1 Русскоязычные ресурсы
13.2 Русскоязычная литература
13.3 Иноязычная литература
Объектно-ориентированное программирование:
Всё есть объект. Даже имя класса есть экземпляр класса Class
% ruby TecToBa9_nporpamma.rb
STDIN.getc
Привет мир!
% irb
% irb(main):001:0> [1,2,3,4]
% => [1, 2, 3, 4]
% irb(main):002:0> "text".methods
% => ["respond_to?", "%", "index", "select", ...]
Или посчитаем:
% irb(main):002:0> 14 * 5
% => 70
У fxri три окна: в левом перечислены все методы c описаниями. Верхнее правое окно
рассказывает о выбранном методе. Нижнее левое окно реализует irb, описанную выше.
Полигон в сети
Иногда интерпретатора Руби нет под рукой, приходится использовать сетевой полигон. Он
позволяет выполнять код на Руби прямо из браузера.
В Ruby знаком начала комментария служит #. Все, что между ним и концом строки
пропускается. Пример:
Вывод на экран
В Ruby есть много методов вывода: print, printf, p, puts, .display и другие. Но мы
использовать будем два:
метод puts. После вывода строки осуществляет переход на новую. Если приходится
выводить объект, не являющийся строкой, то puts вызывает метод .to_s для
преобразования его к строке;
метод p. Имеет самое короткое название, из-за чего часто используется для отладки.
Так же, как и puts, делает перевод на новую строку после вывода. Перед выводом
любого объекта (в том числе и строки) на экран, вызывает метод .inspect.
Иногда возникает ситуация, когда при попытке вывода на экран русскоязычной строки при
помощи метода p выводится непонятный код вида
"\323\367\350\362\345 \320\363\341\350!"
Примеры переменных:
maccuB
nepemeHHa9
gpyra9_nepemeHHa9
HenpaBuJIbHa9_nepemeHHa9 # неправильное имя. Начинается с прописной
3JIemeHT # неправильное имя. Начинается с цифры
_3JIemeHT # а вот как можно
___ # немного странное, но корректное имя переменной
Переменная может иметь имя НЕ только латинское но и, например, русское. Для того, чтоб
использовать такую возможность нужно, чтоб весь текст программы был написан в
кодировке UTF-8, а интерпретатор запускался с параметром -KU
maccuB = [1,2,3,4]
nepemeHHa9 = maccuB + [1,2]
gpyra9_nepemeHHa9 = nepemeHHa9 - maccuB
То, что справа от =, прежде чем стать значением переменной, обычно полностью
вычисляется. Наша nepemeHHa9 будет содержать массив [1, 2, 3, 4, 1, 2], ибо именно
таков результат действия maccuB + [1, 2]. Плюс (+) с массивами поступает именно так:
прицепляет второй массив в хвост первого.
Переменные указывают на объект
Фокус-покус:
nogpyra = "Даша"
k_HAM_B_rocTu_ugeT = nogpyra
puts nogpyra #-> "Даша", разумеется
k_HAM_B_rocTu_ugeT[0] = "М" # меняем первую (номер ноль) букву у переменной-строки
puts nogpyra #-> "Маша"
# На первый взгляд, странно и неожиданно
Сами данные (объект) лежат где-то в другом месте. В виду этого естественно, что
напрямую изменяя сам объект, указуемый переменной, все другие переменные,
указывающие на этот объект, будут возвращать то изменённое значение (также будут
изменяться).
nogpyra = "Даша"
k_HAM_B_rocTu_ugeT = nogpyra.clone
k_HAM_B_rocTu_ugeT[0] = "М"
# Но изменили мы лишь клон. Дома в сохранности сидит настоящая:
puts nogpyra #-> "Даша"
Можно создавать копии объектов ещё методом .dup. Разницу между ними Вы потом
поймёте
#...
k_HAM_B_rocTu_ugeT = "Аристарх"; # Создаётся новый объект, переменная переводится на него
p nogpyra #-> "Даша"
Типы данных
Данные любого типа в Ruby суть объекты тех или иных классов. Самые используемые
встроенные типы данных: Fixnum (целые числа, меньшие 231), Bignum (целые числа,
большие 231), Float (числа с плавающей точкой), Array (массивы), String (строки), Hash
(ассоциативные массивы). Естественно, что это только базовые типы, но их вполне хватает
для широкого спектра задач.
pWrapPolygonVertices8;5;(-25,0);(-25,21554);(21600,21554);(21600,0);(-25,0)wzTooltip"Все
абстрактные типы данных"posh2fBehindDocument1fIsButton1fLayoutInCell1
5 # целое число
-12 # отрицательное целое число
4.5 # число с плавающей точкой
076 # восьмеричное число
0b010 # двоичное число
0x89 # шестнадцатиричное число
Логический тип
Логические операции
название операции символ операции литерное обозначение
логическое "или" || or
логическое "и" && and
логическое "не" ! not
логическое "исключающее или" ^ xor
Логический (булев) тип — это вариация на тему «да» или «нет». В Ruby он представлен
двумя предопределенными переменными true («истина» или «да») и false («ложь» или
«нет»). Появляется логический тип в результате логических операций или вызова
логических методов.
Методы сравнения
название метода символ
равно ==
не равно !=
меньше <
больше >
меньше или равно <=
больше или равно >=
Часто молодые программисты, когда надо написать «меньше или равно», пишут знак =<
вместо <=. Запомнить правильное написание можно вслух проговорив «меньше или
равно» и в этом же порядке писать < и =
Традиционно имена логических методов заканчиваются на ? (знак вопроса).
Массивы
Нет ограничений (это общий принцип языка). Массивы могут быть сколь угодно
длинными.
Динамичность: размер массива легко меняется.
Гетерогенность: один массив может хранить данные разных типов.
Библиотека итераторов на каждый случай жизни. Эта возможность позволяет не
использовать циклов для обработки данных в массивах, а следовательно избегать
множества ошибок, связанных с неосторожным обращением с циклами. Итераторы
реализуются на высочайшем уровне.
Много других методов. Все элементарные задачи для массивов решаются вызовом
нужного метода.
Массив лучше всего вообразить как гусеницу и притом поезд с лапками-запятыми вместо
колёс.
Строки
Строки — это ряды букв и других символов. В Ruby строки используют наработки языка
Перл. Вот небольшой список их возможностей:
Правила — это новое название регулярных выражений. В текущей версии Ruby они
называются регулярными выражениями, но давайте смотреть в будущее. Perl 6 уже не
имеет понятия «регулярное выражение», заменив его «правилами» и «грамматиками»;
Можно вставлять произвольный код на языке Ruby в строку. После выполнения код
заместится результатом.
Например:
puts maccuB["мама"] #-> мыла раму
puts maccuB["807"] #-> nil
puts maccuB[807] #-> Это число улыбается!
puts maccuB[1] #-> nil
puts maccuB["npeBeg"]#-> MegBeg
Диапазоны значений
Чтобы было удобней получать подмассив или подстроку, был введен простенький тип
данных — диапазон (класс Range). Диапазон формируется тремя элементами: начало,
конец и тип протяженности (символ .. или ...). Начало и конец должны быть одного типа
данных (одного класса) и быть перечислимыми, что значит, иметь метод .succ. Пример
диапазонов:
'a'..'z'
'a'...'z' # то же, что и 'a'..'y'
1..100
1...100 # то же, что и 1..99
Объекты, которые имеют метод .succ называют последовательными. Это значит, что по
текущему элементу можно достоверно определить следующий (используя метод .succ)
Классы и объекты
Классы можно определять и создавать по ним объекты. Внутри класса может быть много
всего интересного, и у него может быть фамильное дерево, то есть классы Руби
поддерживают наследование. Однако заметим, что множественное наследование в Руби не
разрешается. И ещё много всего интересного можно сделать с классами и объектами. Но
об этом позже.
Подробнее о числах
Изначально числа представлены тремя типами: два целых типа (классы Fixnum и Bignum) и
один дробный (класс Float). Возможно подключение дополнительных типов, —
например, комплексных и рациональных чисел, но пока ограничимся тремя.
Целые числа
Целые числа в Ruby не ограничены по величине, то есть могут хранить сколь угодно
большие значения. Для обеспечения такого волшебного свойства было создано два класса.
Один из них хранит числа меньшие 231 (по модулю), а второй — всё, что больше. По сути,
для больших чисел создается массив из маленьких, а раз массив не имеет ограничений по
длине, то и число получается неограниченным по значению.
Как только число типа Fixnum становится больше 231 (по модулю), то оно
преобразовывается к классу Bignum. Если число типа Bignum становится меньше 231, то
оно преобразовывается к типу Fixnum.
При записи целых чисел сначала указывается знак числа (знак + обычно не пишется).
Далее идет основание системы счисления, в которой задается число (если оно отлично от
десятичной): 0 — для восьмеричной, 0x — для шестнадцатеричной, 0b — для двоичной.
Затем идет последовательность цифр, выражающих число в данной системе счисления.
При записи чисел можно использовать символ подчеркивания, который игнорируется при
обработке. Чтобы закрепить вышесказанное, посмотрим примеры целых чисел:
# тип Fixnum
123_456 # подчеркивание игнорируется
-567 # отрицательное число
0xbad # шестнадцатеричное число
0377 # восьмеричное
-0b101010 # отрицательное двоичное
0b0101_0101 # подчеркивание игнорируется
# тип Bignum
123_456_789_123_456 # подчеркивание игнорируется
-123_456_789_123_456 # отрицательное
07777777777777777777 # восьмеричное большое
Как видно из примеров, маленькие целые (Fixnum) и большие целые (Bignum) отличаются
только значением.
Дробные числа
Дробные числа задаются только в десятичной системе счисления, при этом для отделения
дробной части используется символ . (точка). Для задания дробных чисел может быть
применена и экспоненциальная форма записи: два различных представления 0.1234e2 и
1234e-2 задают одно и тоже число 12.34.
# тип Float
-12.34 # отрицательное дробное
0.1234е2 # экспоненциальная форма для числа 12.34
1234е-2 # экспоненциальная форма для числа 12.34
От класса Integer наследуются два класса: Fixnum (целое маленькое) и Bignum (целое
большое). К первому относятся все числа, по модулю меньшие 231 , а ко второму — все
остальные.
Как только число типа Complex (комплексное) лишается мнимой части, то оно
становится либо Integer (Fixnum или Bignum), либо Float (в зависимости от типа
действительной части). Если подключена библиотка mathn, получится число типа
Rational (рациональное).
Если в результате арифметических действий в числе типа Rational знаменатель
приравнивается 1, то оно преобразуется к числу Integer (Fixnum или Bignum).
Арифметические операции
Арифметические операции в Ruby точно такие же, как и в математике: сложение (+),
вычитание (-), умножение (*), деление (/), получение остатка от деления (%), возведение в
степень (**).
6 + 4 #-> 10
6 - 4 #-> 2
6 * 4 #-> 24
6 / 4 #-> 1
6 % 4 #-> 2
6 ** 4 #-> 1296
2 + 2 * 2 #-> 6
(2 + 2) * 2 #-> 8
Первое, что бросается в глаза — результат арифметической операции двух целых чисел
всегда будет целым. Особенно это видно при делении:
1/3 #-> 0
2/3 #-> 0
3/3 #-> 1
Если все аргументы арифметического выражения целые числа, то результат будет целым,
если хотя бы одно дробное, то результат будет дробным.
Поразрядная арифметика
| побитовое «или»
~ побитовая инверсия
Операции побитовой арифметики заимствованы из языка Си. На этот раз без всяких
экзотических особенностей.
6 & 4 #-> 4
6 | 4 #-> 6
6 ^ 4 #-> 2
6 << 4 #-> 96
6 >> 4 #-> 0 (чересчур намного сдвинули)
~4 #-> -5 (операция только над одним аргументом)
Операции с присвоением
ogHo_4ucJIo += gpyroe_4ucJIo
Вполне естественно, что вместо операции + может использоваться любая другая, а вместо
чисел могут быть другие типы данных.
cTpoka = "едем"
cTpoka += ", "
cTpoka *= 3
cTpoka #-> "едем, едем, едем, "
maccuB = [1,2,3]
maccuB += [4,5]
maccuB #-> [1,2,3,4,5]
При определении метода +, метод += вы получаете в подарок. Это правило касается всех
бинарных операций, обозначаемых значками.
Методы явного преобразования типов
Метод Операция
Случайное число
rand(100) # 86
rand #-> 0.599794231588021
В первом случае, метод rand возвращает целое число в диапазоне от 0 до 99 (на 1 меньше
100). Во втором случае, метод rand возвращает дробное число в диапазоне от 0.0 до 1.0
включительно. Различие в результате обусловлено передаваемым параметром:
Есть способ предсказать весь ряд «случайных» чисел. Делается это при помощи метода
srand. Ему передается целое число (идентификатор «случайной» последовательности).
После этого, весь случайный ряд можно предугадать. Проведем эксперимент: берусь
угадать массив, который будет сгенерен следующей программой.
srand 123
Array.new(5){ rand(100) } #-> [69, 71, 28, 42, 22]
Если вы выполните данную программу у себя дома, то получите тот же самый массив. 123
— номер «случайной» последовательности. Измените его и массив изменится!
Если вызвать srand без параметра или не вызывать его вообще, то номер «случайной»
последовательности выбирается случайным образом.
Хитрости
ucxogHoe_4ucJIo = 1234
puts sprintf("%b",ucxogHoe_4ucJIo) # метод sprintf заимствован из Си
puts ucxogHoe_4ucJIo.to_s(2) # современный метод - означает "по
основанию",
# аргументом может служить не только 8 и
16, но и 5, 30...
ucxogHoe_4ucJIo = 1234
puts ucxogHoe_4ucJIo.to_s.reverse # метод reverse переворачивает строку
ucxogHoe_4ucJIo, N = 1234, 5
puts ucxogHoe_4ucJIo[ N ]
gpo6Hoe_4ucJIo = 3.1415926535
puts ( gpo6Hoe_4ucJIo * 100 ).to_i.to_f / 100
puts sprintf( "%.2f", gpo6Hoe_4ucJIo ).to_f # полуСишный способ =)
Задачи
1. Сколько часов в году?
2. Сколько минут в неделе?
3. Сколько вам лет в секундах?
4. Сколько шоколада вы съели за свою жизнь? (Осторожно, может не хватить
ресурсов компьютера для вычисления.)
5. Чтобы пронумеровать страницы книги, печатник использовал 1890 цифр. Сколько
страниц в книге? Какова ее тематика?
Подробнее о массивах
Массивы — это тип данных, с которым вам придется работать постоянно. Облик
большинства программ зависит именно от правильного (читай «изящного») использования
массивов.
[1,2,3,4,5,6]
Есть еще тысяча и один способ, но эти три используются чаще всего.
Диапазоны
Раз уж речь зашла о диапазонах, то давайте посмотрим, как они позволяют получать
подмассивы. И насколько изящно у них это получается. Рассмотрим массив:
Индекс -2 значит «второй с конца элемент массива». Вот так и появилась отрицательная
индексация. Теперь давайте разберемся с диапазонами. Оказывается, в них тоже можно
использовать отрицательную индексацию. Вот как можно получить все элементы массива
кроме первого и последнего:
Или так:
О двумерных массивах
Для Ruby двумерный массив — это не более чем массив, содержащий одномерные
массивы. Вот несколько примеров двумерных массивов:
maccuB = [1,'считайте',3,'количество',5,6,'запятых',2,5]
maccuB.size #-> 9
Упорядочение
Не буду травить душу долгими байками. Чтобы упорядочить массив, нужно вызвать метод
.sort или .sort_by (начиная с версии 1.8).
['у','попа','была','собака'].sort
#-> ['была','попа','собака','у'] сортировка по значению
['у','попа','была','собака'].sort_by{ |elem| elem.size }
#-> ['у','попа','была','собака'] сортировка по размеру строки
Остается только добавить, что массивы упорядочиваются по возрастанию. Если вам надо
по убыванию, то придется писать собственный метод сортировки пузырьком. Шутка! По
правде же, есть много способов выстроить массив по убыванию. Пока мы будем
использовать метод .reverse, обращающий массив.
Обращение массива
Обращение массива — это изменение порядка элементов на обратный, то есть первый
элемент становится последним, второй элемент предпоследним и так далее.
['у','попа','была','собака'].sort.reverse #->
['у','собака','попа','была']
['у','попа','была','собака'].sort_by{ |elem| elem.size }.reverse #->
['собака','была','попа','у']
Сложение/вычитание массивов
Плюс берёт массив справа и, будто это железнодорожный состав, прицепляет его к хвосту
первого массива. Это называется конкатенацией.
[ 1, 1, 2, 2, 3, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #-> [3, 3, 3, 5]
[1,2,3,4,5,5,6,0,1,2,3,4,5,7]
Затем, начиная с первого вагона, инспектор идёт от вагона к вагону, удаляя элементы,
которые уже встречались. После зачистки получается настоящее логическое объединение.
[1,2,3,4,5,5,6,0,1,2,3,4,5,7]
Всё просто. Важно лишь помнить, что | и & не изменяют ни первый, ни второй исходные
массивы. Они через описанные процедуры создают новый массив. Чтобы тот не уехал от
вас, нужно присвоить его; но не себе, а переменной, приготовленной слева от =.
Сплющивание массивов
maccuB = [[1,2],[3,4]]
maccuB.flatten.max #-> 4
[[1,2],[3,4]]
А потом, две квадратные скобки добавляются слева и справа. Но делать это надо быстро,
чтобы элементы не успели разбежаться.
[1,2,3,4]
Вот и все! У нас они разбежаться не успели. Повторите данное упражнение на других
массивах (двумерных, трехмерных и т.д).
maccuB_2D = [[1,2],[3,4]]
maccuB_2D.map{ |maccuB| maccuB.max } #-> [2,4]
maccuB_2D = [[1,2],[3,4]]
maccuB_2D.transpose.map{ |maccuB| maccuB.max } #-> [3,4]
Размножение массивов
Того же самого эффекта можно добиться сцепив массив необходимое количество раз:
Заметили, что есть некоторая параллель с целыми числами? Умножение можно заменить
сложением и наоборот!
Функциональность стека
maccuB = [1,2,3,4,5]
maccuB[ maccuB.size ] = 6
maccuB #-> [1,2,3,4,5,6]
И если уж добавили, то надо как-то его и удалить. Делается это примерно так:
maccuB = [1,2,3,4,5,6]
maccuB[0...-1] #-> [1,2,3,4,5]
Но как всегда, эти задачи возникали слишком часто и их решили реализовать в виде
методов. Методы назвали .push («втолкнуть» в конец массива) и .pop («вытолкнуть»
элемент из массива):
maccuB = [1,2,3,4,5]
maccuB.push( 6 )
maccuB #-> [1,2,3,4,5,6]
maccuB.pop #-> 6
maccuB #-> [1,2,3,4,5]
maccuB = [1,2,3,4,5]
maccuB = [1,2,3,4,5]
.unshift( 0 ) .push( 6 )
[1,2,3,4,5]
.shift .pop
Где это может понадобиться? Например, вы реализуете класс, который умеет читать из
файла записи определенной структуры. Основную его логику занимает именно чтение
нужного формата, кеширование, разбор, десериализация и т.п.
Просто реализуйте .each и включите в ваш класс mixin Enumerable. В нем находится
реализация методов, таких как .inject, .each_with_index и т.п.
Логические методы
Логический метод — это метод, результатом которого является логическое выражение
(true или false).
maccuB = [1,2,2,3]
puts maccuB.methods.grep(/\?$/)
maccuB = [1,2,2,3]
puts maccuB.methods.grep(/\?$/).sort
Как узнать, есть ли некоторый элемент в массиве? Попробуем решить эту задачу при
помощи метода .size и итератора .find_all:
maccuB = [1,2,3,4,5,6,7]
uckomoe = 5 # число, которое мы будем искать
maccuB.find_all{ |elem| elem == uckomoe }.size != 0 #-> true
# это значит, что такое
число есть
Использование связки из трех методов (!=, .find_all и .size) для такой задачи —
возмутительно! Разработчики не могли с этим долго мириться и реализовали метод
специально для этой задачи. Имя ему — .include?. Перепишем нашу задачу, но на этот
раз будем использовать правильный метод:
maccuB = [1,2,3,4,5,6,7]
uckomoe = 5 # число, которое мы будем искать
maccuB.include?( uckomoe ) #-> true
# что бы это значило?
Массив пустой?
Если вы хотите задать массиву вопрос «ты пуст ли?», но боитесь обидеть, то можете пойти
окружным путем. Например, спросить у него: ты равен пустому массиву?
nycTou_maccuB = []
noJIHbIu_maccuB = [1,2,2,3]
nycTou_maccuB = []
noJIHbIu_maccuB = [1,2,2,3]
Но наш вам совет: не стоит искать обходных путей. Спросите его напрямую: .empty?
(«пуст?»):
nycTou_maccuB = []
noJIHbIu_maccuB = [1,2,2,3]
Итераторы
Массивы — эти эшелоны переменных, эти ожерелья запятых и элементов, — часто
приходится проходить целиком, обследуя каждый элемент. Да и не только массивы, но и
любые последовательности чего-нибудь.
В старину люди делали это циклами. В Руби у списочных структур данных есть
встроенные методы, которые проходят весь ряд поэлементно, но, в отличие от циклов:
Имя им — итераторы.
Изменение всех элементов массива
maccuB = ['шифровка','Штирлица','в','Центр','секретно']
maccuB.map{ 0 } #-> [0,0,0,0,0]
Можно дать элементу .map иное задание. Для этого зажимаем в фигурные скобы блока
иной код:
maccuB = [1,2,3,4,5]
maccuB.map{ |elem| elem**2 } #-> [1,4,9,16,25]
Прежде, чем блоку выдать квадрат очередного элемента, ему нужно знать этот элемент.
Итератор .map даёт ему значение элемента, словно фотографию, обрамлённую слева и
справа вертикальными чертами |. Чтобы блок смог взять эту фотографию, обязательно
дать ей имя. В нашем случае это elem, но подходят и такие названия:
_3JleMeHt
x
y
a_He_x0tuTe_li_4awe4Ku_4a9_c_LuMoHom
Но недопустимы названия вроде BoT_Takoro или 3Takoro. Правила именования тут такие
же, как и для обычных переменных.
Но ещё важно помнить, что элементы не уходят из первого вагона: блок лишь осматривает
каждый элемент, берёт его значение, но не меняет. Всё, что получается в результате работы
блока, садится в очередной вагон другого поезда.
То имя, что показывается в раздвижных дверях, — это не сам элемент, это лишь его копия.
Фотография. Голограмма. Это даже не другая переменная, это не переменная вообще.
Бессмысленно присваивать новое значение фотографии:
maccuB = [1,2,3,4,5]
maccuB.map{ |elem| elem = elem**2 } # присваивание не имеет смысла: elem
несёт лишь значение элемента, не являясь тем
maccuB = [1,2,3,4,5]
maccuB.map{ |elem| elem**2 } #-> [1,4,9,16,25]
maccuB #-> [1,2,3,4,5] - неизменный первый поезд.
maccuB = [1,2,3,4,5]
maccuB = maccuB.map{ |elem| elem**2 } #-> [1,4,9,16,25]
maccuB #-> [1,4,9,16,25]
Это общее явление Руби: методы (здесь — итераторы) не меняют объект (массив), с
которым работают. Они лишь выдают результат, который потом можно использовать как
аргумент или присвоить переменной.
elem%2==0 — это вопрос "чётен ли elem?". Ответом, как всегда, будет true или false.
Ведь "чётность" — это равенство нулю (==0) остатка деления (%) на 2.
Кстати, равенство нулю можно проверять и при помощи метода .zero?. А чётность тоже
можно проверить разными способами:
(elem%2).zero?
(elem&1).zero?
(elem[0]).zero? # Этот вариант круче всех
Если на вопрос, заданный в блоке, ответ true, то |elem| (значение очередного элемента
исходного массива), заносится в новый массив, который в итоге будет выводом из
итератора .find_all.
Выражение в блоке для .find_all должно быть логическим, то есть принимать значение
true или false.
Мысль: красивее делить лишь последнюю цифру(так в третьем классе учат четность определять :) ). Тем
более, когда нужно определить четность factorial(100) :). В любом случае базовый класс нужно расширить
чем-то вроде метода .chetnoe?.(с)Вовчик
Суммирование/произведение/агрегация элементов
Очень часто возникает задача найти сумму/произведение всех элементов массива. Для
этих целей (начиная с версии 1.8) традиционно используется итератор .inject. Для
демонстрации его работы, давайте найдем сумму элементов массива:
maccuB = [1,2,3,4,5]
maccuB.inject( 0 ){ |result, elem| result + elem } #-> 15
Рассмотрим все по порядку. Начнем с нуля. Его следует расшифровывать как result = 0
перед началом работы итератора, то есть это начальное значение переменной result
(переменной промежуточного результата).
Далее идет объявление двух переменных. Первая из них (result) будет хранить
промежуточный результат. Вторая (elem) — фотография текущего элемента массива (или
последовательности), мы такую уже видели.
После объявления описан алгоритм работы итератора. В данном случае ему предписано
каждый элемент массива складывать с промежуточной суммой: result + elem.
maccuB = [1,2,3,4,5]
maccuB.inject( 0 ){ |result, elem| result = result + elem } #-> 15
maccuB = [1,2,3,4,5]
maccuB.inject( 0 ){ |pe3yJibTaT, nepemeHHa9| pe3yJibTaT + nepemeHHa9 } #-> 15
Для полноты картины решим еще одну задачку. На этот раз будем искать произведение
всех элементов массива:
maccuB = [1,2,3,4,5]
maccuB.inject( 1 ){ |pe3yJibTaT, nepemeHHa9| pe3yJibTaT * nepemeHHa9 } #->
120
a = [1,2,3,4,5,6,7,8,9]
a.partition{ |x| (x%3).zero? } #-> [ [3,6,9], [1,2,4,5,7,8] ]
a = [1,2,3,4,5,6,7,8,9]
one, two = a.partition{ |x| (x%3).zero? }
one #-> [3,6,9]
two #-> [1,2,4,5,7,8]
Меняйте названия переменных хотя бы для того, чтобы не распознали вашего списывания.
Логические итераторы
В версии 1.8 появилось несколько логических методов: .all? и .any?. Они положили
начало такому классу методов, как логические итераторы.
Конечно же, идея логических итераторов долгое время летала в ноосфере. Существовали
итераторы, которые являлись условно-логическими: они возвращали nil в случае неудачи
и какой-либо объект — в случае удачи. В логическом контексте поведение таких
итераторов можно было посчитать логическим (false -> nil, а true ->
число/строка/любой_объект). Примером условно-логического итератора служит метод
.detect.
Данная программа проверяет, все ли элементы maccuBа больше двух. Давайте решим эту
же задачу при помощи новоявленного логического итератора:
maccuB = [1,2,2,3]
maccuB.all?{ |elem| elem > 2 } #-> false
Несмотря на то, что код получился короче, результат остался прежним: утверждение, что
все элементы массива больше двух, ложно.
maccuB = [1,2,2,3]
maccuB.inject( false ){ |result, elem|
result || ( elem > 2 )
} #-> true
maccuB = [1,2,2,3]
maccuB.any?{ |elem| elem > 2 } #-> true
Хитрости
Вот так можно сгенерировать «хороший пароль» — произвольную последовательность из
чисел или латинских букв, общей длиной в 8 символов.
maccuB = [1,2,3,4,5,6,7]
maccuB.sort_by{ rand } #-> перемешанный массив
maccuB = [2,1,3,5,6,7,4]
maccuB.sort{ |x,y| y <=> x } #-> [7,6,5,4,3,2,1]
Двумерные целочисленные
Хеши можно представить как массив пар: ключ => значение. Но в отличие от массива,
хеш неупорядочен: нельзя заранее сказать, какая пара будет первой, а какая последней.
Правда, удобство использования массива это шибко не умаляет. Более того, поскольку в
Ruby переменные не типизированы и методам с похожей функциональностью дают
похожие имена, то использование хеша чаще всего равносильно использованию массива.
xew = {5=>3,1=>6,3=>2}
xew[ 5 ] #-> 3
xew[ 2 ] #-> nil это значит, что объект отсутствует
xew[ 3 ] #-> 2
А вот так будет выглядеть та же самая программа, если мы будем использовать массив:
Использовать хеш в данном случае лучше потому, что, формально, хеш для данного
примера состоит из трех значащих пар, а массив — из шести элементов, из которых лишь
три элемента значащие. Исходя из этого, можно заключить, что массив будет хранить
избыточную информацию, а хеш — только нужную.
Продолжим поиски случаев применимости хеша и, на этот раз подсчитаем, сколько раз
каждое число повторяется в данном целочисленном массиве. Решение массивом:
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]]
maccuB = [1,2,1,2,3,2,1,2,4,5]
maccuB.inject( Hash.new{ 0 } ){ |result, i|
result[i] += 1
result
} #-> {5=>1, 1=>3, 2=>4, 3=>1, 4=>1}
зачем нужно дописывать result? Дело в том, что комбинация result[i]+=i имеет
в качестве результата целое число (учитывая, что массив целочисленный), а не хеш.
Следовательно, параметру result автоматически будет присвоено целое число (см.
описание итератора .inject). На следующей итерации мы будем обращаться к
result, как к хешу, хотя там уже будет храниться число. Хорошо, если программа
выдаст ошибку, а если нет? Проверьте это самостоятельно.
maccuB = [['comp1.mydomen.ru','192.168.0.3'],
['comp2.mydomen.ru','192.168.0.1'],['comp3.mydomen.ru','192.168.0.2']]
Все бы ничего, но чтобы найти IP-адрес по DNS имени, придётся перелопатить весь
массив в поиске нужного DNS:
dns_name = 'comp1.mydomen.ru'
maccuB.find_all{ |key, value| key == dns_name }[0][-1]
#-> "192.168.0.3"
Третий случай применимости хеша: когда требуется сопоставить один набор данных с
другим, то целесообразнее использовать хеш.
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}
Описание метода .update будет дано ниже. На данном этапе, попытайтесь угадать
принцип работы метода .update.
В данном примере ключами хеша (xew) являются два массива (maccuB_1 и maccuB_2).
Одному из них (maccuB_1) мы изменили нулевой элемент (с "а" на "я"). После этого доступ
к значению был потерян. После выполнения метода .rehash все встало на свои места.
Из одномерного массива
Положим, что у нас в наличии индексный массив, где ключ и значение записаны
последовательно. Тогда мы можем использовать связку методов * и Hash[]:
maccuB = [1, 4, 5, 3, 2, 2]
Hash[ *maccuB ] #-> {5=>3, 1=>4, 2=>2}
Из двумерного массива
Но может случиться так, что двумерный массив будет состоять из двух подмассивов:
подмассива ключей и подмассива значений:
maccuB = [[1,5,2],[4,3,2]]
Hash[ *maccuB.transpose.flatten ] #-> {5=>3, 1=>4, 2=>2}
Нет данных
Если нет данных, то лучше записать хеш как пару фигурных скобок:
xew = {}
xew[1] = 4
xew[5] = 3
xew[2] = 2
xew #-> {5=>3, 1=>4, 2=>2}
Сведения о типе значений использовать следует так: создать хеш, в котором будет
определен элемент по умолчанию. Элементом по умолчанию должен быть нулевой
элемент соотвествующего типа, то есть для строки это будет пустая строка ( "" ), для
массива — пустой массив ( [] ), а для числа — нуль (0 или 0.0). Это делается, чтобы к
пустому элементу можно было что-то добавить и при этом не получить ошибку.
Но как известно из любого правила есть исключение: использовать нулевой элемент, когда
значение будет записываться умножением, нежелательно. Потому как, даже не будучи
npopokом (Ых-хы-ыыы), можно предсказать результат. Он будет равен нулю.
Если вам изначально известны все ключи и значения, то и записывайте их сразу в виде
хеша:
Для получения отдельно массива ключей или значений существуют методы .keys и
.values.
Обновление пары
Что вы делаете, если хотите обновить какую-то программу или игру? Правильно,
устанавливаете апдейт. А вот чтобы обновить значение в ассоциативном массиве
используется метод .update. Пример использования этого метода в боевых условиях мы
уже приводили в начале раздела. Если вы помните, то мы считали сколько раз
повторяется каждое число. Наверняка, вы немного подзабыли его решение (у
программистов есть привычка не помнить константы). Позволю себе его вам напомнить:
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}
Сразу после названия метода (в нашем случае .update) идет передача параметра.
Страшная запись { i => 1 } — это не что иное, как еще один хеш. Ключ его хранится в
переменной i (счетчик итератора .inject), а в качестве значения выбрана единица. Зачем?
Расскажу чуть позже.
Здесь вроде бы все понятно. Запись стала менее страшной, но все равно вызывает дрожь.
Будем это исправлять!
Раньше мы не встречались с такой записью. Но ничего страшного в ней нет. Это что-то
типа поля боя. Нам выдали вооружение и необходимо провести некий маневр. В нашем
случае, арсенал у нас внушительный: key, old и new. Бой начинается при некоторых
условиях. Наш бой начнется, когда при добавлении очередной пары (переданной в
предыдущей части страшной записи) обнаружится, что такой ключ уже есть в хеше. Нам
предлагается описать наши действия именно в таком случае. Что же это за действия?
result.update( { i=>1 } ){ |key,old,new| old+new }
Всего лишь сложение old и new. Ничего не говорит? Тогда расскажу, что значат
переменные key, old и new. В переменную key передается значение текущего ключа, в old
— старое значение ключа (old по английски значит "старый"), а в переменную new —
добавляемое значение ключа (new по английски значит "новый").
Теперь переведем запись old+new на русский: в случае обнаружения ключа в хеше, нам
необходимо сложить старое значение с новым. Если помните, то новое значение равняется
единице, то есть в случае когда ключ, хранимый в i уже есть в хеше result, то к старому
значению просто добавляется единица. Вот и все... а вы боялись.
Рекомендуется перечитать данную главу еще раз, так как вы ее немного не поняли
Стоит уточнить, что если в индексных массивах под размером понимается количество
элементов, то в ассоциативноя массиве это количество пар вида ключ => значение. В
остальном же это наш старый добрый .size
Удаление пары по ключу
О том, как добавлять элементы в массив мы знаем, а вот про удаление — нет! Необходимо
это исправить. Чем мы сейчас и займемся.
Как вы, наверно, уже догадались, удалением пары по ключу занимается метод .delete.
Ему передается ключ от пары, которую следует удалить.
Метод .delete возвращает значение, которое соответствовало ключу в удаляемой
паре
Если в хеше отсутствует пара с передаваемым ключем, то метод .delete
возвращает nil
Ответ кроется в отсутствии метода-напарника .pop, так как если нельзя удалить
последний элемент, то под .shift понимается удаление произвольной пары. Вот такое вот
нехитрое доказательство
xew = {5=>3,1=>6,3=>2}
xew.shift #-> [5,3]
xew #-> {1=>6, 3=>2}
Обратите внимание, что метод .shift возвращает удаляемую пару в виде индексного
массива [ ключ, значение ].
Не стоит обольщаться по поводу того, что метод .shift возвращает первую пару.
Помните, что ассоциативные массивы — неупорядоченны
Однажды нерадивому студенту был задан вопрос: как упорядоченны ключи в
ассоциативном массиве? На что он дал радостный ответ «по возрастанию» и получил
заслуженного «гуся». Не повторяйте его ошибку! Помните, что пары в ассоциативных
массивах неупорядоченны.
Преобразовать в индексный массив
Чуть ранее уже говорилось, что в большинстве случаев индексные массивы удобней
ассоциативных.
Мнение авторов таково, что у программиста на Руби есть более благородные пути
времяпровождения, чем заниматься такой вот псевдооптимизационной ерундой.
Чтобы преобразовать ассоциативный массив в индексный, надо использовать метод to_a.
Его используют все, кто не может запомнить методов работы с хешами.
Упорядочение хеша
Да, множество пар в хеше неупорядоченно. Но это можно исправить, разве что результат
потом будет не хешем, а двумерным массивом.
Максимальная пара в хеше ищется точно также, как и максимальный элемент в массиве
xew = {"гаечный ключ" => 10, "разводной ключ" => 22}
xew.max #-> ["разводной ключ", 22]
xew.min #-> ["гаечный ключ", 10]
но с небольшими особенностями:
но, к сожалению, у меня не установлена версия 1.9 и нет способа проверить возможность
развертки массива во время передачи в блок. Дальнейшая информация может быть
неверной
Также, как и в методе .sort_by есть возможность по разному получать текущую пару: в
виде массива или двух переменных.
Логические методы
знали ли вы мистера Х?
вы были на месте преступления?
убивали ли мистера Х
...
ты пустой?
есть ли такой элемент?
ты массив?
уверен, что не строка?
Но давайте рассмотрим их подробней.
Хеш пустой?
Зададим вопрос "Хеш пустой?", но используя известный нам лексикон. Для начала
спросим "Пустой хеш тебе не брат-близнец?"
nycTou_xew = {}
noJIHbIu_xew = { "гаечный" => 20, "замочный" => "английский", "разводной" =>
34 }
nycTou_xew = {}
noJIHbIu_xew = { "гаечный" => 20, "замочный" => "английский", "разводной" =>
34 }
nycTou_xew = {}
noJIHbIu_xew = { "гаечный" => 20, "замочный" => "английский", "разводной" =>
34 }
Обратите внимание, что метод .empty? полностью повторяет такой же метод у индексных
массивов
Есть такой ключ?
Если вам нужно узнать у хеша ответ на вопрос "Есть у тебя такой ключ?", но вы не знаете
как это правильно спросить, то скорее всего вы зададите вопрос с два этапа: "какие ключи
у тебя есть?" и "есть среди них такой ключ?"
Но лучше задавать вопрос напрямую, это покажет ваше прекрасное знание языка.
Давайте подумаем, как задать вопрос "Есть такое значение?" хешу. Скорее всего, мы опять
зададим вопрос в два этапа: "какие значения есть?" и "есть ли среди них нужное нам?"
Задать вопрос "Есть такое значение?" можно не только при помощи метода .value?, но и
при помощи более длинного .has_value?
Итераторы
Обратите внимание на то, что в качестве счетчика передается массив из двух элементов. В
наших примерах счетчик итератора мы назвали array. В своих программах вы вольны
называть его как угодно.
Есть подозрение, что перед работой любого из итераторов вызывается метод .to_a. Уж
больно работа итераторов в хешах напоминает работу с двумерным массивом
Теперь посмотрим, как можно развернуть array в две переменные. Делается это простой
заменой array на key, value
Обратите внимание, что развертка массива прошла успешно только в первых двух
итераторах. В третьем возникла ошибка. Давайте выясним, откуда там взялся nil. Дело в
том, что развернуть массив не удалось и теперь он стал называться не array, а key.
Переменная value осталась "не у дел" и ей присвоилось значение nil. Чтобы это
исправить, достаточно поставить круглые скобки:
Итератор .map, вызванный без аргументов, аналогичен методу .to_a: просто раскладывает
хеш в двумерный массив.
Хитрости
Одному программисту надоело писать xew["key"] и он захотел сделать так, чтобы можно
было написать xew.key.
class Hash
def method_missing( id )
self[ id.id2name ]
end
end
Естественно, что ключи в таком хеше могут содержать только латиницу, нижнее
подчеркивание и цифры (везде, кроме первого символа). Иначе, говоря удовлетворять всем
требованиям, которые мы предъявляем к именам методов и именам переменных.
Задачи
1. Дан массив слов. Необходимо подсчитать сколько раз встречается каждое слово в
массиве.
Подробнее о строках
Строковый тип является самым популярным в любом языке программирования. Ведь без
него невозможно написать любую программу (особенно учитывая, что любая программа
— это строка). При выводе на экран или записи в файл, любой тип данных
преобразовывается к строке (явно или неявно). Это значит, что в конечном итоге все
сводится к строковому типу. Кстати, и ввод данных тоже осуществляется в виде строки (и
только потом преобразовывается в другие типы).
Студенты 4-го курса МЭТТ ГАИ поступили на подготовительные курсы в МГИУ. Там им
начали преподавать основы программирования на Ruby. И одна из заданных им задач
была: "Дано число, необходимо поменять порядок цифр на обратный". Задача сложная, но
наши студенты об этом не знали и решили ее преобразованием к строке:
ucxogHoe.to_s.reverse. Преподаватели были поражены и впредь запретили им
использовать преобразования к строке в своих программах. И все потому, что это сильно
упрощало решение и давало студентам огромное преимущество перед остальными
слушателями курсов.
Язык Ruby унаследовал работу со строками из языка Perl (признанного лидера по работе
со строками). В частности такой мощный инструмент как «правила» (rules).
Cтроки – это универсальный тип данных, т.к. в строку можно преобразовать любой
другой тип данных. А также, строку можно преобразовать в любой другой тип
данных (ведь изначально любой код программы – это строка).
Cтроки очень удобно преобразовывать в массив и обратно (методы .join и
.split). Поэтому работа со строками практически такая же удобная, как и с
массивами.
Если работа со строками обходится без преобразования в массив, то программа либо очень
простая, либо бесполезная
Способы создания строки
Строка создается при помощи ограничительных символов. Для этих целей чаще всего
используются " (лапка) и ' (типографский апостроф/минута/одиночная кавычка). Их смысл
различен. Строка в минутах гарантирует, что в ней будет содержаться текст такой же, как в
коде программы, без изменений. Строка в лапках будет проходить предварительное
преобразование. Будут раскрыты конструкции «вставка» и «специальный символ».
Хотя специальный символ и пишется, как два знака, но на деле это всего один символ.
Доказать это можно выполненением простенького кода: "\n".size #-> 1
Для чего нужны работящие и ленивые строки?
Скорее всего вы будете редко вспоминать про то, что существуют работящие и ленивые
строки. Тем более, что это различие действительно только на момент создания строки.
Рядовой программист пользуется либо работящими, либо ленивыми строками. Давайте
посмотрим, как выглядит код программиста, который использует только ленивые строки:
moe_4ucJIo = 18
mou_maccuB = [1,2,3,4]
puts 'Мое число = ' + moe_4ucJIo.to_s + ', а мой массив длины ' +
mou_maccuB.size.to_s
Обратите внимание, что перед сцеплением (умные дяди называют это конкатенацией)
необходимо все данные преобразовывать к строке методом .to_s. «Вставка» позволяет
этого избежать. Вот как будет выглядеть та же самая программа с использованием
«вставки»:
moe_4ucJIo = 18
mou_maccuB = [1,2,3,4]
puts "Мое число = #{moe_4ucJIo}, а мой массив длины #{mou_maccuB.size}"
moe_4ucJIo = 18
mou_maccuB = [1,2,3,4]
puts "Повторенье -- мать ученья. Мой массив = #{mou_maccuB.join(\",\")}"
moe_4ucJIo = 18
mou_maccuB = [1,2,3,4]
puts "Повторенье -- мать ученья. Мой массив = #{mou_maccuB.join(",")}"
maccuB = [4, 4, 2, 5, 2, 7]
puts maccuB.max #-> 7
maccuB = [4, 4, 2, 5, 2, 7]
puts "Максимальный элемент maccuB'a = #{maccuB.max}"
#-> Максимальный элемент maccuB'a = 7
Всем известно, что числа внутри компьютера хранятся в двоичной системе. Но вот
парадокс: получить двоичное представление числа иногда очень сложно. В частности, для
того, чтобы получить двоичную запись числа необходимо создать строку и записывать
результат в нее. Это значит, что результатом преобразования в другую систему счисления
(из десятичной) будет строка. Давайте посмотрим, как эта задача решается:
ucxogHoe_4ucJIo = 123
puts "В двоичном виде -> %b" % ucxogHoe_4ucJIo
#-> В двоичном виде -> 1111011
ucxogHoe_4ucJIo = 123
puts "В двоичном виде -> #{ ucxogHoe_4ucJIo.to_s(2) }"
#-> В двоичном виде -> 1111011
cynpyr = "Саша"
cynpyra = "Маша"
cynpyr + " плюс " + cynpyra + " = семья!"
#-> "Саша плюс Маша = семья!"
ocTaJIocb_JIeT = 100
"Ky-ky! " * ocTaJIocb_JIeT
#-> "Ky-ky! Ky-ky! Ky-ky! Ky-ky! Ky-ky! Ky-ky! Ky-ky! … Ky-ky! Ky-ky! "
Теперь за свое будущее можно не беспокоиться! Хотя нам может попасться неграмотная
кукушка…
С делением совсем другая история. Сейчас его в языке просто нет, но этот недостаток уже
ощущают многие. Смысл деления состоит в том, чтобы преобразовать строку в массив,
разбив ее по разделителю. Давайте преобразуем речь кукушки в массив и посмотрим,
сколько же лет она нам насчитала:
pe4b_kykywku = "Ky-ky! Ky-ky! Ky-ky! Ky-ky! Ky-ky! Ky-ky! Ky-ky! ... Ky-ky!
Ky-ky! "
( pe4b_kykywku / " " ).size #-> 100
Деление в примере работает также как и метод .split, о котором речь пойдет чуть позже.
А чтобы оно заработало у вас, необходимо добавить небольшой код в начало программы:
class String
alias / :split
end
Этот код как раз и говорит о том, что деление и .split — одно и тоже.
Деление стало нужно, когда метод * для массивов получил возможность работать, как
.join (преобразовать массив в строку, расположив элементы через разделитель). В виду
того, что .join и .split работают вместе точно также, как умножение и деление, то
появилась идея заставить работать деление как .split
Преобразование в массив или путешествие туда и обратно
Случилось так, что итераторы в строках работают настолько неуклюже, что рассмотрение
их в рамках учебника будет пропущено. Следовательно, для того, чтобы задействовать
механизм итераторов, мы будем преобразовывать строки в массив. После того, как
итераторы нам станут не нужны, то мы вернемся к строкам.
"Ky-ky".split('-') #-> ["Ky","ky"]
"Ky-ky".split('y') #-> ["K","-k"]
Чуть ранее мы упоминали, что можно использовать умножение вместо .join. Давайте
посмотрим, как это выглядит:
Если умножать массив на целое число, то будет размножение массива (прямо как
умножение в строках), а не преобразование в строку
Длина строки
Длина строки ищется точно также, как и длина массива или хеша, то есть методом .size:
Получение подстрок работает точно также, как и получение подмассива. С тем лишь
отличием, что нумерация идет не по элементам, а по символам. Это логично, особенно,
если учесть, что для строки элементом является символ.
cTpoka = "Во дворе дрова, а в дровах трава!"
cTpoka[27..-1] #-> "трава!"
cTpoka[27...-1] #-> "трава"
cTpoka[9...14] #-> "дрова"
cTpoka[9..13] #-> "дрова"
Отрицательная нумерация тоже работает, то есть последний элемент имеет индекс -1,
предпоследний -2 и так далее
Помните, что три точки в диапазоне дают подстроку без крайнего правого элемента. Кто-
то шлагбаумом балуется!
Для получения подстроки или символа из строки необходимо всегда указывать диапазон.
Даже если в диапазоне всего один элемент
Иногда хочется перевернуть строку задом наперед. Причины могут быть разные.
Например, вы ищите палиндром (число, которое можно перевернуть без ущерба для его
значения). Занимается этим благородным делом метод .reverse. Давайте сформируем
сообщение для Иных по другую сторону Сумрака:
Вот примерно такой бред надо прокричать, чтобы вас поняли. Кстати, попробуйте быстро
проговорить полученный текст.
Не стоит путать метод .reverse для массивов с методом .reverse для строк. В массивах
меняется порядок элементов, а в строках — символов
Меняю шило на мыло!
Для того, чтобы заменить "шило" на "мыло" используется не газета "Из рук в руки", а
методы .sub и .gsub:
Естественно, что менять можно не только шило и мыло, но и другие данные. Например,
возраст. Девушка Ирина утверждает, что ей 18, но я-то знаю, что ей 26. Давайте
восстановим истину (в ущерб своему здоровью):
Заметили, что мы используем только метод .sub? Давайте теперь рассмотрим работу
метода .gsub и его отличие от .sub. На этот раз мы будем исправлять текст, авторы
которого забыли про правило "ЖИ, ШИ — пиши через И"
Название метода .gsub корнями уходит в язык Perl. В нем, для осуществления
всевозможных замен, использовалась конструкция s///g, где модификатор /g
означал все возможные замены (от английского global — всеобщий, глобальный)
Сканируем текст на оШЫбки
Правила — это образцы к которым можно примерять строки. Правила обладают своим
собственным языком, который позволяет описывать одну, две, сотню и вообще любое
количество строк. Это своеобразная упаковка для множества строк в одну компактную
запись.
/(жы|шы)/
/\w+@[\w\.]+\w+/i
Страшно? А мне нет. На самом деле работа с правилами очень проста. Главное
привыкнуть и попрактиковаться.
Правила в рамках учебника будут описаны очень сжато. Многие тонкости освещены не
будут, поэтому для освоения "фигур высшего пилотажа" необходимо прочитать
специализированную литературу. Например, книгу "Регулярные выражения"
Символьный класс
Короткая Полная
Описание
запись запись
\d [0-9] цифра
Взгляните на примеры правил! Правда, они стали понятней? По крайней мере второе...
Квантификатор
Короткая Полная
Описание
запись запись
Снова посмотрите на примеры правил. Теперь вам они понятны? Если нет, то перечитайте
две предыдущие главы — в них основа правил
Альтернатива
Альтернатива нужна, когда необходимо объединить несколько правил в одно. При этом
совпадение засчитывается, когда есть совпадение хотя бы с одним правилом. Желательно
альтернативу заключать внутрь группировки (круглые скобки). Правила, входящие в
альтернативу, разделяются | (палкой, которая и является альтернативой). Примеры
альтернатив:
Почти калькулятор!
Правильная замена
С правильной заменой не все так просто. Дело в том, что методы .sub и .gsub совместно с
правилами становятся итераторами, которые последовательно обрабатывают каждое
совпадение с правилом. Чтобы это увидеть в действии, давайте решим задачу исправления
ошибок:
Опаньки, а первое слово не исправилось! Видимо дело в том, что слово "Жыло"
начинается с прописной буквы. Сейчас исправим:
Вот, теперь гораздо лучше. Как мы этого добились? Давайте разберемся. Начнем с
регулярного выражения:
/(Ж|Ш|ж|ш)ы/
Зачем была использована группировка? Для пояснения причины, рассмотрим код в ушках:
{ $1 + 'и' }
Вот для того, чтобы можно было использовать переменную $1 (результат первой
группировки) мы и задействовали группировку. В данном случае, в $1 сохраняется первая
буква слога, которая в результате исправления оШЫбки не меняется.
Есть еще пару интересных моментов, которые вам необходимо знать. Во время
предыдущего примера вас могли посетить следующие вопросы:
Нашей переменной мы дали название cJIoBo, что вполне согласуется с нашим geJIom.
Правильный поиск
Вот здесь метод .scan может развернуться в полную силу. Хотите получить массив всех
русских слов в тексте? Запросто:
Ну со .scan должно быть все понятно. А вот то, что метод [] начинает тоже правильно
искать — пока нет.
Очень полезно использовать [] в ситуациях, когда надо узнать ответ на вопрос "Есть хотя
бы одна подстрока, которая удовлетворяет правилу?" или получить первое (или
единственное) совпадение с правилом.
Существует древнее поверье, что если использовать одно и тоже правило для .scan и
.split, то получаются две части текста, из которых реально получить исходный.
Это значит, что если метод .split использует правило, описывающие все знаки
припинания, то результатом будет текст без знаков припинания. А вот если это же правило
будет использовать метод .scan, то в результате мы получим все знаки препинания без
текста
Рекомендуется использовать метод [] вместо метода =~ (заимствованного из Perl), так как
[] более функционален
Жадность
Несколько лет назад (еще при жизни http://ruby-forum.ru) решали мы интересную задачу:
как реализовать автоматический перенос на новую строку (wrap). Для тех, кто не застал те
времена, уточню задание: дан текст, необходимо, вставить переносы таким образом, чтобы
каждая из полученных строк была меньше n (для определенности n=80). Недавно я
осознал, что не могу решить эту задачу тем способом, который был нами тогда найден. Я
его просто не помнил... Решение нашлось быстро, достаточно было вспомнить, что на
англ. языке эта задача именуется коротким и емким словом wrap.
class String
def wrap(col = 80)
gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}})/,"\\1\\3\n")
end
end
Немного о структуре кода... метод .wrap реализован для экземпляров класса String.
Также стоит обратить внимание на то, что внутри правила (регулярного выражения)
возможна "вставка" (как в "рабочих строках"). Используется сей метод следующим
образом:
(.{1,80})( +|$\n?)|(.{1,80})
Руби сам преобразует типы для некоторых простых операций. Например, при включении
строки в другую он воспользуется имеющимся у обьекта методом .to_s:
class Container
def to_s
"контейнер"
end
end
cont = Container.new
class Container
include Comparable
def to_str
"контейнер"
end
def to_s
"контейнер"
end
def <=>(other)
to_s <=> other.to_s
end
end
cont = Container.new
Задачи
1. Дана строка слов, разделёных пробелами. Вывести длиннейшее слово.
2. Дана строка, содержащая кириллицу, латиницу и цифры. Вывести все слова, длина
которых равна средней.
3. Найти в строке первое целиком кириллическое слово.
4. Дан текст (строка с переносами). Найти все слова, содержащие лишь три буквы "о".
Только для русских слов.
Для французских и русских слов.
Для любого регистра буквы "о".
5. Найти в тексте время в формате "часы:минуты:секунды".
6. Найти все слова без повторяющихся букв (например, "Лисп" или "Ruby", но не
"Паскаль" или "Java").
Только для русскоязычных слов.
Не учитывайте цифры в словах.
7. Найти в тексте слова, содержащие две прописные буквы и исправить.
Решите задачу для слов и в кириллице, и в латинице.
8. Найти в тексте даты формата "день.месяц.год".
Найдите дату, где день ограничен числом 31, а месяц 12. Год ограничивайте
четырёхзначными числами.
Распознавайте месяц в виде "31.марта.2001".
9. Дан текст. Найдите все URL адреса и вычлените из них только корневой домен
(например, из http://ru.wikibooks.org/wiki/Ruby сделайте http://ru.wikibooks.org ).
Подробнее о методах
Все функции в Ruby являются методами, то есть свойственны обьектам. При
программировании на это можно не обращать внимания, поскольку любая программа на
Руби уже является определением класса. У методов могут быть обязательные или
необязательные параметры. Методы разграничиваются фигурными скобками или
ключевыми словами def и end.
Создание метода
def sum(a, b)
return a + b
end
sum(10, 2) #-> 12
def sum(a, b = 5)
a + b
end
sum(10, 2) #-> 12
sum(10) #-> 15
arr = []
if arr.length == 0
puts "empty"
else
puts "not empty"
end
У массива в Ruby есть метод-предикат .empty?, возвращающий true если массив пуст, и
метод-предикат .any?, возвращающий true если массив содержит хотя бы один элемент.
arr = []
if arr.empty?
puts "empty"
else
puts "not empty"
end
string #-> "Some string with spaces" #=> ...и меняет состояние
объекта-адресата
Методы присвоения
Знак равенства в конце названия метода означает, что этот метод присваивает свойству
объекта значение:
class Bottle
def capacity
@capacity
end
def capacity=(new_cap)
@capacity = new_cap
end
end
bytilka = Bottle.new
bytilka.capacity = 10 #-> 10, автоматически преобразуется в вызов метода
capacity=
Операторы
class BeHuK
def+(another)
12 + another
end
end
meTeJIka = BeHuK.new
meTeJIka + 10 #-> 22
Это применяется, например, во встроенном в Ruby объекте Time. При прибавлении к нему
целого числа он возвращает новый объект Time с добавленным количеством секунд:
require 'date'
d = Date.today #-> Sun Jun 11
d + 1 #-> Mon Jun 12 - на день позже
def sum(*members)
members[0] + members[1]
end
sum(10, 2) #-> 12
def sum(*members)
initial = 0
members.collect{ | item | initial += item }
initial
end
sum(10, 2) #-> 12
sum(10, 2, 12, 34) #-> 58
Подробнее о блоках
Понятие блока довольно просто: это часть программы, при создании захватывающая
переменные окружающей среды. По сути блок есть анонимный метод.
Блок передается методу через конструкцию do... end или фигурные скобки.
Общепринятым является использовать фигурные скобки, если вызов блока умещается на
одну строку программы. Для демонстрации работы блока, мы будем использовать метод
.map. Этот метод принимает блок и выполняет его строго заданное число раз.
Важно помнить, что блок использует методы и переменные, указанные при его создании,
то есть блок захватывает контекст, но переменные определенные в блоке остаются для
него локальными!
puts (1..3).map{ word = 'Bay!'; word } #-> выводит Bay! три раза
# поскольку блок знает про переменную word и она определена в нем
puts word # вызывает сообщение об ошибке - вне блока об этой переменной
ничего не известно
Как уже упоминалось, если блок многострочный целесообразней пользоваться формой с
do ... end
puts (1..3).map do
random_number = rand()
"Bay - случайный номер!\n"+
random_number.to_s
end
В данном случае при каждом выполнении блока переменная index будет устанавливаться
на положение итератора, начиная с 1! Аргументы метода указываются после открывающей
фигурной скобки или после слова do через запятую, и разграничиваются двумя
вертикальми чертами. Чтобы не перепутать черту со строчной латинской L, принято
"отбивать" аргументы блока от вертикальной черты пробелами.
method { | argument | .. }
def pa3_u_gBa
yield "u pa3"
yield "u gBa"
end
При этом строка будет передаваться блоку в переменную cJloBa при каждом выполнении.
Если блок обязателен, следует пометить его как последний аргумент метода и в начале
аргумента добавить амперсанд:
def twice(&block)
yield "u pa3"
yield "u gBa"
end
def func(a,&block)
return a if a
yield "u pa3"
yield "u gBa"
end
Более того, вызов функции pa3_u_gBa без указания блока также приведет к ошибке. Таким
образом, гораздо лучше вместо введения обязательного параметра задавать блок по-
умолчанию:
def func(a,&block)
return a if a
block ||= lambda{ | cJloBa | puts "!!! " + cJloBa }
block.call("u pa3")
block.call("u gBa")
end
Здесь lambda - пустая функция, а block.call - явный способ вызова блока кода на
выполнение.
Блок можно также передать другому методу, просто указав его как последний аргумент с
амперсандом:
Блоки — одна из главных особенностей Ruby. Уметь ими пользоваться — ключ к очень
коротким и очень понятным программам, делающим очень много.
Типичное применение блока — когда после выполнений некой операции нужно "вынести
мусор": закрыть открытый ресурс или отсоединиться от сети. Предположим, что мы
пишем метод для интернет-системы. При этом мы хотим выполнить несколько операций,
но чтобы их выполнить, нужно подключить пользователя к Сети. После того, как
операции завершились, надо его так же незаметно отключить.
connected { download_email }
def connected
connect_to_internet
result = yield
disconnect
result
end
В данном случае мы сохраняем то, что вернул блок в метод, закрываем соединение и
возвращаем результат блока как свой собственный. Чаще всего о методах, принимающих
блоки, можно говорить как о деепричастном обороте — например "соединившись",
"внутри_транзакции", "с файлом", "трижды".
def connected
connect_to_internet
begin
result = yield
ensure
disconnect
end
result
end
Тогда даже если метод вызовет ошибку, соединение все равно будет закрыто.
Ширяевский .size
Студент МЭТТ Ширяев Денис, на одном из зачетов предложил использовать метод .size в
качестве итератора. Он использовал его для подсчета количества элементов массива,
удовлетворяющих условию. По сути, он предложил укоротить связку .find_all{ ... }.
size. Вот как будет выглядеть программа подсчета количества четных элементов массива:
maccuB = [1,2,3,4,5,6]
maccuB.size{ |i| (i%2).zero? } #-> 3
class Array
def size( &block )
block ? inject( 0 ){ |count,elem| (yield elem ) ? count + 1 : count }
: length
end
end
Метод реализован только для массивов, но возможно его добавление к хешам или строкам.
3 + rand( 4 )
Откуда чего берется? Почему нельзя написать проще? Например вот так:
(3..6).rand
class Range
def rand
first + Kernel.rand( last - first + ( exclude_end? ? 0 : 1 ) )
end
end
Что и требовалось реализовать! Кстати, данная реализация имеет один изъян: для
строковых диапазонов, метод Range#rand будет выдавать ошибку. Решается проблема
достаточно просто. Надо реализовать Array#rand (получение случайного элемента
массива), а внутри Range#rand вызывать связку .to_a.rand. Теперь тоже самое, но на
Ruby:
class Array
def rand
self[ Kernel.rand( size ) ]
end
end
class Range
def rand
to_a.rand
end
end
Важно помнить, что в Ruby все типы являются обьектами, даже сами классы. Каждый
класс до конца выполнения программы остается открытым, а это значит, что в любой тип
можно добавить собственные методы (или изменить поведение существующих). Каждый
класс можно определять постепенно, в нескольких частях программы:
class BeHuK
def mecTu
end
end
class BeHuK
def noMblTb_B_yHuta3e(yHuTa3)
end
end
Одним из специфических свойств Ruby является то, что классы сами по себе —
экземпляры класса Class, и с ними можно работать как с обычными объектами.
Специальный синтаксис для доступа к методам класса в Ruby не нужен. Классы можно
хранить в переменных, передавать методам и так далее.
Проиллюстрируем это простым примером. Как мы знаем, у класса File есть метод open.
Создадим метод у класса File, дающий нам доступ к временному файлу, создаваемому в
момент выполнения кода. Это такой же метод, но открывающий только файлы из
директории /tmp:
class File
def self.temporary(&block)
# определим директорию в которой в данный момент запущена программа
# методы 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
Для управления временными файлами в Руби существует класс Tempfile - помимо других
достоинств он гарантирует, что созданные временные файлы по завершении программы
будут удалены. Так что даже в этом случае велосипед изобретать не стоит!
Если к классу надо добавить много методов сразу, то при описании класса можно выйти на
уровень его обьекта-класса. Это свойство в Ruby называется eigenclass (нем. eigen = "свой,
особый"). Подозревая, что многие из читателей незнакомы с математическим понятием
собственного значения/вектора/пространства, мы кратко и по-программистски назовём
eigenclass эйгенклассом.
Добавим к классу File метод myself, который дает быстрый доступ к текущему файлу с
исходным кодом:
class Manager
class << self
def create
...
end
def manage
...
end
end
end
Если нужно добавить метод только к конкретному экземпляру, нужно выйти на его
эйгенкласс:
str = "Crazy fox jumps over a lazy dog"
other_string = "Three black witches"
def str.vowels
vowels = []
scan /[AEIOUYaeiuoy]/ do | m |
vowels << m
end
vowels.uniq.join
end
end
Программист-разрушитель
class BeHuK
def mecTu
"МетёмЪ!"
end
end
venik = BeHuK.new
beriozovy_venik = BeHuk_6epe3oBblu.new
Object.send(:remove_const, :BeHuK)
meTeJIka = BeHuK.new
Object.send(:remove_const, :BeHuK)
Это свойство Ruby крайне полезно, если нужно создать класс, наследующий от другого, но
при этом имеющий другого родителя. Например:
# в чужой программе...
class Connection < Socket
# ... много-много методов connection...#
end
conn = Connection.new()
# в нашей программе...
Object.send(:remove_const, :Сonnection)
Полная замена чужих классов довольно опасна, но бывают ситуации, когда эта методика
спасает.
История из жизни
require 'matrix'
p Matrix[[1,-2,3],[3,4,-5],[2,4,1]].det #-> -50
давала неверный результат (правильный ответ = 62). Как выяснилось позднее, эта
проблема связана со спецификой целочисленной арифметики в Ruby («одна вторая в Руби
- нуль»). Предположив это, я решил, что проблема легко решится, если я преобразую
элементы матрицы к дробному типу (классу Float):
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
На этом бы история и закончилась (как позже я узнал, на этом она заканчивалась для
многих), но мне в руки попалась книга Programming Ruby 2ed с описанием возможностей
стандартной библиотеки версии 1.8.2. Именно там (на стр. 671) я наткнулся на описание
библиотеки mathn. Уникальность ее состоит в том, что она существенно расширяет
возможности стандартных чисел, добавляя к ним рациональные числа (класс Rational) и
комплексные числа (класс Complex).
Проще говоря, появляется возможность делить числа без погрешностей (класс Rational)
и возможность извлекать квадратный корень из отрицательного числа (класс Complex)
Чуть позднее, один из моих студентов написал мне письмо с просьбой объяснить как
«работать с матрицами в Руби»? При этом он задал всего три вопроса:
Для того, чтобы начать наше знакомство с матрицами, я отвечу сперва на них.
require 'mathn'
Matrix[[1, -2, 3], [3, 4, -5], [2, 4, 1]]
Этот способ создания мы уже с вами видели раньше. Обратите внимание, что для
использования матриц необходимо подключать библиотеку mathn.
require 'mathn'
maccuB = [[1, -2, 3], [3, 4, -5], [2, 4, 1]]
Matrix[ *maccuB ]
И вот тут всплывает «недоделанность» матриц. Метода для изменения элемента матрицы в
них нет! Для того, чтобы изменить элемент матрицы, надо преобразовать матрицу в
массив, изменить элемент массива и преобразовать массив в матрицу. Примерно вот так
(меняем элемент с индексом 0,0):
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]]
Исправляем оплошность разработчиков
Для начала, рассматриваем поближе библиотеку matrix (исходник или описание в fxri) и
выясняем, что для получения значения элемента используется метод «батарейка» с двумя
аргументами. Вполне закономерно использовать метод «батарейка равно» (то есть []=) для
изменения элемента. Сейчас мы его и реализуем:
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]]
Ну вот как-то примерно так... Почему не могли этого сделать разработчики, я так и не
понял. Скорее всего по идеологическим соображениям («не дело, чтобы матрицы вели
себя как простые массивы»).
Во-первых, для Ruby вектор — это объект класса Vector. Подключается он одновременно
с матрицами (класс Matrix). Во-вторых, очень похож на массив, но с одним существенным
отличием (cобственно это отличие и определяет полезность вектора): массивы и вектора
по разному складываются и вычитаются. Давайте рассмотрим небольшой пример:
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]
Обратите внимание, что BekTop создается точно также, как и матрица. По сути, вектор —
это матрица, которая состоит лишь из одной строки. А матрица, в свою очередь — это
массив векторов
Итак, в чем соль? Дело в том, что для сложения векторов необходимо сложить их
соответствующие координаты. В случае же с массивами происходит конкатенация
(сцепление) массивов. Вот именно в этой роли вектора не имеют себе равных. В
остальных случаях, замечательно работает массив.
Хитрости
Задачи
Решение систем линейных уравнений методом Гаусса
Как я уже рассказывал раньше, первая моя программа на Ruby (декабрь 2002 года) --
реализация симплексного алгоритма для решения задач оптимизации. Весь фокус в том,
что я эту программу нашел! Это 11 Кбайт и 363 строчки (140 + 223) программного кода.
Кстати, если найду подходящее для нее место, то обязательно надо будет опубликовать.
Но суть не в этом. После того, как я ее нашел, у меня появилась идея-фикс: реализовать
эту программу заново, но с высоты текущего уровня. Вот только для этого надо вспомнить
хотя бы что-то по поводу симплексного алгоритма (чтение моего старого программного
кода мне помогло мало). Первое, что пришло мне на ум -- там был метод Гаусса (правда,
насколько я помню, не в чистом виде). Вот как раз его мы сейчас и реализуем.
require 'mathn'
ypaBHeHue = [Vector[1,2,1,1],Vector[1,5,6,2],Vector[1,5,7,10]]
Почему был выбран именно массив векторов, а не матрица или двумерный массив? Дело в
том, что в методе Гаусса приходится выполнять такие векторные операции, как: вычитание
векторов и деление вектора на скаляр. Поэтому смысла создавать матрицу (векторных
операций не предусмотрено) или двумерный массив (придется реализовывать эти
операции) -- нет! Кстати, вполне возможно создать массив векторов и из двумерного
массива (что мы и сделаем в следующем примере). Итак, приступим к реализации прямого
хода метода Гаусса:
require 'mathn'
ypaBHeHue = [[1,2,1,1],[1,5,6,2],[1,5,7,10]].map{ |array| Vector[ *array ] }
# Прямой ход метода Гаусса
ypaBHeHue[0] /= ypaBHeHue[0][0]
И вот тут нас ждет неприятный сюрприз: у векторов не реализован метод /. Смотрим в
файл matrix.rb (который мирно лежит в директории со стандартными библиотеками).
Действительно нет! Метод * (умножить) есть, а разделить -- нет. Ну и ладно, мы --
программисты не гордые... сами напишем!
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]
require 'mathn'
class Vector
def /( arg )
self * (1/arg)
end
end
ypaBHeHue[1] /= ypaBHeHue[1][1]
ypaBHeHue[2] -= ypaBHeHue[1]*ypaBHeHue[2][1]
ypaBHeHue[2] /= ypaBHeHue[2][2]
require 'mathn'
class Vector
def /( arg )
self * (1/arg)
end
end
ypaBHeHue[1] /= ypaBHeHue[1][1]
ypaBHeHue[2] -= ypaBHeHue[1]*ypaBHeHue[2][1]
ypaBHeHue[2] /= ypaBHeHue[2][2]
ypaBHeHue[0] -= ypaBHeHue[1]*ypaBHeHue[0][1]
Теперь задачу можно считать решенной! Жаль только, что программа работает только для
уравнения 3х4. Надо бы добавить несколько итераторов, чтобы они самостоятельно
определяли размерность уравнения. Для этого нужно проследить чередование индексов и
записать для них итераторы (хоть я и недолюбливаю .each, но для данного случая он
пришелся как нельзя кстати). Расписывать преобразование я не буду, так как не вижу в
этом большой надобности. Поэтому, смотрите сразу то, что у меня получилось:
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
Обратите внимание, что ypaBHeHue задается через матрицу (которая, не без помощи
метода .row_vectors, преобразуется в массив векторов). Также обратите внимание, что
получить последний столбец можно посредством итератора .map и метода "батарейка".
Работа с файлами
Файлы в программах играют роль хранилищ, в которые можно записать любые объекты. В
отличие отпривычных нам объектов, файлы позволяют хранить данные даже тогда, когда
программа завершила свою работу. Именно поэтому они могут использоваться для
передачи данных между разными программами или разными запусками одной и той же
программы.
В своих операционных системах фирма Microsoft ввела понятие "двоичный файл", но оно
порождает больше проблем, чем удобств. Особенно при создании кроссплатформенных
приложений
Как организована работа с файлами? В самом общем случае работа с файлами состоит из
следующих этапов:
1. Открытие файла. Сущность этого этапа состоит в создании объекта класса File.
1. Чтение при помощи класса IO. Класс IO имеет множество методов, которые
позволяют производить чтение из текстовых файлов (с "двоичными файлами"
лучше так не работать). Если нужно считать весь текстовый файл, то лучше
пользоваться методами класса IO.
1. Если нужно считать весь файл целиком, то надо испольвать методы класса IO.
2. Если нужно работать только с одним файлом на чтение и только одиним файлом на
запись, то надо использовать перенаправление потока.
3. Если нельзя применить два вышеперечисленных способа, то надо использовать
универсальный способ работы с файлами.
Для чтения файла целиком используется метод .read. Он считывает весь файл в виде
строки. Во время его использования не стоит задумывать об открытии/закрытии файла,
так как эти операции скрыты внутри метода.
config = IO.read('config.yaml')
config.class #-> String
Имя файла — это строка. Не стоит усложнять себе жизнь и думать, что имя файла имеет
некий "божественный" тип данных. Будьте проще и люди к вам потянутся
Очень часто программист проектирует программу таким образом, чтобы ввод данных
осуществлялся с клавиатуры. После сотни-другой циклов откладки программист так
устает вводить данные, что создает файл, который содержит все необходимую входную
информацию и перенаправляет поток ввода с клавиатуры на этот самый файл. Добавляя
всего одну строчку в начало своей программы:
После этого, он сильно бьет себя по лбу и выкрикивает нечто вроде «Я идиот!» И все от
осознания того, что процесс откладки давно можно было упростить таким способом.
Вторым параметром метода .open передается модификатор доступа, то есть кодовое слово
по которому метод .open может предположить то, что вы будете делать с этим файлом. В
нашем примере мы использовали модификатор w (от англ. write - писать), который говорит
о том, что мы будем только писать в файл. Причем каждый раз файл будет
перезаписываться. При помощи модификатора a (от англ. append - добавлять) мы указали,
что мы будем добавлять данные в файл, а не перезаписывать, как в случае с w
Блок метода .open (т.е. фигурные скобки) нужен для того, чтобы при выходе их блока
автоматически осуществлять закрытие файла.
Хитрости
Задачи
Сети
Как написать троян?
Однажды, один из студентов (Geka) попросил меня рассказать о том, как создать
простейшее клиент-серверное приложение на Ruby. Перед тем, как подойти ко мне он уже
облазил несколько форумов, но у него все равно осталось много вопросов. Отчаявшись он
наконец подошел ко мне...
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
srv.close
end
Программка конечно хорошая, но сдается мне, что она не работает. Первое на что следует
обратить внимание - это закрытие соединения после выполнения каждой команды. При
работе с этим сервером через telnet будут возникать определенные проблемы. Давайте
предельно упростим специализированный код этой программы и посмотрим на структуру
клиент-серверного приложения.
require 'socket'
TCPServer.open('localhost', 3000){ |server|
if (session = server.accept)
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
}
Для того, чтобы соединиться с этим сервером, необходимо выполнить команду telnet и
набрать o localhost 3000. После успешного соединения можете набирать команды ls, cd
или shutdown
Во-первых, был добавлен бесконечный цикл loop, чтобы сессия сохранялась до тех пор,
пока не поступит команда shutdown. При этом было сокращено количество
поддерживаемых команд (убраны команды удаления и создания директорий), чтобы нас не
обвинили в создании и распространении деструктивного кода.
После небольших манипуляций с кодом стал виден базовый каркас сервера. Он состоит из
следующих блоков:
require 'socket'
Для того, чтобы работать с классом TCPServer (и не только с ним) необходимо подключить
библиотеку socket.
Этот программный код создает сервер, который будет прослушивать порт 3000. В качестве
порта может использоваться любой другой (например, 31337). Менять имя хоста
(localhost) не нужно, если только у Вас не несколько сетевых интерфейсов. Если у Вас
их все таки несколько, то ничего по поводу смены хоста Вам объяснять не надо. Вы и так
все, скорее всего, знаете ...
Данный код интересен тем, что в программе студента для его реализации задействовано
аж три строчки. И все лишь потому, что он не знал, что такое оператор * (звездочка). Этот
оператор преобразует массив в список параметров. Тем самым он освобождает нас от
нуждого присваивания. Сам же код получает от клиента строку, которая интерпретируется
им как "команда и аргумент, разделенные пробелом". Обратите внимание, что работа идет
с переменной session, а не server.
Для того, чтобы прекратить работу сервера необходимо нажать комбинацию клавишь
Ctrl+C или Ctrl+Break. Команду отключения данный сервер не поддерживает. Команда
shutdown относится к клиенту
Несмотря на то, что данный сервер вполне рабочий, у него есть один существенный
недостаток - он не работает для нескольких клиентов. Для того, чтобы это реализовать
необходимо обрабатывать каждое соединение с клиентом в отдельном потоке. Кто изучал
мультипроцессорное программирование, тот понимает о чем речь. Вот только не стоит
сразу кидаться в книжный магазин за необходимой литературой. В составе дистрибутива
Ruby уже есть замечательная библиотека gserver, которая как раз и занимается тем, что
реализует обработку запросов клиентов в отдельных потоках. Для демонстрации ее
работы перепишем предыдущую программу под gserver.
require 'gserver'
class Tpo9H < GServer
def serve( session )
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
tpo9H = Tpo9H.new( 31337 )
tpo9H.audit = true
tpo9H.start
tpo9H.join
require 'gserver'
Вместо библиотеки socket мы подключаем библиотеку gserver. Для нас это означает то,
что работать с классом TCPServer напрямую мы не будем. Использовать мы будем класс
GServer.
Этот код создает класс Tpo9H, который наследует функциональность от класса GServer.
Можно было бы конечно просто расширить класс GServer, но так хотелось "хакерское"
название класса, что я не смог удержаться и произвел наследование.
tpo9H.audit = true
Если пропустить эту строчку, то сервер будет работать молча. Настолько молча, что Вы не
сможете понять работает он или нет? Надпись об удачном запуске сервера будет выглядеть
примерно так:
tpo9H.start
tpo9H.join
Как мы уже видели выше, серверная часть полностью определяет функциональность всего
клиент-серверного приложения. Клиентская часть же лишь принимает от пользователя
запросы, пересылает их серверу и получает ответ, который передает пользователю.
Итак, давайте наберем клиентский код, который будет соединяться с нашей серверной
частью. Скорее всего он будет выглядеть вот так:
require 'socket'
TCPSocket.open('localhost',31337){ |client|
2.times{ puts client.gets.chomp }
client.puts "ls"
puts client.read
client.puts "cd .."
puts client.gets
client.puts "ls"
puts client.read
client.puts "shutdown"
}
require 'socket'
TCPSocket.open('localhost',31337){ |client|
2.times{ puts client.gets.chomp }
client.puts "ls"
puts client.sysread(5000)
client.puts "cd .."
puts client.gets
client.puts "ls"
puts client.sysread(5000)
client.puts "shutdown"
}
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
session.puts "+OK"
}
end
end
tpo9H = Tpo9H.new( 31337 )
tpo9H.audit = true
tpo9H.start
tpo9H.join
Была добавлена лишь одна команда (хотя, самые внимательные могут заметить, что еще и
метод .print был заменен на .puts) - session.puts "+OK". Она будет выполнятся после
каждой передачи данных от сервера к клиенту. Тем самым мы будем извещать клиента о
том, что передача завершается. Теперь займемся переписыванием клиента. Необходимо
исправить код там, где происходит чтение, чтобы он учитывал строки +OK.
require 'socket'
TCPSocket.open('localhost',31337){ |client|
2.times{ puts client.gets.chomp }
client.puts "ls"
loop{
str = client.gets.chomp
if str[/^\+OK/]
break
else
puts str
end
}
client.puts "cd .."
loop{
str = client.gets.chomp
str[/^\+OK/] ? break : puts( str )
}
client.puts "ls"
while !(str = client.gets.chomp)[/^\+OK/]
puts str
end
client.puts "shutdown"
}
Код стал выглядеть ужасней, зато стал более честным. Стоит отметить, что предложено
три варианта обработки строки +OK. Правда, отличаются они лишь в деталях и
функционально делают одно и то же. Давайте рассмотрим подробней решения, которые
были реализованы в ходе исправления:
loop{ ... }
При помощи команды str[/^\+OK/] мы проверяем наличие строки +OK в переменной str.
Если проверка прошла успешно, то происходит выход из бесконечного цикла. Если нет, то
продолжается получение данных и вывод их на экран.
Это всего лишь иная запись кода: if str[/^\+OK/] then ... else ... end. Зато более
короткая...
Итак, теперь все экземпляры класса TCPSocket приобрели метод .cmd, который отсылает
команды и принимает результат. Непонятными могут быть следующие моменты:
self.puts command
Переменная self содержит указатель на текущий объект, то есть она будет указывать на
объект, который хранится в переменной client (так как вызываться метод будет именно
для этого объекта).
yield str
Данная команда передает значение переменной str в блок. Наличие этой команды
подразумевает наличие блока во время вызова метода (имеется в виду .cmd). Блок
позволит нам обрабатывать результат так, как мы хотим. Не ограничивая себя выводом
результата на экран.
TCPSocket.open('localhost',31337){ |client|
2.times{ puts client.gets.chomp }
client.cmd("ls"){ |str| puts str }
client.cmd("cd .."){ |str| puts str }
client.cmd("ls"){ |str| puts str }
client.cmd("shutdown"){ |str| puts str }
}
Метод .cmd работает как итератор. Последовательно передавая пришедшие строки в блок
для дальнейшей обработки.
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 "CmeHa gupekTopuu Ha #{arg} npowJIa
yga4Ho!\r\n"
rescue
session.puts "HeT Takoro qpau'JIa uJIu gupekTopuu
- #{ arg.inspect }\r\n"
end
when "shutdown"
session.puts "+OK"
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
На этом можно было бы и закончить, если бы не одно НО: метод .cmd уже
реализован в рамках класса Net::Telnet. И не переписать наш клиент под этот
класс было бы неправильно. Переписываем:
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
Вот теперь, уж точно все!
Вскоре, после публикации данной главы в учебнике, Geka прислал клиентскую часть,
которую он реализовал в две строчки:
Способ не совсем честный, но нет причин о нем не рассказать. Данный способ использует
метод system, который вызывает внешнюю программу (в данном случае telnet). Далее
все введенное с клавиатуры уходит в программу telnet, а выдаваемое на экран берется из
результата работы этой программы.
После запуска данной программы надо ввести имя хоста и порт. Далее, можно вводить
команды, которые поддерживает сервер (в нашем случае ls, cd и shutdown).
Идея написать подобную программу появилась после прочтения статьи Создаем свой
online-блокнот. Продемонстрированная там программа предельно проста, но на ее
примере очень удобно показать работу с ERB. А учитывая тот факт, что ERB используется
в составе инструментария Руби на Рельсах, то ценность этой статьи становится
очевидной.
Первое приближение
Смысл всей программы следующий: надо создать страницу с окном ввода и кнопкой
"Сохранить", при нажатии на которую происходит сохранение текста из окна ввода в файл
notepad.txt. Ввод осуществляется через браузер по адресу http://localhost:8080.
Сервер будем запускать на порт 8080, так как стандартный для веб-серверов порт 80 у
меня занял Skype. При желании, порт можно легко поменять
require 'webrick'
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 = %& <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>&
}
server.start
require 'webrick'
Подключение библиотеки WEBrick для построения серверов (в том числе и веб-
серверов).
:Port => 8080
Как уже было сказано ранее, порт 80 у меня занят. Поэтому приходится искать
другой. Исключительной магической силой порт 8080 не обладает. Поэтому, при
желании, его можно сменить на другой.
server.mount_proc('/')
На виртуальную корневую директорию мы вешаем процедурный сервлет. Он будет
заниматься обработкой запроса на адрес http://localhost:8080/, то есть обращение к
виртуальной корневой директории. Чтобы изменить запрос, на который будет
откликаться сервлет, достаточно заменить строку '/' на другую, например
'/notepad'. Тогда, адрес сервлета будет http://localhost:8080/notepad. Только, зачем
писать больше?
{ |req,resp| ... }
Процедурному (как и любому другому) сервлету передается в блок две
переменные. Переменная req содержит информацию о запросе (какой браузер
запрашивает, какие переменные передаются и так далее), а при помощи
переменной resp формируется ответ (какой тип информации передается,
информация для отображения и так далее).
... if req.query["text"]
Постфиксная запись условного оператора if. Блок, перед if будет выполняться
только в том случае, когда сервлету будет передаваться переменная text. Метод
.query возвращает ассоциативный массив в виде {имя_переменной => значение}.
File.open('notepad.txt','w'){ |f| f.write( req.query["text"] ) }
Открываем файл notepad.txt для записи и пишем туда значение переменной text
(передается сервлету в теле запроса).
resp['Content-Type'] = 'text/html'
Устанавливаем тип передаваемых нами данных. Обычно тип определяется по
расширению файла, но процедурный сервлет про файл, а тем более про его
расширение, ничего не слышал. Вот и приходится указывать тип передаваемых
данных через параметр Content-Type.
resp.body = %& ... &
При помощи метода .body= мы передаем HTML-код в качестве ответа на запрос.
Сам HTML-код передается в виде строки, заключенной в %& ... & . Это
альтернативный способ задания строки. После символа % идет символ, который
будет замыкающим (в нашем случае & ). Такой способ задания строки используется
в случаях, когда строка содержит много кавычек и апострофов (чтобы не
заниматься их экранированием).
server.start
При помощи метода .start мы запускаем веб-сервер.
Для того, чтобы прекратить работу веб-сервера, надо его просто выгрузить. Это делается
при помощи комбинации клавиш Ctrl+Break или Ctrl+C
Добавляем ERB
Не знаю почему, но ERB для меня это "PHP, только на Ruby". Эта фраза обладает столь
магическим свойством, что после нее собеседнику все становится понятным. Вот и вам
советую воспринимать ERB как "PHP на Ruby".
Давайте посмотрим, как может выглядеть наша программа в которой используется ERB-
шаблон:
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'
wa6JIoH = %& <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>&
resp.body = ERB.new( wa6JIoH ).result
}
server.start
require 'erb'
Подключаем библиотеку ERB.
wa6JIoH = %& ... <%=IO.read('notepad.txt')%> ... &
Создаем переменную wa6JIoH и присваиваем ей строку, которая по
совместительству является ERB-шаблоном. Внутри строки можно заметить тег <%=
... %> внутри которого осуществляется считывание файла notepad.txt. Результат
считывания будет вставлен вместо тега <%= ... %> во время обработки ERB-
шаблона.
resp.body = ERB.new( wa6JIoH ).result
<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>
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( IO.read('index.html') ).result
}
server.start
Вот, уже другое дело! Можно было бы этим и ограничиться, если бы в библиотеке WEBrick
отсутствовал бы ERB-сервлет... Но он есть!
require 'webrick'
server = WEBrick::HTTPServer.new( :Port => 8080 )
server.mount('/',WEBrick::HTTPServlet::ERBHandler,'index.html')
server.start
Самые внимательные читатели заметили, что строка require 'erb' волшебным образом
испарилась. Связано это с тем, что реализация ERB-сервлета уже подключает библиотеку
ERB
Но, чтобы добиться столь существенного уменьшения кода программы, пришлось немного
изменить файл index.html (с ERB-шаблоном):
Как можно заметить, в самое начало шаблона был добавлен тег, который осуществляет
сохранение содержимого переменной text в файл notepad.txt. Код тега был перенесен
из программы практически один к одному. За одним только исключением: к переменной
text мы теперь обращаемся через переменную query, а не через req.query. Вот и все
отличие!
На этом все... из чего состоит наша программа теперь?
require 'ping'
host = ARGV[0] || "localhost"
printf("%s alive? - %sn", host, Ping::pingecho(host, 5))
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")
Итак, давайте разберем, что делает наш метод. Он создает соединение посредством класса
TCPSocket и тут же закрывает его. Если соединение проходит слишком долго (хост не
существует в сети) или произошла какая-то другая ошибка (не поддерживается протокол
или еще что-то), то метод возвращает false. Если удаленный хост явно отверг наш запрос
или принял его, то мы возвращаем true. Как видите, ничего сложного в этом нет.
текущее время;
время отклика;
баннер службы.
Ее я решил писать в логи следующим образом... Каждый день будет создаваться файл лога
и каждый час в него будет писаться информация о работе службы. В качестве
планировщика заданий использовался виндовый Cron, который запускал мою программу в
нужное время. Для начала я написал программу, которая соединяется со службой SMTP и
получает от него баннер:
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}" )
}
request = t.gets.chomp
Чтобы разобраться с проблемой, пришлось читать книжку. Слава богу, что под рукой
оказалась книга TCP/IP. Учебный курс. В ней на с.345 черным по серому была начертана
схема взаимодействия SMTP протокола. Как оказалось, чтобы получить баннер от службы,
надо послать команду NOOP. Переписываем наш фрагмент программы.
require 'socket'
request = ""
begin_time = Time.now
t = TCPSocket.open('mail.scli.ru', 'smtp')
t.puts('NOOP')
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}" )
}
Великолепно! Программа работает... но иногда зависает. И тогда меня посетила еще одна
мысль: на соединение отводить всего одну секунду (не уложился — сам дурак). Если
соединение зависало, то в файл записывалось timeout. Чтобы "отводить время на
выполнение операции" нужно задействовать библиотеку timeout. Она у меня входила в
состав дистрибутива. Итак, переписываем нашу программу:
require 'timeout'
require 'socket'
request = ""
beachmark = ""
begin
beachmark = Time.now
timeout(1){
t = TCPSocket.open('mail.scli.ru', 'smtp')
t.puts('NOOP')
request = t.gets.chomp t.close
}
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}" )
}
require 'timeout'
require 'socket'
require 'benchmark'
request = ""
beachmark = ""
begin
beachmark = Benchmark.measure{
timeout(1){
t = TCPSocket.open('mail.scli.ru', 'smtp')
t.puts('NOOP')
request = t.gets.chomp
t.close
}
}.to_s
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}" )
}
Нам нужен только real. Все остальное — для более детального анализа. Поэтому немного
доработаем результат блока measure:
beachmark = Benchmark.measure{
timeout(1){
t = TCPSocket.open('mail.scli.ru', 'smtp')
t.puts('NOOP')
request = t.gets.chomp
t.close
}
}.real
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 Timeout::Error
beachmark = 'timeout'
rescue
beachmark = ' '
request = 'error'
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}" )
}
А теперь вопрос: как переписать программу так, чтобы она могла тестировать не только
SMTP, но и HTTP, FTP, POP3 и т.д? Но это уже для самостоятельного изучения.
Ни для кого не секрет, что админы — это жутко ленивый народ. Но начальство, как назло,
не хочет платить деньги за просто так. Ему нужны отчеты! Представляю на Ваш суд
датчик обновления новостной ленты сайта. Для начала следует как следует поставить себе
задачу:
Итак, для чего нам нужен датчик? Допустим Вы админ сайта и постоянно добавляете
новости. По окончании недели (месяца, года, столетия, ...) от Вас требуют отчет о
проделанной работе. Вам приходится заходить на сайт и смотреть те новости, которые Вы
добавили за последний период. Муторно и неэффективно. Намного приятнее постоянно
вести записи о добавленных новостях (при помощи программы, конечно) и по завершении
периода просто сделать соотвествующую выборку.
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 )
}
Приемо-передача файлов
Как скачать HTML-страницу
require 'net/http'
Net::HTTP.open('ru.wikibooks.org'){ |http|
File.open('RubyBook.html','w'){ |file|
file.write http.get('/wiki/Ruby').body
}
}
Недостатком использования класса Net::HTTP является то, что URL разбивается на две
части: адрес сервера (ru.wikibooks.org) и адрес страницы (/wiki/Ruby). Такое разбиение
удобно только в том случае, когда необходимо скачивать несколько HTML-страниц с
одного сервера. А вот как раз таких ситуации возникают крайне редко. Чаще приходится
запрашивать страницы с различных серверов. Для этих целей и был придуман метод open
из библиотеки open-uri. Он самостоятельно разбирает URL и производит нужный запрос
(не только HTTP).
require 'open-uri'
File.open('RubyBook.html','w'){ |file|
file.write open( 'http://ru.wikibooks.org/wiki/Ruby' ).read
}
Во время скачивания передается не только сама страница (тело сообщения или на англ.
boby), но и техническая информация (заголовок или на англ. head). Мы еще не раз будем
свидетелями такого поведения в протоколах сети Интернет. В заголовке содержится
информация об успешности запроса (код возврата), типе передаваемых данных,
кодировке, HTTP-сервере и так далее. Для примера, мы будем производить HTTP-запрос и
получать ответ только в виде заголовка. Запись заголовка в файл производить не будем, так
как в реальной практике этот прием практически не используется. Сначала
«потренируемся на кошках», то есть на классе Net::HTTP:
require 'net/http'
Net::HTTP.open('ru.wikibooks.org'){ |http|
p http.head('/wiki/Ruby')
}
Как и обещалось ранее, вывод заголовка мы делаем на экран. А запрос заголовка, вместо
тела сообщения, осуществляется простой заменой метода .get на метод .head. А как
тогда получить заголовок и тело сообщения одновременно? Очень просто:
require 'net/http'
Net::HTTP.open('ru.wikibooks.org'){ |http|
head, body = http.get('/wiki/Ruby')
}
Для этого достаточно присвоить результат метода .get двум переменным. Произойдет
присвоение списков и в первую переменную попадет заголовок, а во вторую тело
сообщения.
Теперь рассмотрим, как выглядит чтение заголовков в методе open библиотеки open-uri:
require 'open-uri'
Замена метода .read на метод .meta позволяет нам дотянуться до заголовка. Заголовок
имеет вид ассоциативного массива (объект класса Hash), где ключем является имя
параметра (по стандарту MIME), а значением – значение параметра. Читать одновременно
заголовок и тело сообщения можно вот так:
require 'open-uri'
require 'net/http'
Net::HTTP::Proxy('you.proxy.host',8808).start('ru.wikibooks.org'){ |http|
p http.get('/wiki/Ruby').body
}
Добавив всего лишь небольшой фрагмент кода мы получили работу через прокси. В
нашем случае использовался прокси-сервер с адресом you.proxy.host, который
предоставляет прокси-доступ через порт 8808.
require 'open-uri'
Реализация соединения через прокси у метода open более удобна, так как в зависимости от
внешних факторов можно соединяться как через прокси, так и напрямую. В случае
Net::HTTP и Net::HTTP::Proxy используются два разных класса и подобные трюки
затруднительны (хотя и возможны).
Вам письмо или You have got mail
Запускаем свой веб-сервер
Для того, чтобы создать простейший веб-сервер не нужно реализовывать протокол HTTP
на уровне сокетов. Достаточно знать, какую библиотеку использовать. В нашем случае это
будет библиотека WEBrick (http://www.webrick.org), которая уже включена в дистрибутив
Руби. Возможности библиотеки настолько широкие, что для их описания потребуется
создание отдельного учебника. Мы рассмотрим лишь часть из них, зато на реальном
жизненном примере.
Для того, чтобы проверить работает ли библиотека WEBrick я выполнил следующий код:
require 'webrick'
WEBrick::HTTPServer.new( :DocumentRoot => 'public_html', :Port => 8080 ).
start
<html><body><h1>Сервер работает!</h1></body></html>
После обновления страницы на экране красовалась крупная надпись: "Сервер работает!"
Все! Мы с вами написали свой первый веб-сервер.
Для того, чтобы прекратить выполнение сервера нужно нажать комбинацию клавиш
Ctrl+Break или закрыть окно терминала. Для данной реализации веб-сервера это
единственный способ прекратить его работу
require 'webrick'
Подключение библиотеки WEBrick. Ничего интересного и неожиданного здесь не
произошло.
WEBrick::HTTPServer.new( :DocumentRoot => 'public_html', :Port =>
8080 ).start
Очень часто необходимо осуществить передачу данных между компьютерами по сети. Для
этого вполне подойдет WEBrick. Быстро напишите веб-сервер, который будет
просматривать нужную директорию. Теперь, для вас это больше не проблема!
Но вернемся к заказанной мне программе. Она будет состоять из двух частей: обычная
html-страница (index.html) и сервлет (/input). Страница public_html/index.html будет
содержать форму в которую будут вводится исходные данные. Ее задачей будет
формирование запроса для сервлета. Сервлет /input будет получать номера
подразделений, а выдавать их названия и ФИО действующих начальников. Вот код
public_html/index.html:
Geka, когда увидел этот код, воскликнул: "И не лень было такой код писать?" На что я ему
резонно ответил, что этот код является результатом программы-генератора, которую я не
привожу здесь, чтобы не отвлекать внимание читателя от более интересной темы
Хотя код и довольно объемный, но я решил поместить его целиком, чтобы программа у вас
работала точно также, как и у меня. Первое, на что следует обратить внимание, это
параметры тега FORM. Именно он занимается тем, что подготавливает данные для сервлета
и переправляет их ему.
action='/input'
Адрес, по которому будут передаваться данные. Это может быть как CGI-
приложение, так и сервлет. В нашем случае это сервлет /input.
method='post'
В протоколе HTTP используется два метода передачи данных: POST и GET.
Отличаются они тем, что POST не отображает передаваемые данные в адресной
строке браузера. Возьмите за правило использовать метод POST.
Внутри тега FORM присутствует великое множество тегов INPUT, которые как раз и
формируют данные. Каждый из тегов INPUT имеет два параметра: NAME и TYPE.
TYPE="checkbox"
Параметр TYPE указывает на тип формируемых данных. В данном случае это
checkbox (переключатель). Он имеет два значения: "on" (включен) и "off"
(выключен). Значение "off" обычно не передается. Получается своеобразный
булевский тип.
NAME="check_01"
Все данные передаются в виде пары имя=значение. Параметром NAME задается имя.
Значение же задается пользователем.
<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
bosses = { ... }
Создается ассоциативный массив, который будет использоваться при обработке
данных. Ключем является имя «переключателя», а значением — строка, на которую
надо этот ключ заменить. Путем такой замены мы будем формировать результат.
require 'webrick'
Подключаем библиотеку WEBrick. По-хорошему ее надо было подключать перед
инициализацией ассоциативного массива. Но не хотелось разбивать на части код,
относящийся к реализации веб-сервера.
server = WEBrick::HTTPServer.new( :Port => 8080 )
Создаем экземпляр класса WEBrick::HTTPServer. На этот раз мы его сохраняем в
переменную server. Это необходимо для подключения сервлетов. Параметр
:DocumentRoot мы не указываем, так как файл public_html/index.html мы тоже
сделаем сервлетом. Поэтому подключать для этого всю папку public_html просто
глупо.
server.mount_proc('/'){ |req,resp| resp.body =
IO.read('public_html/index.html') }
Вот почти такую программу я и презентовал секретарше Анюте. «Почти» потому, что
фамилии должны идти строго в определенном порядке (чего нельзя добиться в
ассоциативном массиве), но для того, чтобы посмотреть реализацию веб-сервера —
продемонстрированной версии программы должно быть достаточно. Смело меняйте код и
создавайте свои веб-сервера!
Файловый сервлет
Реализует взаимосвязь запроса с реальным файлом (или директорией). В самом
первом примере мы использовали файловый сервлет, когда передавали
:DocumentRoot => 'public_html' в качестве параметра методу .new. Это было
равнозначно созданию файлового сервлета на корневой директории веб-сервера.
Функциональность файлового сервлета описана в классе
WEBrick::HTTPServlet::FileHandler.
CGI-сервлет
Реализует взаимосвязь запроса с CGI-приложением, то есть считывается первая
строка и вытаскивается оттуда путь к интерпретатору. Далее производится запуск
интерпретатора, которому параметром передается вызванный файл. Строка для
CGI-приложений на Ruby будет выглядеть приемерно так:
#!/usr/bin/ruby
для операционных систем семейства Unix
#!c:/ruby/bin/ruby.exe
для операционных систем семейства Windows
Кстати, я столкнулся с тем, что под Windows CGI-сервлеты отказывались работать.
Это связано с тем, что WEBrick считает, что для того, чтобы запустить скрипт
достаточно просто его вызывать (./sctipt_name) и он сам запустится. Понятное
дело, что в Windows такое работать не будет. Поэтому мне пришлось немножко
переписать часть библиотеки WEBrick, которая отвечает за запуск CGI-программ.
Для того, чтобы мои изменение стали доступны и вам, я написал небольшую
программку, которая устанавливает себя куда надо:
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
Для того, чтобы было проще пользоваться всеми видами сервлетов WEBrick я написал
небольшую программку, которая создает метод mount_file (для файлового сервлета),
mount_erb (для ERB-сервлета) и mount_cgi (для CGI-сервлета). Как вы могли заметить
mount_proc уже существует (собственно, его название и послужило прототипом для
остальных). Вот эта программа:
require 'webrick'
module WEBrick
class HTTPServer
['ERB','CGI','File'].each{ |handler|
class_eval( "def
mount_#{handler.downcase}(point,file)\nmount(point,HTTPServlet::#{handler}Han
dler,file)\nend" )
}
end
end
require 'webrick'
module WEBrick
class HTTPServer
['ERB','CGI','File'].each{ |handler|
class_eval( "def
mount_#{handler.downcase}(point,file)\nmount(point,HTTPServlet::#{handler}Han
dler,file)\nend" )
}
end
end
Приложения
/Задачник
Сборник задач
/Справочник
Справочник по базовым классам
/Лицензия
Перевод Лицензии Руби
/Жаргон
Придумываем слова с рубинами и рельсами
/Фольклор
Народное творчество любителей Руби
/Избранное с RubyNews
К разграблению. Полезные статьи перемещаются в основную часть учебника, затем
это приложение удалим.
/Идеология
программирования вообще и на Руби в частности
Дальнейшее чтение
Русскоязычные ресурсы
Форум Ruby on rails. Русскоязычный форум написанный на Ruby on Rails (пока в
тестовом варианте) но планируется быстрое развитие.
Учебные материалы МФТИ. Коллекция учебных материалов по Ruby на сайте
МФТИ
Русскоязычная литература
Ruby — руководство пользователя. (недоступно) Перевод Александра Мячкова.
Конспект лекций по высокоуровневому программированию. Шипиев Р.Н.
Rolling with Ruby on Rails. Перевод Андрея Горбатова (pdf).
Самоучитель Ruby. Параллельный проект по написанию учебника.
ЧАсто задаваемые ВОпросы. (требуется авторизация) FAQ по Ruby.
Программирование на Ruby. Учебное пособие по языку Ruby, Роганова Н.А., Тузов
А.С.
Лекции по программированию. Роганова Н.А.
Иноязычная литература
Why’s (Poignant) Guide to Ruby(англ.) — эта книга достойна чтения, даже если Вам не
нужно знание Руби. Просто шедевр. Распространяется бесплатно. Там ещё богатая
подсветка текста; эх, нам бы такую.. Частичный перевод здесь.
Где-то на странице (обычно слева, снизу от меню) должен быть список ссылок на
параллельные версии этого викиучебника на других языках.
Получено с http://ru.wikibooks.org/wiki/Ruby