Практическая информатика

Введение в ООП

Разбить на страницы
Показывать лекцию целиком

Язык программирования Ruby

Ruby - один из самых молодых языков программирования. Своим именем он обязан драгоценному камню рубину (по аналогии с другим широко распространенным языком программирования Perl - жемчуг). Вот как описывает Ruby его создатель, японский программист Юкихиро Мацумото (Yukihiro Matsumoto): "Это мощный и динамический объектно-ориентированный язык с открытыми исходниками, который я начал разрабатывать в 1993 году. Ruby работает на многих платформах, включая Linux и многие реализации Unix, MS-DOS, Windows 9x/2000/NT, BeOS и MacOS. Главная цель Ruby - эффективность разработки программ, и пользователи найдут, что программирование на нем эффективно и даже забавно".

В Японии Ruby сильно потеснил такие известные языки как Python и Perl (а книга "Ruby the Object-Oriented Scripting Language" стала бестселлером) и начал распространяться по всему миру. За последний год появились три англоязычных книги, посвященные Ruby (к сожалению, пока не имеющие русского перевода). У этого языка очень неплохие шансы стать действительно популярным - ведь он вобрал в себя достоинства других языков, учтя их недостатки.

Ruby входит в стандартную поставку ОС GNU\Linux (начиная с версии 7.2), а пользователям MS Windows для первого знакомства стоит порекомендовать его несколько устаревшую версию (1,2 Мбайт), легко умещающуюся на одной дискете, которая включает, помимо интерпретатора языка и библиотек, руководство пользователя, FAQ (ответы на часто задаваемые вопросы) и множество примеров. Ruby является свободно распространяемым продуктом, поэтому вы можете не беспокоиться ни о его стоимости, ни об ограничениях в его использовании.

Этот язык несомненно является одним из лучших в качестве первого языка программирования, изучаемого студентами и школьниками. Быстрый цикл разработки (редактирование - запуск - редактирование), использование интерпретатора, изначальная объектно-ориентированность языка, нетипизированные переменные, которые не требуют объявления - все это позволяет учащимся сконцентрировать свое внимание на общих принципах программирования. В дальнейшем изложении мы будем ориентироваться на работу в ОС Linux. Использование Ruby в других операционных системах практически ничем не отличается, а результаты выполнения не зависят от используемой ОС.

Сначала проверим, установлен ли интерпретатор Ruby в системе или нет. В окне shell введите команду ruby -v (этот ключ требует вывода версии языка). Если следующее сообщение появится, то Ruby установлен (версия, дата и платформа могут отличаться):

ruby 1.6.4 (2001-06-04) [i386-linux-gnu]

Файлы, содержащие программы на языке Ruby, обычно имеют расширение rb. По давней программистской традиции наша первая программа будет печатать фразу "Hello, World!". При помощи любого редактора plain-текста (emacs, kwrite, notebook и т. п.) создадим файл hello.rb, в который поместим текст

puts "Hello, World!"

Для выполнения этой программы в командной строке введите

ruby hello.rb

В результате выполнения программы в командном окне будет напечатана требуемая фраза.

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

#!/usr/bin/env ruby

Она обязательно должна начинаться с первой позиции. Затем нужно изменить права доступа файла с программой, сделав его исполняемым: chmod +x hello.rb. Теперь для запуска программы достаточно ввести команду

./hello.rb

Для того чтобы сделать программу более понятной человеку, который ее читает, вставляются комментарии. Однострочные комментарии начинаются с символа # и продолжаются до конца строки. Многострочные комментарии заключают в специальные "скобки" - все, что располагается между строками =begin и =end, считается комментарием. Например,

#!/usr/bin/env ruby
=begin
Это
 комментарий
=end
puts "Hello, World!"
# Это тоже комментарий

Программа на языке Ruby, часто называемая скриптом, есть последовательность инструкций (утверждений, предложений). Каждая инструкция по умолчанию заканчивается концом строки. Если же по каким-либо причинам требуется разместить несколько инструкций на одной строке, то их нужно разделять символом ; (точка с запятой). С другой стороны, иногда инструкция не помещается на одной строке. В этом случае символ \ сигнализирует о том, что ее продолжение располагается в следующей строке.

Пример

#!/usr/bin/env ruby
# Инструкция заканчивается концом строки
puts "Hello, World!"

# Несколько инструкций в одной строке
puts "Это тест, "; puts "демонстрирующий работу Ruby."

# Незаконченная инструкция, 
# продолжение которой на следующей строке
puts "Программирование на Ruby - " +
  "приятное занятие."

# Утверждение, разделенное на несколько строк
puts \
       "И мы обязательно этому научимся!"

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

Далее строка #!/usr/bin/env ruby в тексты программ не включается.

Объекты и методы

При описании Ruby было отмечено, что он является объектно-ориентированным языком программирования. Это означает что все, с чем работает программа, является объектом, при этом вычисление осуществляется посредством взаимодействия объектов. Каждый объект есть представитель (экземпляр) некоторого класса и его поведение (функциональность) определяется именно им. Тем самым все объекты, которые являются экземплярами одного класса, могут выполнять одни и те же действия, называемые методами. Для того чтобы применить метод к некоторому объекту используется оператор вызова метода, обозначаемый символом . (точка): после указания объекта ставится точка, а затем указывается применяемый метод.

Пока мы не будем вдаваться в особенности объектно-ориентированного программирования (тем более, что в различных языках программирования по разному трактуется концепция ООП), а лишь познакомимся с некоторыми "простейшими" объектами, без которых невозможно приступать к дальнейшему изучению языка. Это представители так называемых "встроенных" классов. Среди них:

true - логическая величина, означающая истину; единственный представитель класса TrueClass ;

false - логическая величина, означающая ложь; единственный представитель класса FalseClass ;

числа - представители класса Numeric, который в качестве подклассов содержит дробные ( Float ) и целые числа ( Integer );

строки - представители класса String ;

nil - "ничто"; единственный представитель класса NilClass.

моменты времени - представители класса Time.

Числа

Знакомство с представителями различных классов начнем с чисел. Целые числа в Ruby являются экземплярами класса Integer, который объединяет два подкласса: Fixnum или Bignum. Объектами класса Fixnum являются те целые, чье двоичное представление способно разместится в машинном слове (31 бит на большинстве компьютеров). Если число выходит за границы указанного диапазона, то оно автоматически преобразуется в объект класса Bignum, чей диапазон изменения ограничивается только объемом доступной памяти. Если в результате работы с объектами класса Bignum итоговое значение попадает в диапазон, задаваемый классом Fixnum, то выдаваемый результат преобразуется в экземпляр класса Fixnum.

Число Класс
123456 Fixnum
123_456 Fixnum (подчеркивание игнорируется)
-543 отрицательное Fixnum
123_456_789_123_456 Bignum
0xaabb шестнадцатеричное
0377 восьмеричное
-0b1010 двоичное (отрицательное)
0b001_001 двоичное

При записи целых чисел сначала указывается его знак (знак + обычно опускается). Далее следует основание системы счисления, в которой задается число (если оно отлично от 10): 0 - для восьмеричной, 0x - для шестнадцатеричной, 0b - для двоичной. Затем идет последовательность цифр, выражающих число в данной системе счисления. При записи больших чисел можно использовать символ подчеркивания, который игнорируется при обработке. В таблице представлены целые числа и указана их принадлежность к тому или иному классу.

Дробные числа задаются в десятичной системе счисления, при этом для отделения дробной части используется символ . (точка). Такие числа являются экземплярами класса Float, например, 12.34. Для задания дробных чисел может быть применена и экспоненциальная форма записи: два различных представления -.1234е2 и 1234е-2 задают одно и тоже число 12.34.

Для вычисления арифметических выражений применяются следующие операторы: + (сложение), - (вычитание), * (умножение), / (деление), % (остаток от деления), ** (возведение в степень). Порядок вычисления определяется стандартными приоритетами операций, а для его изменения используются круглые скобки. Заметим, что если все аргументы выражения целые числа, то и результат будет целым, если же хотя бы одно из чисел, входящих в выражение - дробное, то и результат будет экземпляром класса Float.

Пример

Создайте файл с именем object.rb, в который поместите следующий фрагмент. Выполните программу и объясните результат.

puts 5/8                       # 0
puts 5.0/8                     # 0.625
puts 2**1000    
puts ((2*500+1)*(2**500-1))
#!/usr/bin/env ruby

puts 5/8                       # 0
puts 5.0/8                     # 0.625
puts 2**1000    
puts ((2*500+1)*(2**500-1))    

=begin
puts (2**30-1).type
a=0b001_001
puts a
puts a.type
a=12.34
puts a
puts a.type
a=12.34e2
puts a
puts a.type
=end

puts 12.34.ceil                  #  13
puts 12.34.floor                 #  12
puts -12.ceil                    # -12  
puts -12.floor                   # -12
puts 12.34.round                 #  12 
puts 12.54.round                 #  13
puts -34.56.abs                  #  34.56

puts -12.34.to_i                 # -12
puts 1234.to_f                   # 1234.0


=begin
puts 123_456_789.type
puts 0xaabb
puts -0b1010/0b10
puts 2/3.to_f
=end

Отметим еще несколько методов, используемых при работе с числами (т. е. представителями класса Numeric ). Среди них: ceil (нахождение наименьшего целого не меньшего, чем данное), floor (наибольшее целое, не большее данного), round (округление до ближайшего целого), получение абсолютной величины числа abs. Ниже приведены примеры использования этих методов.

puts 12.34.ceil                  #  13
puts 12.34.floor                 #  12
puts -12.ceil                    # -12  
puts -12.floor                   # -12
puts 12.34.round                 #  12 
puts 12.54.round                 #  13
puts -34.56.abs                  #  34.56

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

Метод Назначение метода Пример использования Результат
to_f Преобразовать объект в экземпляр класса Float 1234.to_f 1234.0
to_i Преобразовать объект в экземпляр класса Fixnum или Bignum -12.34.to_i -12

В последнем примере первая точка отделяет дробную часть числа, а вторая является оператором вызова метода.

Строки

Другим столь же распространенным классом является класс String. К этому классу относятся произвольные строки символов, заключенные в апострофы или кавычки, например, 'hello', 'раз, два, три', "привет всем". Есть несколько альтернативных способов задания каждого из этих видов строк.

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

puts 'hello'        # hello 
puts %q/hello/      # hello 
puts %q(hello)      # hello

Два подряд идущих символа \ внутри такой строки заменяются на один.

puts 'hell\\o'      #  hell\o  
puts %q(hell\\o)    #  hell\o

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

puts 'hell\'o'      # hell'o  
puts %q(hell\'o)    # hell\'o  
puts 'hel"l"o'      # hel"l"o

Для создания строки, заключенной в кавычки, следует либо просто заключить ее в кавычки, либо использовать конструкции %Q или %, после которых указывается строка, обрамленная с двух сторон одним и тем же символом, отличным от цифр и букв (можно использовать также пару любых скобок). Ниже перечислено несколько способов задания строки "hello".

"hello"  %Q/hello/ %Q{hello} %Q(hello) %Q!hello!
%<hello> %(hello)  %!hello!  %*hello*  %+hello+

puts "Say \"Hello\"" # Say "Hello"

Строка, заключенная в кавычки, позволяет интерпретировать последовательности символов, начинающиеся с символа \ такие, например, как \n (символ перехода на новую строку) и \t (табуляция). Другой особенностью строк, заключенных в кавычки, является возможность использования подстановки: если строка содержит некоторое выражение, ограниченное символами #{ и }, то оно заменяется на результат его вычисления. Добавьте в программу следующие строки и проанализируйте полученный вывод.

puts "a \t b"; puts 'a \t b'
puts "выражение 3*5+8 равно  #{3*5+8}"
puts 'выражение 3*5+8 равно  #{3*5+8}'
puts "работа с целыми числами: 5/8 = #{5/8}"
puts "перевод в класс Float: 5/8 = #{5/8.to_f}"

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

puts "-12.34".to_i
puts "12.34".to_f
puts "+12:34".to_i
puts "12qq34".to_f

Для получения строки, содержащей символ с заданным ASCII кодом, используется метод chr, например,

puts 209.chr

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

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

Назначение и пример использования метода Результат
+ Сцепление строк "мол" + "око" "молоко"
* Повтор строки "ab" * 3 "ababab"
[ ] Возвращает ASCII-код символа, находящегося на указанной позиции строки (отсчет начинается с нуля)
"abcdef"[0]
"abcdef"[0].chr

97

"a"

[нач..кон] Возвращает подстроку, заключенную в указанном диапазоне (включая концы) "abcdef"[0..3] "abcd"
[нач, дл] Возвращает подстроку, начинающуюся с указанной позиции и имеющую заданную длину "abcdef"[0,3] "abc"
capitalize Заменяет первый символ строки (если он является буквой латинского алфавита) на заглавную "abc".capitalize "Abc"
chop Удаляет указанные символы из строки, может указываться диапазон изменения символов
"abcdef".delete('ea')
"abcdef".delete('a-c')
"bcdf" "def"
index Определяет номер позиции, с которой начинается указанная подстрока; можно указывать номер позиции, с которой начинается поиск
"abcdabcd".index("cd")
"abcdabcd".index("cd",3)

2

6

length size Определяют длину строки (в байтах)
"12345678".length
"12345678".size

8

8

ljust rjust center Дополняют строку пробелами до указанной ширины, выравнивая соответственно по левому краю, по правому краю или по центру
"123".ljust(8)
"123".rjust(8)
"123".center(8)
"123     "
"     123"
"  123   "
reverse Возвращает строку, содержащую символы в обратном порядке "1234567".reverse "7654321"
strip Удаляет пробелы в начале и конце строки " 123 ".strip "123"
squeeze Оставляет в группе повторяющихся символов только один; допускается задание списка символов, на которых распространяется данное действие
"22---23**".squeeze
"22---23**".squeeze('*-')
"2-23*" "22-23*"
tr Заменяет все найденные вхождения символов на заданные "22+33=55".tr('25','47') "44+33=77"

Отдельного упоминания заслуживает метод eval, позволяющий динамически выполнять другие методы. Этот метод анализирует переданную ему строку, рассматривая ее как часть программы, и выполняет ее. Обратите внимание, что при вызове этого метода используется не точечная, а функциональная форма записи:

puts eval("2**10")
puts eval('"мол".size * "око".size')   # 9
#!/usr/bin/env ruby

puts 'hello'        # hello 
puts %q/hello/      # hello 
puts %q(hello)      # hello 

puts 'hell\\o'      #  hell\o  
puts %q(hell\\o)    #  hell\o  

puts 'hell\'o'      # hell'o  
puts %q(hell\'o)    # hell\'o  
puts 'hel"l"o'      # hel"l"o 

puts "hello"
puts %Q/hello/
puts %Q!hello!
puts %Q{hello}
puts %Q(hello)
puts %Q!hello!
puts %<hello>
puts %!hello!
puts %*hello*
puts %+hello+
puts %(hello)

puts "Say \"Hello\""   #  Say "Hello"  
puts "Say 'Hello'"     #  Say 'Hello' 

puts "a \t b"
puts 'a \t b'
puts "выражение 3*5+8 равно  #{3*5+8}"
puts 'выражение 3*5+8 равно  #{3*5+8}'
puts "работа с целыми числами: 5/8 = #{5/8}"
puts "перевод в класс Float: 5/8 = #{5/8.to_f}"

puts "-12.34".to_i
puts "12.34".to_f
puts "+12:34".to_i
puts "12qq34".to_f

puts 209.chr

puts "123456789".reverse
puts "  123       "
puts "  123  ".strip 
puts "---22--23****".squeeze('*-')
puts "---22--23****".squeeze

puts "Дашка любит молоко, а Танька - квас".tr('ДТ','МД')
puts "22+33=55".tr('25','47')

puts "abcdef"[0].chr
puts "abcdef".chop
puts "abcdef".chop.chop
puts "abcdef".delete("ea")
puts "abcdef".delete('a-c')
puts "abcdefabcdef".length
puts "12345678".size

puts "ABCDEF".downcase
puts "abcdef".index("cd")
puts "abcdefabcdef".index("cd",3)

a="12345"
puts "12345"[3].chr
puts  "12345"[0..3]
puts  "12345"[0,3]

puts  "привет".capitalize # Не работает с русскими буквами!
puts "abcdabcd".index("cd",3)
puts "*"+"123".ljust(9)+*"
puts "*"+"123".center(9)+"*"
puts "*"+"123".rjust(9)+"*"

puts (eval "2**10")
puts (eval ""мол".size * "око".size') 

=begin
s="2*3+4"
puts s
puts "#{s}"
puts eval(s)
puts eval s

puts s.eval # Ошибка! Неправильный вызов метода

print "Введите команды:"
eval gets.chop!
=end

Время и дата

Экземпляр класса Time в языке Ruby содержит информацию о дате, времени и временной зоне. Для создания объекта этого класса, содержащего текущие дату и время, используется метод now. Такие объекты могут являться аргументами операций + и -:

puts Time.now
puts Time.now+60   # добавили 60 секунд к текущему времени
puts Time.now-60   # отняли 60 секунд от текущего времени

Отметим некоторые методы этого класса, для вызова которых используется точечная нотация.

Метод Назначение метода
sec Получить число секунд
min Получить число минут
hour Получить число часов
mday и day Получить день месяца
mon и month Получить номер месяца
year Получить год
wday Получить номер дня недели
yday Получить номер дня в году
zone Получить информацию о временной зоне
to_i Получить число секунд, прошедших с 1 января 1970 года

В языке Ruby имеется метод sleep, который заставляет программу "заснуть" на число секунд, указанное в качестве аргумента метода. Посмотрите на пример использования методов для работы с объектами класса Time.

puts "До Нового года осталось #{365-Time.now.yday} дней"
puts Time.now
puts "Подождем 10 секунд."
sleep(10)
puts Time.now
#!/usr/bin/env ruby
puts Time.now
puts Time.now+60   # добавили 60 секунд к текущему времени
puts Time.now-60   # отняли 60 секунд от текущего времени

puts "До Нового года осталось #{365-Time.now.yday} дней"
puts Time.now
puts "Подождем 10 секунд."
sleep(10)
puts Time.now

Вывод данных

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

puts "puts всегда завершается переходом к новой строке."
print "А оператор print не делает этого по умолчанию, "
print "как вы видите в этом примере.\n"
print "print может быть вызван сразу  ", 
   "с несколькими аргументами.\n"

Оператор p подобен оператору puts и отображает объекты в виде, понятном человеку. Не следует использовать его для вывода русских букв - он печатает их ASCII-коды.

p Time.now, Time.now+3600

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

  • флаги (в любом порядке):

    - указывает на то, что преобразованный аргумент должен быть прижат к левому краю поля;

    + предписывает всегда печатать число со знаком;

    0 - указывает, что числа должны дополняться ведущими нулями до всей ширины поля;

    пробел - указывает, что числа должны дополняться ведущими нулями до всей ширины поля;

  • число, задающее минимальную ширину поля;
  • точка, отделяющая указатель ширины поля от указателя точности;
  • число, задающее точность.
  • Основные литеры-спецификаторы и разъяснение их смысла приводятся в следующей таблице.

    Литера Аргумент Вид печати
    d Fixnum, Bignum печать целого числа
    s String печать строки
    f Float печать дробного числа

    Включите в файл следующие операторы форматной печати и посмотрите на результаты выполнения:

    printf "%8s~~%-8s\n", "abcd", "abcd"
    printf "%-8s::%8s\n", "abcd", "abcd"
    printf "%06d\n", 2**10; printf "%+6d\n", 2**10
    printf "%6d\n", 2**10;  printf "%+6d\n", 2.5*1.3
    printf "%4.3f\n", 2.5*1.3
    printf "-2/7=%+1.6f, -2/6=%2.15f\n",
          -2/7.to_f, -2/6.to_f

    Обратите внимание, что строка формата заключается в двойные кавычки и может содержать специальные последовательности символов. Одной из них является \n. Эта последовательность дает указание интерпретатору Ruby продолжить вывод информации со следующей строки (newline). Другой часто используемой последовательностью является \t (табуляция), которая передвигает фокус вывода к следующей позиции табуляции.

    puts "*\t*\t*\t*\t*"
    puts " 1234567 1234567 1234567 1234567 "
    #!/usr/bin/env ruby
    puts "puts всегда завершается переходом к новой строке."
    print "А оператор print не делает этого по умолчанию, "
    print "как вы видите в этом примере.\n"
    print "print может быть вызван сразу  ", 
       "с несколькими аргументами.\n"
    
    p Time.now, Time.now+3600
    
    printf "%8s~~%-8s\n", "abcd", "abcd"
    printf "%-8s::%8s\n", "abcd", "abcd"
    printf "%06d\n", 2**10; printf "%+6d\n", 2**10
    printf "%6d\n", 2**10;  printf "%+6d\n", 2.5*1.3
    printf "%4.3f\n", 2.5*1.3
    printf "-2/7=%+1.6f, -2/6=%2.15f\n",
          -2/7.to_f, -2/6.to_f
    
    puts "*\t*\t*\t*\t*"
    puts " 1234567 1234567 1234567 1234567 "

    Переменные и константы

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

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

    Зарезервированные слова
    __FILE__ and def end in or self unless
    __LINE__ begin defined? ensure module redo super until
    BEGIN break do false next rescue then when
    END case else for nil retry true while
    alias class elsif if not return undef yield

    Кроме локальных переменных Ruby поддерживает и другие их виды - глобальные переменные, переменные классов и экземпляров. Принадлежность к каждому из этих видов определяются символом, стоящим перед именем переменной (префиксом): символ $ задает глобальную переменную, @ - переменную экземпляра, @@ - переменную класса. Локальные переменные не имеют префикса. Имена констант и классов должны начинаться с прописной латинской буквы (A-Z) и, аналогично переменным, продолжаются любой комбинацией латинских букв, цифр и символа подчеркивания.

    Локальная переменная создается динамически в момент, когда ей в процессе выполнения программы первый раз присваивается какое-то значение. Оператор присваивания имеет вид знака = (равно), например, a = 1; b = a; name = "Иван". Переменные могут использоваться во всех выражениях Ruby аналогично соответствующим объектам:

    a = "Привет всем!"
    puts a

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

    a = "one"
    puts a
    puts a.type
    a = 23
    puts a
    puts a.type

    Обратите внимание на метод type, возвращающий тип объекта. При первом использовании переменная a есть ссылка на объект класса String, затем мы присваиваем этой переменной другое значение (наклеиваем ту же этикетку на новый объект), после чего она становится ссылкой на экземпляр класса Fixnum.

    Ruby поддерживает также и присваивание с операцией. Результат выполнения следующих двух операторов одинаковы:

    a+=12
    a=a+12

    Общая форма подобных операторов выглядит так:

    выражение1 операция= выражение2

    что соответствует оператору

    выражение1 = выражение1 операция выражение2

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

    +, -, *, /, %, **, , |, ^, <<, >>, , |

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

    a, b, c = 1, 2, 3

    Пример

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

    a, b = b, a

    Данный пример объясняет, почему эту форму оператора присваивания называют также "параллельным присваиванием".

    Мы уже упоминали одну из замечательных возможностей Ruby - подстановку. Она позволяет выполнять разнообразные трюки со строкой, заключенной в двойные кавычки, например,

    magic = 42
    puts "Секретное число равно #{magic - 3}!!"

    Когда Ruby находит конструкцию #{выражение} внутри строки, заключенной в двойные кавычки, то заменяет эту ее часть на результат вычисления выражения.

    Пример

    Дано трехзначное число. Напечатать число, получающееся при перестановке цифр десятков и единиц заданного числа.

    number = 342
    puts "Исходное число равно #{number}"
    n1 = number/100                   # число сотен
    n2 = (number/10)%10               # число десятков
    n3 = number%10                    # число единиц
    answer = n1*100 + n3*10 + n2
    puts "Результат #{answer}"
    #!/usr/bin/env ruby
    
    number = 342
    puts "Исходное число равно #{number}"
    n1 = number/100                   # число сотен
    n2 = (number/10)%10               # число десятков
    n3 = number%10                    # число единиц
    answer = n1*100 + n3*10 + n2
    puts "Результат #{answer}"

    Иногда возникает необходимость указать, что тот или иной объект в программе является неизменяемым. Такие объекты называются константами. В Ruby имена констант, также как и имена классов, должны начинаться с заглавной буквы (есть определенная традиция задавать имена констант, состоящие из одних заглавных букв). Рассмотрим следующий фрагмент программы:

    NUM = 234
    p NUM      # 234
    NUM = "qq" # warning: already initialized constant NUM 
    p NUM      # "qq"

    Как видим, интерпретатор выдал предупреждение о том, что константа NUM уже была определена ранее. Тем не менее значение константы было изменено.

    Задания

  • Напишите программу, печатающую количество квадратов со стороной 130 мм, которое можно отрезать от прямоугольника со сторонами 543x130 мм.
  • Напишите программу, которая по данному числу a, не используя никаких арифметических операций, кроме умножения, получает a8 за три операции.
  • Операторы ветвления

    Условный оператор, называемый также оператором выбора, позволяет в зависимости от истинности или ложности некоторого условия выполнять ту или иную последовательность команд.

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

    Следует запомнить, что только false и nil трактуются как ложные в логическом контексте. Все остальное, что вычисляется, является истинным. Для того чтобы лучше понять смысл данного высказывания, выполните следующую программу:

    if 23
      puts "23 есть ИСТИНА"
    end
    
    if "qq"
      puts "И любая строка есть ИСТИНА"
    end

    Ruby поддерживает все стандартные логические операторы, а также дополнительный оператор defined?. Для конъюнкции двух логических выражений используются and и . Их результат будет истинен только если инстинны оба операнда. Эти функции вычисляют второй операнд лишь тогда, когда первый истинен, а отличаются они только своим приоритетом (приоритет оператора and меньше ).

    В свою очередь результатом выполнения операторов or и || (дизъюнкции) будет истина, если хотя бы один из операндов истинен. Они вычисляют второй оператор только в том случае, если первый окажется ложным. Как и в случае с оператором and, оператор or имеет меньший приоритет по сравнению с || (отметим, что оператор and имеет приоритет, равный приоритету оператора or, в то время как приоритет выше, чем у || ).

    Операторы ! возвращают значение, противоположное своему аргументу (если аргумент истинен, то результат ложен, и наоборот). Между собой отличаются только приоритетом.

    Оператор defined? возвращает nil, если его аргумент не определен, и его описание в противном случае. Ниже приведены примеры применения этого оператора.

    puts defined? 1
    puts defined? dummy
    puts defined? printf
    puts defined? (c, d = 1, 2)
    puts defined? 42.abs

    В логических выражениях допускаются также следующие операции сравнения: == <, <=, >=, > !=.

    Синтаксис условного оператора if в языке Ruby аналогичен его синтаксису в большинстве других языков программирования.

    # Определение метода оценки величины выражения
    def howBigPrint(i)
      if i < 10
        puts "Число  #{i} меньше 10"
      elsif i < 20
        puts "Число #{i} между 10 и 20"
      elsif i < 30
        puts "Число #{i} между 20 и 30"
      else
        puts "Число #{i} больше или равно 30"
      end
    end
    # Использование метода ...
    howBigPrint(7);  howBigPrint(15)
    howBigPrint(23); howBigPrint(105)

    Общая форма оператора if такова:

    if <логическое_выражение> [then]
        тело_оператора
    elsif <логическое_выражение> [then]
        тело_оператора
            ...
    else
        тело_оператора
    end

    Здесь <логическое_выражение> ; может быть любым фрагментом кода на языке Ruby, результатом вычисления которого является логическая величина (с учетом сказанного выше). Слово then отделяет тело оператора от условия. Запись его в квадратных скобках означает, что оно может быть опущено, если тело начинается с новой строки. Значение, возвращаемое оператором if, есть результат последнего вычисленного выражения. Изменим наш пример, чтобы продемонстрировать сказанное:

    # Метод оценки величины выражения
    # теперь возвращает строку
    def howBig(i)
      if i < 10 then "Аргумент меньше 10"
      elsif i < 20 then "Аргумент между 10 и 20"
      elsif i < 30 then "Аргумент между 20 и 30"
      else "Аргумент больше или равно 30"
      end
    end
    # Использование метода ...
    puts howBig(7);  puts howBig(15)
    puts howBig(23); puts howBig(105)

    Для выполнения одного варианта из двух возможных используется следующая форма оператора if:

    if <логическое_выражение> [then]
        тело_оператора
    else
        тело_оператора
    end

    В следующем фрагменте сравнивается значение переменной i с числом 100.

    if i < 100 
      puts "Мало"
    else
      puts "Значительно больше"
    end

    Оператор if может использоваться в правой части оператора присваивания, например,

    str = if i < 100 then "Мало" else "Побольше" end
    puts str

    Для аналогичных целей используется и тернарный оператор

    логическое_выражение ? выражение1 : выражение2

    Если логическое_выражение истинно, то выполняется выражение1, иначе - выражение2. Посмотрите на примеры использования этого оператора:

    i = 23
    i < 100 ? puts("Мало") : puts("Значительно больше")
    i = 1234
    s = i < 100 ? "Мало" : "Значительно больше"
    puts s

    Как видите, этот оператор особенно удобен, если требуется переменной присвоить одно из двух значений.

    Пример

    Напишите программу, определяющую четность введенного числа.

    print "Введите целое число: "
    a = gets.to_i
    str = a%2 == 0 ? "четно" : "нечетно"
    puts "Число #{a} " + str

    Если требуется выполнить последовательность операторов только в том случае, когда выполнено некоторое условие, то используется форма

    if <логическое_выражение> [then]
        тело_оператора
    end

    Для этих же целей можно использовать и модифицированный оператор if:

    выражение if <логическое_выражение>

    Ниже приводятся две версии фрагмента программы, использующей оператор if:

    if radiation > 3000
       puts "Радиационная опасность!"
    end
    # то же самое
    puts "Радиационная опасность!" if radiation > 3000

    Пример

    Напишите программу, определяющую, является ли введенная фраза палиндромом (перевертышем).

    puts "Введите фразу:"
    a = gets.chop!.delete(' ')     # удалили все пробелы
    a = a.tr('A-Z,А-Я', 'a-z,а-я') # замена букв на прописные
    str = "палиндром"
    str = "не " + str if a != a.reverse # проверка
    puts "Введенная фраза - " + str + "."

    При выборе из большего числа альтернатив удобнее использовать оператор case. Ниже приведен его общий вид (части, заключенные в квадратные скобки, могут быть опущены).

    case <выражение>
    when  <тест1> [then]
        ...
    when  <тест2> [then]
        ...
    when  <тестN> [then]
        ...
    [else
        ... ]
    end

    В операторе case <выражение> последовательно сравнивается с выражениями <тест> до тех пор пока, оно не совпадет с одним из них (сравнение производится при помощи операции == ), после чего выполняется соответствующий фрагмент кода. Оператор case возвращает значение последнего вычисленного выражения, или nil, если такого не было.

    s = case i
        when   0 .. 9
           "однозначное"
        when  10 .. 99
           "двузначное"
        when 100 .. 999
           "трехзначное"
        else
           "огромное"
        end
    puts "Введено #{s} число."

    Здесь мы снова воспользовались оператором .. (двоеточие), который возвращает список целых чисел, заключенных между левым и правым операндами (включая их самих).

    #!/usr/bin/env ruby
    if 23
      puts "23 есть ИСТИНА"
    end
    
    if "qq"
      puts "И любая строка есть ИСТИНА"
    end
    puts
    
    # Определение метода оценки величины выражения
    def howBigPrint(i)
      if i < 10
        puts "Число  #{i} меньше 10"
      elsif i < 20
        puts "Число #{i} между 10 и 20"
      elsif i < 30
        puts "Число #{i} между 20 и 30"
      else
        puts "Число #{i} больше или равно 30"
      end
    end
    # Использование метода ...
    howBigPrint(7);  howBigPrint(15)
    howBigPrint(23); howBigPrint(105)
    puts
    
    # Метод оценки величины выражения
    # теперь возвращает строку
    def howBig(i)
      if i < 10 then "Аргумент меньше 10"
      elsif i < 20 then "Аргумент между 10 и 20"
      elsif i < 30 then "Аргумент между 20 и 30"
      else "Аргумент больше или равно 30"
      end
    end
    # Использование метода ...
    puts howBig(7);  puts howBig(15)
    puts howBig(23); puts howBig(105)
    
    puts
    i = 23
    i < 100 ? puts("Мало") : puts("Значительно больше")
    i = 1234
    s = i < 100 ? "Мало" : "Значительно больше"
    puts s
    
    puts "Перевертыш"
    
    puts "Введите фразу:"
    a = gets.chop!.delete(' ')     # удалили все пробелы
    a = a.tr('A-Z,А-Я', 'a-z,а-я') # замена букв на прописные
    str = "палиндром"
    str = "не " + str if a != a.reverse # проверка
    puts "Введенная фраза - " + str + "."
    
    print "Введите неотрицательное целое число: "
    i= gets.to_i
    s = case i
        when   0 .. 9
           "однозначное"
        when  10 .. 99
           "двузначное"
        when 100 .. 999
           "трехзначное"
        else
           "огромное"
        end
    puts "Введено #{s} число."

    Задания

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

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

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

    while <выражение> [do]
            ...
         тело цикла
            ...
    end

    Другим оператором цикла является until, выполняющийся до тех пор, пока его условие ложно:

    until <выражение> [do]
            ...
         тело цикла
            ...
    end

    Пример Рассмотрим программу, печатающую числа от 1 до 5. Сначала используем оператор while, затем until. Обратите внимание, что условие окончания одного оператора цикла является отрицанием условия другого оператора.

    i=1
    while i <= 5
      puts i; i += 1
    end
    
    # еще раз
    i=1
    until i > 5
      puts i; i += 1
    end

    Кроме этих двух операторов цикла в Ruby имеется большое число, так называемых, итераторов (iterate - повторять). Давайте посмотрим на примеры их использования. Конструкция

    3.times do
       print "Ау! "
    end

    использует итератор times. Цикл, заданный таким образом, выполнится ровно три раза.

    В случае, когда нужно выполнить некоторые действия, зависящие от изменяемой величины, несколько раз, можно использовать итератор upto, так в процессе выполнения программы

    0.upto(9) do |x| print x, " " end

    будет напечатано 0 1 2 3 4 5 6 7 8 9.

    Повтор от 0 до 12 с шагом 3 можно записать при помощи итератора step:

    0.step(12, 3) {|x| print x, " " } # 0 3 6 9 12

    При работе с массивами удобно использовать итератор each:

    [1, 1, 2, 3].each {|k| print k, " " } # 1 1 2 3

    Итератор for in очень похож на each, например, вывод, полученный в результате выполнения следующих двух конструкций одинаков.

    for i in  ["one", "two", "three"]
      print i, " "
    end
    
    # то же самое
    ["one", "two", "three"].each{ |i| print i, " "}

    Итератор for обычно используют там же, где и итератор each - при работе с массивами и диапазонами. Общий вид оператора for таков:

    for <переменная> in <выражение> [do]
       тело_итератора
    end

    Пример

    Перепишем программу печати чисел от 1 до 5 с использованием оператора for:

    for i in 1 .. 5
      puts i
    end

    В этом примере мы снова воспользовались оператором задания диапазона .. (двоеточие), который позволил нам создать список чисел от 1 до 5 (включительно). Похожий оператор ... (троеточие) при создании диапазона не включает в него правый операнд. Фрагмент программы, расположенный ниже, эквивалентен предыдущему.

    for i in 1 ... 6
      puts i
    end

    Пример

    Рассмотрим несколько вариантов программы, вычисляющей факториал введенного числа.

  • Использование оператора while
    print "Введите целое положительное число: "
    str = gets.chop! # ввели строку
    num = str.to_i   # преобразовали в число
    if num > 0
      i = 1
      fact = 1
      while i <= num
        fact *= i
        i += 1
      end
      puts "Факториал числа #{num} равен #{fact}"
    else
      puts "Вы ввели неположительное число"
    end
  • Использование оператора for
    print "Введите целое положительное число: "
    num = gets.to_i # строку сразу преобразовали в число
    if num > 0
      fact = 1
      for i in 1 .. num
        fact *= i
      end
      puts "Факториал числа #{num} равен #{fact}"
    else
      puts "Вы ввели неположительное число"
    end
  • Использование функции для определения факториала
    def fact(n)
      f = 1
      1.step(n,1) {|k| f *= k}
      return f
    end
    print "Введите целое положительное число: "
    if (num = gets.to_i) > 0
      print "#{num}! = #{fact(num)}\n"
    else
      puts "Факториал числа #{num} не определен\n"
    end
  • В последнем примере мы оформили вычисление факториала в виде функции и использовали итератор step, который обеспечил изменение переменной k с шагом 1.

    Пример

    Приведенная ниже программа запрашивает целое положительное число и определяет количество цифр в нем (обратите внимание на множественное присваивание).

    print "Введите целое положительное число: "
    a, k = gets.to_i, 0
    while a>0
      a /= 10; k += 1 # отбросили последнюю цифру
    end
    print "Количество цифр в введенном числе равно #{k}.\n"

    Пример

    Пусть нужно ввести с клавиатуры массив чисел и напечатать сумму всех элементов. Приведем несколько решений этой задачи.

  • Сначала введем количество элементов массива, а затем заполним его элементами, одним за другим. Обратите внимание, что первый элемент массива имеет индекс 0, а последний - на единицу меньший, чем размерность массива.
    print "Введите число элементов массива: "
    sn = gets.chop!; n = sn.to_i
    b = Array.new(n)   # создали массив из n элементов  
    s = 0              # обнулили сумму
    for i in 0 .. n - 1
      print "Введите #{i+1}-й элемент массива: "
      b[i] = gets.chop!.to_f; s = s + b[i]
    end
    print "Сумма всех элементов массива равна ", s, "\n"
  • В этой версии программы все числа вводятся сразу в виде одной строки, в которой числа отделяется друг от друга пробелом (например, 23 -34.67 100.5).
  • Встроенный метод split разделяет строку на элементы массива, аргументом этого метода является разделитель (если разделителем является пробел, то можно вызвать этот метод без аргумента). Таким образом, если бы мы договорились разделять числа, например, точкой с запятой, то вызов метода выглядел бы так: a.split(';').

    Для определения длины массива мы применили метод length (можно заменить на эквивалентный ему метод size ).

    puts "Введите массив чисел (разделяя их пробелами):"
    a = gets.chop!
    b = a.split # разбили строку на отдельные числа
    s = 0
    for i in 0 .. b.length - 1
      s += b[i].to_f
    end
    puts "Сумма всех элементов массива равна #{s}"

    В Ruby имеются четыре конструкции, задаваемые ключевыми словами break, redo, next и retry, которые изменяют обычный порядок выполнения циклов. Их действие описано в следующей таблице.

    break Немедленно прекращает выполнение цикла; управление передается на утверждение, расположенное сразу за циклом
    redo Повторяет тело цикла с начала, не пересчитывая условие выполнения цикла (не переходя к следующему элементу в случае итератора)
    next Пропускает часть тела цикла, следующую за ним, и переходит к следующей итерации
    retry Начинает выполнение цикла с самого начала

    Рассмотрим на примере итератора for действие указанных конструкций.

    for i in 1 .. 5
      print  i
      break if i == 3
      print "*"
    end
    Результат: 1*2*3
    for i in 1 .. 5
      print  i
      redo if i == 3
      print "*"
    end
    Результат: 1*2*33333 ... выполнение цикла не прекращается
    for i in 1 .. 5
      print  i
      next if i == 3   
      print "*"
    end
    Результат: 1*2*34*5*
    for i in 1 .. 5
      print  i
      retry if i == 3
      print "*"
    end
    Результат: 1*2*31*2*31*2*... выполнение цикла не прекращается

    Пример

    Следующая программа начинает повторение цикла сначала, если при вводе указать символ y.

    for i in 1 .. 5
      print "Now at #{i}. Restart?(y/n) "
      retry if gets.chop == "y"
    end

    Вот один из возможных вариантов выполнения этой программы:

    Now at 1. Restart?(y/n) n
    Now at 2. Restart?(y/n) y
    Now at 1. Restart?(y/n) n
            ...
    #!/usr/bin/env ruby
    i=1
    while i <= 5
      puts i; i += 1
    end
    puts
    
    # еще раз
    i=1
    until i > 5
      puts i; i += 1
    end
    puts
    
    3.times do
       print "Ау! "
    end
    puts
    
    0.upto(9) do |x| print x, " " end 
    puts
    
    0.step(12, 3) {|x| print x, " " } # 0 3 6 9 12
    puts
    
    [1, 1, 2, 3].each {|k| print k, " " } # 1 1 2 3 
    puts
    
    for i in  ["one", "two", "three"]
      print i, " "
    end
    puts
    
    # то же самое
    ["one", "two", "three"].each{ |i| print i, " "}
    puts
    
    for i in 1..5
      puts i
    end
    puts
    
    for i in 1 ... 6
      puts i
    end
    puts
    
    print "Введите целое положительное число: "
    str = gets.chop! # ввели строку
    num = str.to_i   # преобразовали в число
    if num > 0
      i = 1
      fact = 1
      while i <= num
        fact *= i
        i += 1
      end
      puts "Факториал числа #{num} равен #{fact}"
    else
      puts "Вы ввели неположительное число"
    end
    puts
    
    print "Введите целое положительное число: "
    num = gets.to_i # строку сразу преобразовали в число
    if num > 0
      fact = 1
      for i in 1 .. num
        fact *= i
      end
      puts "Факториал числа #{num} равен #{fact}"
    else
      puts "Вы ввели неположительное число"
    end
    puts
    
    def fact(n)
      f = 1
      1.step(n,1) {|k| f *= k}
      return f
    end
    print "Введите целое положительное число: "
    if (num = gets.to_i) > 0
      print "#{num}! = #{fact(num)}\n"
    else
      puts "Факториал числа #{num} не определен\n"
    end
    
    puts
    print "Введите целое положительное число: "
    a, k = gets.to_i, 0
    while a>0
      a /= 10; k += 1 # отбросили последнюю цифру
    end
    print "Количество цифр в введенном числе равно #{k}.\n"
    puts
    
    print "Введите число элементов массива: "
    sn = gets.chop!; n = sn.to_i
    b = Array.new(n)   # создали массив из n элементов  
    s = 0              # обнулили сумму
    for i in 0 .. n - 1
      print "Введите #{i+1}-й элемент массива: "
      b[i] = gets.chop!.to_f; s = s + b[i]
    end
    print "Сумма всех элементов массива равна ", s, "\n"
    puts
    
    puts "Введите массив чисел (разделяя их пробелами):"
    
    a = gets.chop!
    b = a.split # разбили строку на отдельные числа
    s = 0
    for i in 0 .. b.length - 1
      s += b[i].to_f
    end
    puts "Сумма всех элементов массива равна #{s}"
    puts
    
    =begin
    
    for i in 1 .. 5
      print  i
      break if i == 3
      print "*"
    end
    puts
    
    
    for i in 1 .. 5
      print  i
      redo if i == 3
      print "*"
    end
    puts
    
    for i in 1 .. 5
      print  i
      next if i == 3   
      print "*"
    end
    puts
    
    for i in 1 .. 5
      print  i
      retry if i == 3
      print "*"
    end
    puts
    
    =end
    
    for i in 1 .. 5
      print "Now at #{i}. Restart?(y/n) "
      retry if gets.chop == "y"
    end
    puts

    Задания

  • Напишите программу, вычисляющую сумму всех четных натуральных чисел, не превосходящих 1000.
  • Напишите программу, определяющую максимальный элемент массива чисел.
  • Примеры программ

    НОД и алгоритм Евклида

    При рассмотрении задач, связанных с обработкой целых чисел, часто приходится сталкиваться с понятием НОД - наибольшего общего делителя чисел. Известно много алгоритмов вычисления НОД, мы рассмотрим лишь два из них.

    Задача

    Напишите программу, вычисляющую НОД(a,b) - наибольший общий делитель двух введенных с клавиатуры неотрицательных целых чисел a и b, не равных нулю одновременно.

    Вариант 1

    Определим максимум из этих чисел и, последовательно уменьшая его, будем искать число, на которое делятся и a и b. Программа обязательно завершит свою работу, так как в самом неудачном случае, когда эти числа взаимно просты, k станет равно 1, а на 1 делятся все числа, следовательно выполнение цикла прекратится.

    print "Введите первое число: "; a = gets.to_i
    print "Введите второе число: "; b = gets.to_i
    
    k = a >= b ? a : b # теперь k -  максимум 
    until (a % k == 0) and (b % k == 0)
      k -= 1
    end
    print "НОД(#{a},#{b}) = #{k}\n"

    Вариант 2 - алгоритм Евклида

    Алгоритм Евклида основан на следующих свойствах НОД: для всех a и b, больших или равных 0, выполнены равенства

    НОД(a, b) = НОД(a - b, b)= НОД(a, b - a); 
    НОД(a, 0) = НОД(0, a) = a

    Ниже приведен текст программы, реализующей алгоритм Евклида.

    print "Введите первое число: "; a = gets.to_i
    print "Введите второе число: "; b = gets.to_i
    m, n = a, b
    while  !((m == 0) || (n == 0))
      if m >= n
        m = m - n
      else
        n = n - m
      end
    end
    
    k = m == 0 ? n : m
    print "НОД(#{a},#{b}) = #{k}\n"
    #!/usr/bin/env ruby
    
    # 1
    
    print "Введите первое число: "; a = gets.to_i
    print "Введите второе число: "; b = gets.to_i
    
    k = a >= b ? a : b # теперь k -  максимум 
    
    
    until (a%k == 0) (b%k == 0)
      k-=1
    end
    print "НОД(#{a},#{b}) = #{k}\n"
    
    #2 Алгоритм Евклида
    
    print "Введите первое число: "; a = gets.to_i
    print "Введите второе число: "; b = gets.to_i
    m, n = a, b
    while  !((m == 0) || (n == 0))
      if m >= n
        m = m - n
      else
        n = n - m
      end
    end
    
    k = m == 0 ? n : m
    print "НОД(#{a},#{b}) = #{k}\n"

    Аргументы командной строки

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

    =begin
    Напишите программу подсчета суммы всех нечетных
    чисел, заключенных в интервале от K до L, 
    где  K и L - аргументы командной строки
    =end
    
    if ARGV.length < 2 
      puts "Слишком мало входных данных"
      exit(1)
    end
    
    s = 0
    k = ARGV[0].to_i
    l = ARGV[1].to_i
    for i in k .. l
      s += i if i%2 != 0
    end
    puts "Сумма нечетных чисел в интервале " +
         "от #{k} до #{l} равна #{s}"

    Для экстренного прерывания процесса выполнения программы здесь использован встроенный метод exit.

    #!/usr/bin/env ruby
    =begin
    Напишите программу подсчета суммы всех нечетных
    чисел, заключенных в интервале от K до L, 
    где  K и L - аргументы командной строки
    =end
    
    if ARGV.length < 2 
      puts "Слишком мало входных данных"
      exit(1)
    end
    
    s = 0
    k = ARGV[0].to_i
    l = ARGV[1].to_i
    for i in k .. l
      s += i if i%2 != 0
    end
    puts "Сумма нечетных чисел в интервале " +
         "от #{k} до #{l} равна #{s}"

    Задача

    Числами Фибоначчи называется последовательность целых чисел, задаваемая соотношениями: а0 = 0, a1 = 1, an = an-1+an-2. Напишите программу, получающую в качестве аргумента командной строки целое число n, и печатающую nчисло Фибоначчи.

    Вариант 1 - рекурсивная функция

    В следующей программе первый элемент массива ARGV преобразовывается в целое число и передается в качестве аргумента рекурсивной функции fib.

    def fib(n)
      if n<2
        n
      else
        fib(n-2)+fib(n-1)
      end
    end
    n = ARGV[0].to_i
    puts "#{n}-е число Фибоначчи равно #{fib(n)}"

    Для вычисления с помощью этой программы, размещенной в файлe fib.rb, например, 30-го числа Фибоначчи достаточно выполнить команду

    ruby fib.rb 30

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

    #!/usr/bin/env ruby
    
    =begin
    Напишите программу, получающую с качестве аргумента командной строки целое
    число n, и печатающую n-е  число Фибоначчи. 
    =end
    
    def fib(n)
      if n<2
        n
      else
        fib(n-2)+fib(n-1)
      end
    end
    t1 = Time.now
    n = ARGV[0].to_i
    puts "#{n}-е число Фибоначчи равно #{fib(n)}"
    t2 = Time.now
    puts "Время расчета около #{(t2-t1).round} сек."

    Вариант 2 - использование массива

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

    n =ARGV[0].to_i
    f= [0, 1]
    fib = case n
          when   0
    	f[0]
          when  1
    	f[1]
          else
    	for i in 2 .. n
    	  f[i] = f[i-1] + f[i-2]
    	end
    	f[n]
          end
    
    puts "#{n}-е число Фибоначчи равно #{fib}"
    #!/usr/bin/env ruby
    
    =begin
    Напишите программу, получающую с качестве аргумента командной строки целое
    число n, и печатающую n-е  число Фибоначчи. 
    =end
    t1 = Time.now
    n =ARGV[0].to_i
    f= [0, 1]
    fib = case n
          when   0
    	f[0]
          when  1
    	f[1]
          else
    	for i in 2 .. n
    	  f[i] = f[i-1] + f[i-2]
    	end
    	f[n]
          end
    puts "#{n}-е число Фибоначчи равно #{fib}"
    t2 = Time.now
    puts "Время расчета около #{(t2-t1).round} сек."
    a = fib.to_s.split('")
    puts "Количество цифр в #{n}-м числе Фибоначчи равно #{a.size}."

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

    n =ARGV[0].to_i
    if n > 1
      prev, beforePrev = 1, 0
      for i in 2 .. n
        fib  = prev + beforePrev
        prev, beforePrev = fib, prev
      end
    else 
      fib = n
    end
    puts "#{n}-е число Фибоначчи равно #{fib}"
    #!/usr/bin/env ruby
    
    =begin
    Напишите программу, получающую с качестве аргумента командной строки целое
    число n, и печатающую n-е  число Фибоначчи. 
    =end
    t1 = Time.now
    n =ARGV[0].to_i
    if n > 1
      prev, beforePrev = 1, 0
      for i in 2 .. n
        fib  = prev + beforePrev
        prev, beforePrev = fib, prev
      end
    else 
      fib = n
    end
    puts "#{n}-е число Фибоначчи равно #{fib}"
    t2 = Time.now
    puts "Время расчета около #{(t2-t1).round} сек."
    a = fib.to_s.split("')
    puts "Количество цифр в #{n}-м числе Фибоначчи равно #{a.size}."

    Включите в рассмотренные программы операторы, информирующие о продолжительности вычислений, и сравните их эффективность. Для этого найдите с помощью первой из рассмотренных программ 30, 31 и 32 числа Фибоначчи. Пользуясь второй и, тем более, третьей вы сможете вычислить достаточно большое число Фибоначчи, например, для вычисления 60000-го числа Фибоначчи, содержащего 12539 цифр на компьютере с процессором Celeron-500, программе, размещающей промежуточные результаты в массиве, потребовалось около 19 секунд, в то время как последняя программа вычислила его за 2 секунды.

    Работа с файлами

    Ввод данных с клавиатуры в процессе выполнения программы удобен только в случае небольших объемов. В других ситуациях необходимо чтение информации из заранее подготовленного файла.

    Для работы с файлом при помощи метода new создается экземпляр класса File. Обязательным аргументом этого метода является строка, содержащая имя файла, например,

    f = File.new("myfile.txt")

    Другим (необязательным) аргументом является задание режима работы с файлом. По умолчанию этот параметр имеет значение " r ", что соответствует режиму "только чтение". Если требуется открыть файл с возможностью записи в него, то следует указать параметр " w ", например,

    f1 = File.new("newfile.txt", "w")

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

    Для вывода строки в выходной поток (в файл или на экран монитора) используется метод write, например,

    a = "Hello, world!"
    f1.write(a)

    Задача

    Имеется текстовый файл fio.txt, содержащий список фамилий, имен и отчеств учащихся. При этом каждая строка файла содержит данные только об одном человеке, например,

    Петров Сергей Васильевич
    Сидорова Ольга Петровна
    Иванова Марья Даниловна

    Напишите программу, которая читает информацию из файла и

  • печатает пронумерованный список учеников;
  • печатает пронумерованный список фамилий и инициалов.
  • Решение 1

    f = File.new("fio.txt")
    n = 1
    student = f.readlines
    student.each{ |i|
      print n, ". ", i
      n += 1
    }

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

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

    Решение 2

    f = File.new("fio.txt")
    n = 1
    student = f.readlines
    student.each{ |i|
      i.chop!
      fio = i.split
      print n, ". ", fio[0], " ", fio[0][0].chr, ". ",
        fio[1][0].chr, ".\n" 
      n += 1
    }
    #!/usr/bin/env ruby
    
    # 1
    f = File.new("fio.txt")
    n = 1
    student = f.readlines
    student.each{ |i|
      print n, ". ", i
      n += 1
    }
    
    puts
    # 2
    f = File.new("fio.txt")
    n = 1
    student = f.readlines
    student.each{ |i|
      i.chop!
      fio = i.split
      print n, ". ", fio[0], " ", fio[0][0].chr, ". ",
        fio[1][0].chr, ".\n" 
      n += 1
    }

    Случайные числа

    Моделирование случайных процессов на компьютере - достаточно распространенное явление. Как и в большинстве языков программирования в Ruby есть методы, генерирующие, так называемые, "псевдослучайные" числа (их нельзя считать чисто случайными, ведь создаются они при помощи некоторого алгоритма).

    Для получения случайного целого числа в диапазоне от 0 до n используется метод rand(n). Случайное число в диапазоне от 0 до 1 получается с помощью этого же метода без указания параметра.

    a1 = (1..10).collect { |j| rand(100)}
    a2 = (1..4).collect { |j| rand()}
    p a1, a2

    Ниже представлен возможный вывод:

    [64, 17, 21, 95, 58, 24, 29, 60, 47, 63]
    [0.2009671596, 0.8890923676, 0.3349569312, 0.8719313448]

    Одним из применений случайных чисел является компьютерное моделирование метода Монте-Карло для определения площади некоторой фигуры, рассмотренный нами ранее. Напомним, что для применения этого метода фигуру вписывают в другую, известной площади, и случайным образом "бросают" точки, подсчитывая число попаданий в фигуру. При достаточно большом числе испытаний отношение числа точек, попавших внутрь фигуры, к общему числу точек, стремится к отношению их площадей.

    Задача

    Напишите программу, вычисляющее приближенное значение числа PI при помощи метода Монте-Карло.

    Решение

    Число PI равно отношению площади круга к квадрату его радиуса. Впишем круг единичного радиуса с центром в начале координат в квадрат и методом Монте-Карло найдем его площадь. Координаты случайных точек, бросаемых в круг, принадлежат интервалу (-1; 1). Величина, случайно распределенная в этом интервале, задается выражением 2*rand() - 1.

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

    puts "Введите количество точек:"
    n1, n, t1  = 0, eval(gets.chop), Time.now
    for i in 1 .. n
      x = 2*rand() - 1
      y = 2*rand() - 1
    #      проверяем попадание внутрь круга
      n1 += 1 if (x**2 + y**2) < 1
    end
    puts "PI=#{4.0*n1/n}"
    t2 = Time.now
    puts "Число точек #{n}, время расчета " +
      "около #{(t2 - t1).round} сек."

    Возможные результаты работы программы будут похожи (случайность!) на следующие:

    Введите количество точек: 10**5    PI=3.14028
    Число точек 100000, время расчета около 2 сек.
    
    Введите количество точек: 10**6    PI=3.142204
    Число точек 1000000, время расчета около 20 сек.
    #!/usr/bin/env ruby
    
    # Метод Монте-Карло
    puts "Введите количество точек:"
    n1, n, t1  = 0, eval(gets.chop), Time.now
    for i in 1 .. n
      x = 2*rand() - 1
      y = 2*rand() - 1
    #      проверяем попадание внутрь круга
      n1 += 1 if (x**2 + y**2) < 1
    end
    puts "PI=#{4.0*n1/n}"
    t2 = Time.now
    puts "Число точек #{n}, время расчета " +
      "около #{(t2 - t1).round} сек."

    Разработка пользовательских классов

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

    Задача

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

    Решение

    Поместите в файл с именем car.rb следующую программу:

    class Car
        @@NUM_CARS = 0
        def initialize
            @@NUM_CARS = @@NUM_CARS +  1
            puts @@NUM_CARS
        end
    end
    class SportsCar < Car
    end
    class FamilyCar < Car
    end
    
    a = Car.new
    b = SportsCar.new
    c = FamilyCar.new

    Для запуска программы выполните команду ruby car.rb. При создании каждого автомобиля, независимо от того, к какому (родительскому или дочернему) классу он принадлежит, значение переменной класса @@NUM_CARS увеличивается на единицу.

    #!/usr/bin/env ruby
    
    class Car
        @@NUM_CARS = 0
        def initialize
            @@NUM_CARS = @@NUM_CARS +  1
            puts @@NUM_CARS
        end
    end
    class SportsCar < Car
    end
    class FamilyCar < Car
    end
    
    a = Car.new
    b = SportsCar.new
    c = FamilyCar.new

    Задания

  • Напишите программу, запрашивающую с клавиатуры натуральное число, большее 1, и печатающую список всех простых несократимых дробей, заключенных в интервале между 0 и 1, знаменатели которых не превышают введенное число. Например, если ввести число 4, то должна быть напечатана последовательность 1/2, 1/3, 2/3, 1/4, 3/4. Подсказка: используйте функцию нахождения НОД.
  • Создайте текстовый файл, в котором разместите фамилии учащихся и их рост в сантиметрах. Напишите программу
  • печатающую фамилию и рост самых высоких учеников;
  • по введенному числу - росту с сантиметрах - печатающую список всех учащихся, чей рост не превышает введенного числа.
  • Напишите программу, вычисляющую методом Монте-Карло площадь криволинейной трапеции, ограниченной графиками функций y=sin(x) и y=0 ( x изменяется в интервале от 0 до PI ).
  • Страницы:

    Язык программирования Ruby

    Ruby - один из самых молодых языков программирования. Своим именем он обязан драгоценному камню рубину (по аналогии с другим широко распространенным языком программирования Perl - жемчуг). Вот как описывает Ruby его создатель, японский программист Юкихиро Мацумото (Yukihiro Matsumoto): "Это мощный и динамический объектно-ориентированный язык с открытыми исходниками, который я начал разрабатывать в 1993 году. Ruby работает на многих платформах, включая Linux и многие реализации Unix, MS-DOS, Windows 9x/2000/NT, BeOS и MacOS. Главная цель Ruby - эффективность разработки программ, и пользователи найдут, что программирование на нем эффективно и даже забавно".

    В Японии Ruby сильно потеснил такие известные языки как Python и Perl (а книга "Ruby the Object-Oriented Scripting Language" стала бестселлером) и начал распространяться по всему миру. За последний год появились три англоязычных книги, посвященные Ruby (к сожалению, пока не имеющие русского перевода). У этого языка очень неплохие шансы стать действительно популярным - ведь он вобрал в себя достоинства других языков, учтя их недостатки.

    Ruby входит в стандартную поставку ОС GNU\Linux (начиная с версии 7.2), а пользователям MS Windows для первого знакомства стоит порекомендовать его несколько устаревшую версию (1,2 Мбайт), легко умещающуюся на одной дискете, которая включает, помимо интерпретатора языка и библиотек, руководство пользователя, FAQ (ответы на часто задаваемые вопросы) и множество примеров. Ruby является свободно распространяемым продуктом, поэтому вы можете не беспокоиться ни о его стоимости, ни об ограничениях в его использовании.

    Этот язык несомненно является одним из лучших в качестве первого языка программирования, изучаемого студентами и школьниками. Быстрый цикл разработки (редактирование - запуск - редактирование), использование интерпретатора, изначальная объектно-ориентированность языка, нетипизированные переменные, которые не требуют объявления - все это позволяет учащимся сконцентрировать свое внимание на общих принципах программирования. В дальнейшем изложении мы будем ориентироваться на работу в ОС Linux. Использование Ruby в других операционных системах практически ничем не отличается, а результаты выполнения не зависят от используемой ОС.

    Сначала проверим, установлен ли интерпретатор Ruby в системе или нет. В окне shell введите команду ruby -v (этот ключ требует вывода версии языка). Если следующее сообщение появится, то Ruby установлен (версия, дата и платформа могут отличаться):

    ruby 1.6.4 (2001-06-04) [i386-linux-gnu]

    Файлы, содержащие программы на языке Ruby, обычно имеют расширение rb. По давней программистской традиции наша первая программа будет печатать фразу "Hello, World!". При помощи любого редактора plain-текста (emacs, kwrite, notebook и т. п.) создадим файл hello.rb, в который поместим текст

    puts "Hello, World!"

    Для выполнения этой программы в командной строке введите

    ruby hello.rb

    В результате выполнения программы в командном окне будет напечатана требуемая фраза.

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

    #!/usr/bin/env ruby

    Она обязательно должна начинаться с первой позиции. Затем нужно изменить права доступа файла с программой, сделав его исполняемым: chmod +x hello.rb. Теперь для запуска программы достаточно ввести команду

    ./hello.rb

    Для того чтобы сделать программу более понятной человеку, который ее читает, вставляются комментарии. Однострочные комментарии начинаются с символа # и продолжаются до конца строки. Многострочные комментарии заключают в специальные "скобки" - все, что располагается между строками =begin и =end, считается комментарием. Например,

    #!/usr/bin/env ruby
    =begin
    Это
     комментарий
    =end
    puts "Hello, World!"
    # Это тоже комментарий

    Программа на языке Ruby, часто называемая скриптом, есть последовательность инструкций (утверждений, предложений). Каждая инструкция по умолчанию заканчивается концом строки. Если же по каким-либо причинам требуется разместить несколько инструкций на одной строке, то их нужно разделять символом ; (точка с запятой). С другой стороны, иногда инструкция не помещается на одной строке. В этом случае символ \ сигнализирует о том, что ее продолжение располагается в следующей строке.

    Пример

    #!/usr/bin/env ruby
    # Инструкция заканчивается концом строки
    puts "Hello, World!"
    
    # Несколько инструкций в одной строке
    puts "Это тест, "; puts "демонстрирующий работу Ruby."
    
    # Незаконченная инструкция, 
    # продолжение которой на следующей строке
    puts "Программирование на Ruby - " +
      "приятное занятие."
    
    # Утверждение, разделенное на несколько строк
    puts \
           "И мы обязательно этому научимся!"

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

    Далее строка #!/usr/bin/env ruby в тексты программ не включается.

    Объекты и методы

    При описании Ruby было отмечено, что он является объектно-ориентированным языком программирования. Это означает что все, с чем работает программа, является объектом, при этом вычисление осуществляется посредством взаимодействия объектов. Каждый объект есть представитель (экземпляр) некоторого класса и его поведение (функциональность) определяется именно им. Тем самым все объекты, которые являются экземплярами одного класса, могут выполнять одни и те же действия, называемые методами. Для того чтобы применить метод к некоторому объекту используется оператор вызова метода, обозначаемый символом . (точка): после указания объекта ставится точка, а затем указывается применяемый метод.

    Пока мы не будем вдаваться в особенности объектно-ориентированного программирования (тем более, что в различных языках программирования по разному трактуется концепция ООП), а лишь познакомимся с некоторыми "простейшими" объектами, без которых невозможно приступать к дальнейшему изучению языка. Это представители так называемых "встроенных" классов. Среди них:

    true - логическая величина, означающая истину; единственный представитель класса TrueClass ;

    false - логическая величина, означающая ложь; единственный представитель класса FalseClass ;

    числа - представители класса Numeric, который в качестве подклассов содержит дробные ( Float ) и целые числа ( Integer );

    строки - представители класса String ;

    nil - "ничто"; единственный представитель класса NilClass.

    моменты времени - представители класса Time.

    Числа

    Знакомство с представителями различных классов начнем с чисел. Целые числа в Ruby являются экземплярами класса Integer, который объединяет два подкласса: Fixnum или Bignum. Объектами класса Fixnum являются те целые, чье двоичное представление способно разместится в машинном слове (31 бит на большинстве компьютеров). Если число выходит за границы указанного диапазона, то оно автоматически преобразуется в объект класса Bignum, чей диапазон изменения ограничивается только объемом доступной памяти. Если в результате работы с объектами класса Bignum итоговое значение попадает в диапазон, задаваемый классом Fixnum, то выдаваемый результат преобразуется в экземпляр класса Fixnum.

    Число Класс
    123456 Fixnum
    123_456 Fixnum (подчеркивание игнорируется)
    -543 отрицательное Fixnum
    123_456_789_123_456 Bignum
    0xaabb шестнадцатеричное
    0377 восьмеричное
    -0b1010 двоичное (отрицательное)
    0b001_001 двоичное

    При записи целых чисел сначала указывается его знак (знак + обычно опускается). Далее следует основание системы счисления, в которой задается число (если оно отлично от 10): 0 - для восьмеричной, 0x - для шестнадцатеричной, 0b - для двоичной. Затем идет последовательность цифр, выражающих число в данной системе счисления. При записи больших чисел можно использовать символ подчеркивания, который игнорируется при обработке. В таблице представлены целые числа и указана их принадлежность к тому или иному классу.

    Дробные числа задаются в десятичной системе счисления, при этом для отделения дробной части используется символ . (точка). Такие числа являются экземплярами класса Float, например, 12.34. Для задания дробных чисел может быть применена и экспоненциальная форма записи: два различных представления -.1234е2 и 1234е-2 задают одно и тоже число 12.34.

    Для вычисления арифметических выражений применяются следующие операторы: + (сложение), - (вычитание), * (умножение), / (деление), % (остаток от деления), ** (возведение в степень). Порядок вычисления определяется стандартными приоритетами операций, а для его изменения используются круглые скобки. Заметим, что если все аргументы выражения целые числа, то и результат будет целым, если же хотя бы одно из чисел, входящих в выражение - дробное, то и результат будет экземпляром класса Float.

    Пример

    Создайте файл с именем object.rb, в который поместите следующий фрагмент. Выполните программу и объясните результат.

    puts 5/8                       # 0
    puts 5.0/8                     # 0.625
    puts 2**1000    
    puts ((2*500+1)*(2**500-1))
    #!/usr/bin/env ruby
    
    puts 5/8                       # 0
    puts 5.0/8                     # 0.625
    puts 2**1000    
    puts ((2*500+1)*(2**500-1))    
    
    =begin
    puts (2**30-1).type
    a=0b001_001
    puts a
    puts a.type
    a=12.34
    puts a
    puts a.type
    a=12.34e2
    puts a
    puts a.type
    =end
    
    puts 12.34.ceil                  #  13
    puts 12.34.floor                 #  12
    puts -12.ceil                    # -12  
    puts -12.floor                   # -12
    puts 12.34.round                 #  12 
    puts 12.54.round                 #  13
    puts -34.56.abs                  #  34.56
    
    puts -12.34.to_i                 # -12
    puts 1234.to_f                   # 1234.0
    
    
    =begin
    puts 123_456_789.type
    puts 0xaabb
    puts -0b1010/0b10
    puts 2/3.to_f
    =end

    Отметим еще несколько методов, используемых при работе с числами (т. е. представителями класса Numeric ). Среди них: ceil (нахождение наименьшего целого не меньшего, чем данное), floor (наибольшее целое, не большее данного), round (округление до ближайшего целого), получение абсолютной величины числа abs. Ниже приведены примеры использования этих методов.

    puts 12.34.ceil                  #  13
    puts 12.34.floor                 #  12
    puts -12.ceil                    # -12  
    puts -12.floor                   # -12
    puts 12.34.round                 #  12 
    puts 12.54.round                 #  13
    puts -34.56.abs                  #  34.56

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

    Метод Назначение метода Пример использования Результат
    to_f Преобразовать объект в экземпляр класса Float 1234.to_f 1234.0
    to_i Преобразовать объект в экземпляр класса Fixnum или Bignum -12.34.to_i -12

    В последнем примере первая точка отделяет дробную часть числа, а вторая является оператором вызова метода.

    Строки

    Другим столь же распространенным классом является класс String. К этому классу относятся произвольные строки символов, заключенные в апострофы или кавычки, например, 'hello', 'раз, два, три', "привет всем". Есть несколько альтернативных способов задания каждого из этих видов строк.

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

    puts 'hello'        # hello 
    puts %q/hello/      # hello 
    puts %q(hello)      # hello

    Два подряд идущих символа \ внутри такой строки заменяются на один.

    puts 'hell\\o'      #  hell\o  
    puts %q(hell\\o)    #  hell\o

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

    puts 'hell\'o'      # hell'o  
    puts %q(hell\'o)    # hell\'o  
    puts 'hel"l"o'      # hel"l"o

    Для создания строки, заключенной в кавычки, следует либо просто заключить ее в кавычки, либо использовать конструкции %Q или %, после которых указывается строка, обрамленная с двух сторон одним и тем же символом, отличным от цифр и букв (можно использовать также пару любых скобок). Ниже перечислено несколько способов задания строки "hello".

    "hello"  %Q/hello/ %Q{hello} %Q(hello) %Q!hello!
    %<hello> %(hello)  %!hello!  %*hello*  %+hello+

    puts "Say \"Hello\"" # Say "Hello"

    Строка, заключенная в кавычки, позволяет интерпретировать последовательности символов, начинающиеся с символа \ такие, например, как \n (символ перехода на новую строку) и \t (табуляция). Другой особенностью строк, заключенных в кавычки, является возможность использования подстановки: если строка содержит некоторое выражение, ограниченное символами #{ и }, то оно заменяется на результат его вычисления. Добавьте в программу следующие строки и проанализируйте полученный вывод.

    puts "a \t b"; puts 'a \t b'
    puts "выражение 3*5+8 равно  #{3*5+8}"
    puts 'выражение 3*5+8 равно  #{3*5+8}'
    puts "работа с целыми числами: 5/8 = #{5/8}"
    puts "перевод в класс Float: 5/8 = #{5/8.to_f}"

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

    puts "-12.34".to_i
    puts "12.34".to_f
    puts "+12:34".to_i
    puts "12qq34".to_f

    Для получения строки, содержащей символ с заданным ASCII кодом, используется метод chr, например,

    puts 209.chr

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

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

    Назначение и пример использования метода Результат
    + Сцепление строк "мол" + "око" "молоко"
    * Повтор строки "ab" * 3 "ababab"
    [ ] Возвращает ASCII-код символа, находящегося на указанной позиции строки (отсчет начинается с нуля)
    "abcdef"[0]
    "abcdef"[0].chr

    97

    "a"

    [нач..кон] Возвращает подстроку, заключенную в указанном диапазоне (включая концы) "abcdef"[0..3] "abcd"
    [нач, дл] Возвращает подстроку, начинающуюся с указанной позиции и имеющую заданную длину "abcdef"[0,3] "abc"
    capitalize Заменяет первый символ строки (если он является буквой латинского алфавита) на заглавную "abc".capitalize "Abc"
    chop Удаляет указанные символы из строки, может указываться диапазон изменения символов
    "abcdef".delete('ea')
    "abcdef".delete('a-c')
    "bcdf" "def"
    index Определяет номер позиции, с которой начинается указанная подстрока; можно указывать номер позиции, с которой начинается поиск
    "abcdabcd".index("cd")
    "abcdabcd".index("cd",3)

    2

    6

    length size Определяют длину строки (в байтах)
    "12345678".length
    "12345678".size

    8

    8

    ljust rjust center Дополняют строку пробелами до указанной ширины, выравнивая соответственно по левому краю, по правому краю или по центру
    "123".ljust(8)
    "123".rjust(8)
    "123".center(8)
    "123     "
    "     123"
    "  123   "
    reverse Возвращает строку, содержащую символы в обратном порядке "1234567".reverse "7654321"
    strip Удаляет пробелы в начале и конце строки " 123 ".strip "123"
    squeeze Оставляет в группе повторяющихся символов только один; допускается задание списка символов, на которых распространяется данное действие
    "22---23**".squeeze
    "22---23**".squeeze('*-')
    "2-23*" "22-23*"
    tr Заменяет все найденные вхождения символов на заданные "22+33=55".tr('25','47') "44+33=77"

    Отдельного упоминания заслуживает метод eval, позволяющий динамически выполнять другие методы. Этот метод анализирует переданную ему строку, рассматривая ее как часть программы, и выполняет ее. Обратите внимание, что при вызове этого метода используется не точечная, а функциональная форма записи:

    puts eval("2**10")
    puts eval('"мол".size * "око".size')   # 9
    #!/usr/bin/env ruby
    
    puts 'hello'        # hello 
    puts %q/hello/      # hello 
    puts %q(hello)      # hello 
    
    puts 'hell\\o'      #  hell\o  
    puts %q(hell\\o)    #  hell\o  
    
    puts 'hell\'o'      # hell'o  
    puts %q(hell\'o)    # hell\'o  
    puts 'hel"l"o'      # hel"l"o 
    
    puts "hello"
    puts %Q/hello/
    puts %Q!hello!
    puts %Q{hello}
    puts %Q(hello)
    puts %Q!hello!
    puts %<hello>
    puts %!hello!
    puts %*hello*
    puts %+hello+
    puts %(hello)
    
    puts "Say \"Hello\""   #  Say "Hello"  
    puts "Say 'Hello'"     #  Say 'Hello' 
    
    puts "a \t b"
    puts 'a \t b'
    puts "выражение 3*5+8 равно  #{3*5+8}"
    puts 'выражение 3*5+8 равно  #{3*5+8}'
    puts "работа с целыми числами: 5/8 = #{5/8}"
    puts "перевод в класс Float: 5/8 = #{5/8.to_f}"
    
    puts "-12.34".to_i
    puts "12.34".to_f
    puts "+12:34".to_i
    puts "12qq34".to_f
    
    puts 209.chr
    
    puts "123456789".reverse
    puts "  123       "
    puts "  123  ".strip 
    puts "---22--23****".squeeze('*-')
    puts "---22--23****".squeeze
    
    puts "Дашка любит молоко, а Танька - квас".tr('ДТ','МД')
    puts "22+33=55".tr('25','47')
    
    puts "abcdef"[0].chr
    puts "abcdef".chop
    puts "abcdef".chop.chop
    puts "abcdef".delete("ea")
    puts "abcdef".delete('a-c')
    puts "abcdefabcdef".length
    puts "12345678".size
    
    puts "ABCDEF".downcase
    puts "abcdef".index("cd")
    puts "abcdefabcdef".index("cd",3)
    
    a="12345"
    puts "12345"[3].chr
    puts  "12345"[0..3]
    puts  "12345"[0,3]
    
    puts  "привет".capitalize # Не работает с русскими буквами!
    puts "abcdabcd".index("cd",3)
    puts "*"+"123".ljust(9)+*"
    puts "*"+"123".center(9)+"*"
    puts "*"+"123".rjust(9)+"*"
    
    puts (eval "2**10")
    puts (eval ""мол".size * "око".size') 
    
    =begin
    s="2*3+4"
    puts s
    puts "#{s}"
    puts eval(s)
    puts eval s
    
    puts s.eval # Ошибка! Неправильный вызов метода
    
    print "Введите команды:"
    eval gets.chop!
    =end

    Время и дата

    Экземпляр класса Time в языке Ruby содержит информацию о дате, времени и временной зоне. Для создания объекта этого класса, содержащего текущие дату и время, используется метод now. Такие объекты могут являться аргументами операций + и -:

    puts Time.now
    puts Time.now+60   # добавили 60 секунд к текущему времени
    puts Time.now-60   # отняли 60 секунд от текущего времени

    Отметим некоторые методы этого класса, для вызова которых используется точечная нотация.

    Метод Назначение метода
    sec Получить число секунд
    min Получить число минут
    hour Получить число часов
    mday и day Получить день месяца
    mon и month Получить номер месяца
    year Получить год
    wday Получить номер дня недели
    yday Получить номер дня в году
    zone Получить информацию о временной зоне
    to_i Получить число секунд, прошедших с 1 января 1970 года

    В языке Ruby имеется метод sleep, который заставляет программу "заснуть" на число секунд, указанное в качестве аргумента метода. Посмотрите на пример использования методов для работы с объектами класса Time.

    puts "До Нового года осталось #{365-Time.now.yday} дней"
    puts Time.now
    puts "Подождем 10 секунд."
    sleep(10)
    puts Time.now
    #!/usr/bin/env ruby
    puts Time.now
    puts Time.now+60   # добавили 60 секунд к текущему времени
    puts Time.now-60   # отняли 60 секунд от текущего времени
    
    puts "До Нового года осталось #{365-Time.now.yday} дней"
    puts Time.now
    puts "Подождем 10 секунд."
    sleep(10)
    puts Time.now

    Вывод данных

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

    puts "puts всегда завершается переходом к новой строке."
    print "А оператор print не делает этого по умолчанию, "
    print "как вы видите в этом примере.\n"
    print "print может быть вызван сразу  ", 
       "с несколькими аргументами.\n"

    Оператор p подобен оператору puts и отображает объекты в виде, понятном человеку. Не следует использовать его для вывода русских букв - он печатает их ASCII-коды.

    p Time.now, Time.now+3600

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

  • флаги (в любом порядке):

    - указывает на то, что преобразованный аргумент должен быть прижат к левому краю поля;

    + предписывает всегда печатать число со знаком;

    0 - указывает, что числа должны дополняться ведущими нулями до всей ширины поля;

    пробел - указывает, что числа должны дополняться ведущими нулями до всей ширины поля;

  • число, задающее минимальную ширину поля;
  • точка, отделяющая указатель ширины поля от указателя точности;
  • число, задающее точность.
  • Основные литеры-спецификаторы и разъяснение их смысла приводятся в следующей таблице.

    Литера Аргумент Вид печати
    d Fixnum, Bignum печать целого числа
    s String печать строки
    f Float печать дробного числа

    Включите в файл следующие операторы форматной печати и посмотрите на результаты выполнения:

    printf "%8s~~%-8s\n", "abcd", "abcd"
    printf "%-8s::%8s\n", "abcd", "abcd"
    printf "%06d\n", 2**10; printf "%+6d\n", 2**10
    printf "%6d\n", 2**10;  printf "%+6d\n", 2.5*1.3
    printf "%4.3f\n", 2.5*1.3
    printf "-2/7=%+1.6f, -2/6=%2.15f\n",
          -2/7.to_f, -2/6.to_f

    Обратите внимание, что строка формата заключается в двойные кавычки и может содержать специальные последовательности символов. Одной из них является \n. Эта последовательность дает указание интерпретатору Ruby продолжить вывод информации со следующей строки (newline). Другой часто используемой последовательностью является \t (табуляция), которая передвигает фокус вывода к следующей позиции табуляции.

    puts "*\t*\t*\t*\t*"
    puts " 1234567 1234567 1234567 1234567 "
    #!/usr/bin/env ruby
    puts "puts всегда завершается переходом к новой строке."
    print "А оператор print не делает этого по умолчанию, "
    print "как вы видите в этом примере.\n"
    print "print может быть вызван сразу  ", 
       "с несколькими аргументами.\n"
    
    p Time.now, Time.now+3600
    
    printf "%8s~~%-8s\n", "abcd", "abcd"
    printf "%-8s::%8s\n", "abcd", "abcd"
    printf "%06d\n", 2**10; printf "%+6d\n", 2**10
    printf "%6d\n", 2**10;  printf "%+6d\n", 2.5*1.3
    printf "%4.3f\n", 2.5*1.3
    printf "-2/7=%+1.6f, -2/6=%2.15f\n",
          -2/7.to_f, -2/6.to_f
    
    puts "*\t*\t*\t*\t*"
    puts " 1234567 1234567 1234567 1234567 "

    Переменные и константы

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

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

    Зарезервированные слова
    __FILE__ and def end in or self unless
    __LINE__ begin defined? ensure module redo super until
    BEGIN break do false next rescue then when
    END case else for nil retry true while
    alias class elsif if not return undef yield

    Кроме локальных переменных Ruby поддерживает и другие их виды - глобальные переменные, переменные классов и экземпляров. Принадлежность к каждому из этих видов определяются символом, стоящим перед именем переменной (префиксом): символ $ задает глобальную переменную, @ - переменную экземпляра, @@ - переменную класса. Локальные переменные не имеют префикса. Имена констант и классов должны начинаться с прописной латинской буквы (A-Z) и, аналогично переменным, продолжаются любой комбинацией латинских букв, цифр и символа подчеркивания.

    Локальная переменная создается динамически в момент, когда ей в процессе выполнения программы первый раз присваивается какое-то значение. Оператор присваивания имеет вид знака = (равно), например, a = 1; b = a; name = "Иван". Переменные могут использоваться во всех выражениях Ruby аналогично соответствующим объектам:

    a = "Привет всем!"
    puts a

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

    a = "one"
    puts a
    puts a.type
    a = 23
    puts a
    puts a.type

    Обратите внимание на метод type, возвращающий тип объекта. При первом использовании переменная a есть ссылка на объект класса String, затем мы присваиваем этой переменной другое значение (наклеиваем ту же этикетку на новый объект), после чего она становится ссылкой на экземпляр класса Fixnum.

    Ruby поддерживает также и присваивание с операцией. Результат выполнения следующих двух операторов одинаковы:

    a+=12
    a=a+12

    Общая форма подобных операторов выглядит так:

    выражение1 операция= выражение2

    что соответствует оператору

    выражение1 = выражение1 операция выражение2

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

    +, -, *, /, %, **, , |, ^, <<, >>, , |

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

    a, b, c = 1, 2, 3

    Пример

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

    a, b = b, a

    Данный пример объясняет, почему эту форму оператора присваивания называют также "параллельным присваиванием".

    Мы уже упоминали одну из замечательных возможностей Ruby - подстановку. Она позволяет выполнять разнообразные трюки со строкой, заключенной в двойные кавычки, например,

    magic = 42
    puts "Секретное число равно #{magic - 3}!!"

    Когда Ruby находит конструкцию #{выражение} внутри строки, заключенной в двойные кавычки, то заменяет эту ее часть на результат вычисления выражения.

    Пример

    Дано трехзначное число. Напечатать число, получающееся при перестановке цифр десятков и единиц заданного числа.

    number = 342
    puts "Исходное число равно #{number}"
    n1 = number/100                   # число сотен
    n2 = (number/10)%10               # число десятков
    n3 = number%10                    # число единиц
    answer = n1*100 + n3*10 + n2
    puts "Результат #{answer}"
    #!/usr/bin/env ruby
    
    number = 342
    puts "Исходное число равно #{number}"
    n1 = number/100                   # число сотен
    n2 = (number/10)%10               # число десятков
    n3 = number%10                    # число единиц
    answer = n1*100 + n3*10 + n2
    puts "Результат #{answer}"

    Иногда возникает необходимость указать, что тот или иной объект в программе является неизменяемым. Такие объекты называются константами. В Ruby имена констант, также как и имена классов, должны начинаться с заглавной буквы (есть определенная традиция задавать имена констант, состоящие из одних заглавных букв). Рассмотрим следующий фрагмент программы:

    NUM = 234
    p NUM      # 234
    NUM = "qq" # warning: already initialized constant NUM 
    p NUM      # "qq"

    Как видим, интерпретатор выдал предупреждение о том, что константа NUM уже была определена ранее. Тем не менее значение константы было изменено.

    Задания

  • Напишите программу, печатающую количество квадратов со стороной 130 мм, которое можно отрезать от прямоугольника со сторонами 543x130 мм.
  • Напишите программу, которая по данному числу a, не используя никаких арифметических операций, кроме умножения, получает a8 за три операции.
  • Операторы ветвления

    Условный оператор, называемый также оператором выбора, позволяет в зависимости от истинности или ложности некоторого условия выполнять ту или иную последовательность команд.

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

    Следует запомнить, что только false и nil трактуются как ложные в логическом контексте. Все остальное, что вычисляется, является истинным. Для того чтобы лучше понять смысл данного высказывания, выполните следующую программу:

    if 23
      puts "23 есть ИСТИНА"
    end
    
    if "qq"
      puts "И любая строка есть ИСТИНА"
    end

    Ruby поддерживает все стандартные логические операторы, а также дополнительный оператор defined?. Для конъюнкции двух логических выражений используются and и . Их результат будет истинен только если инстинны оба операнда. Эти функции вычисляют второй операнд лишь тогда, когда первый истинен, а отличаются они только своим приоритетом (приоритет оператора and меньше ).

    В свою очередь результатом выполнения операторов or и || (дизъюнкции) будет истина, если хотя бы один из операндов истинен. Они вычисляют второй оператор только в том случае, если первый окажется ложным. Как и в случае с оператором and, оператор or имеет меньший приоритет по сравнению с || (отметим, что оператор and имеет приоритет, равный приоритету оператора or, в то время как приоритет выше, чем у || ).

    Операторы ! возвращают значение, противоположное своему аргументу (если аргумент истинен, то результат ложен, и наоборот). Между собой отличаются только приоритетом.

    Оператор defined? возвращает nil, если его аргумент не определен, и его описание в противном случае. Ниже приведены примеры применения этого оператора.

    puts defined? 1
    puts defined? dummy
    puts defined? printf
    puts defined? (c, d = 1, 2)
    puts defined? 42.abs

    В логических выражениях допускаются также следующие операции сравнения: == <, <=, >=, > !=.

    Синтаксис условного оператора if в языке Ruby аналогичен его синтаксису в большинстве других языков программирования.

    # Определение метода оценки величины выражения
    def howBigPrint(i)
      if i < 10
        puts "Число  #{i} меньше 10"
      elsif i < 20
        puts "Число #{i} между 10 и 20"
      elsif i < 30
        puts "Число #{i} между 20 и 30"
      else
        puts "Число #{i} больше или равно 30"
      end
    end
    # Использование метода ...
    howBigPrint(7);  howBigPrint(15)
    howBigPrint(23); howBigPrint(105)

    Общая форма оператора if такова:

    if <логическое_выражение> [then]
        тело_оператора
    elsif <логическое_выражение> [then]
        тело_оператора
            ...
    else
        тело_оператора
    end

    Здесь <логическое_выражение> ; может быть любым фрагментом кода на языке Ruby, результатом вычисления которого является логическая величина (с учетом сказанного выше). Слово then отделяет тело оператора от условия. Запись его в квадратных скобках означает, что оно может быть опущено, если тело начинается с новой строки. Значение, возвращаемое оператором if, есть результат последнего вычисленного выражения. Изменим наш пример, чтобы продемонстрировать сказанное:

    # Метод оценки величины выражения
    # теперь возвращает строку
    def howBig(i)
      if i < 10 then "Аргумент меньше 10"
      elsif i < 20 then "Аргумент между 10 и 20"
      elsif i < 30 then "Аргумент между 20 и 30"
      else "Аргумент больше или равно 30"
      end
    end
    # Использование метода ...
    puts howBig(7);  puts howBig(15)
    puts howBig(23); puts howBig(105)

    Для выполнения одного варианта из двух возможных используется следующая форма оператора if:

    if <логическое_выражение> [then]
        тело_оператора
    else
        тело_оператора
    end

    В следующем фрагменте сравнивается значение переменной i с числом 100.

    if i < 100 
      puts "Мало"
    else
      puts "Значительно больше"
    end

    Оператор if может использоваться в правой части оператора присваивания, например,

    str = if i < 100 then "Мало" else "Побольше" end
    puts str

    Для аналогичных целей используется и тернарный оператор

    логическое_выражение ? выражение1 : выражение2

    Если логическое_выражение истинно, то выполняется выражение1, иначе - выражение2. Посмотрите на примеры использования этого оператора:

    i = 23
    i < 100 ? puts("Мало") : puts("Значительно больше")
    i = 1234
    s = i < 100 ? "Мало" : "Значительно больше"
    puts s

    Как видите, этот оператор особенно удобен, если требуется переменной присвоить одно из двух значений.

    Пример

    Напишите программу, определяющую четность введенного числа.

    print "Введите целое число: "
    a = gets.to_i
    str = a%2 == 0 ? "четно" : "нечетно"
    puts "Число #{a} " + str

    Если требуется выполнить последовательность операторов только в том случае, когда выполнено некоторое условие, то используется форма

    if <логическое_выражение> [then]
        тело_оператора
    end

    Для этих же целей можно использовать и модифицированный оператор if:

    выражение if <логическое_выражение>

    Ниже приводятся две версии фрагмента программы, использующей оператор if:

    if radiation > 3000
       puts "Радиационная опасность!"
    end
    # то же самое
    puts "Радиационная опасность!" if radiation > 3000

    Пример

    Напишите программу, определяющую, является ли введенная фраза палиндромом (перевертышем).

    puts "Введите фразу:"
    a = gets.chop!.delete(' ')     # удалили все пробелы
    a = a.tr('A-Z,А-Я', 'a-z,а-я') # замена букв на прописные
    str = "палиндром"
    str = "не " + str if a != a.reverse # проверка
    puts "Введенная фраза - " + str + "."

    При выборе из большего числа альтернатив удобнее использовать оператор case. Ниже приведен его общий вид (части, заключенные в квадратные скобки, могут быть опущены).

    case <выражение>
    when  <тест1> [then]
        ...
    when  <тест2> [then]
        ...
    when  <тестN> [then]
        ...
    [else
        ... ]
    end

    В операторе case <выражение> последовательно сравнивается с выражениями <тест> до тех пор пока, оно не совпадет с одним из них (сравнение производится при помощи операции == ), после чего выполняется соответствующий фрагмент кода. Оператор case возвращает значение последнего вычисленного выражения, или nil, если такого не было.

    s = case i
        when   0 .. 9
           "однозначное"
        when  10 .. 99
           "двузначное"
        when 100 .. 999
           "трехзначное"
        else
           "огромное"
        end
    puts "Введено #{s} число."

    Здесь мы снова воспользовались оператором .. (двоеточие), который возвращает список целых чисел, заключенных между левым и правым операндами (включая их самих).

    #!/usr/bin/env ruby
    if 23
      puts "23 есть ИСТИНА"
    end
    
    if "qq"
      puts "И любая строка есть ИСТИНА"
    end
    puts
    
    # Определение метода оценки величины выражения
    def howBigPrint(i)
      if i < 10
        puts "Число  #{i} меньше 10"
      elsif i < 20
        puts "Число #{i} между 10 и 20"
      elsif i < 30
        puts "Число #{i} между 20 и 30"
      else
        puts "Число #{i} больше или равно 30"
      end
    end
    # Использование метода ...
    howBigPrint(7);  howBigPrint(15)
    howBigPrint(23); howBigPrint(105)
    puts
    
    # Метод оценки величины выражения
    # теперь возвращает строку
    def howBig(i)
      if i < 10 then "Аргумент меньше 10"
      elsif i < 20 then "Аргумент между 10 и 20"
      elsif i < 30 then "Аргумент между 20 и 30"
      else "Аргумент больше или равно 30"
      end
    end
    # Использование метода ...
    puts howBig(7);  puts howBig(15)
    puts howBig(23); puts howBig(105)
    
    puts
    i = 23
    i < 100 ? puts("Мало") : puts("Значительно больше")
    i = 1234
    s = i < 100 ? "Мало" : "Значительно больше"
    puts s
    
    puts "Перевертыш"
    
    puts "Введите фразу:"
    a = gets.chop!.delete(' ')     # удалили все пробелы
    a = a.tr('A-Z,А-Я', 'a-z,а-я') # замена букв на прописные
    str = "палиндром"
    str = "не " + str if a != a.reverse # проверка
    puts "Введенная фраза - " + str + "."
    
    print "Введите неотрицательное целое число: "
    i= gets.to_i
    s = case i
        when   0 .. 9
           "однозначное"
        when  10 .. 99
           "двузначное"
        when 100 .. 999
           "трехзначное"
        else
           "огромное"
        end
    puts "Введено #{s} число."

    Задания

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

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

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

    while <выражение> [do]
            ...
         тело цикла
            ...
    end

    Другим оператором цикла является until, выполняющийся до тех пор, пока его условие ложно:

    until <выражение> [do]
            ...
         тело цикла
            ...
    end

    Пример Рассмотрим программу, печатающую числа от 1 до 5. Сначала используем оператор while, затем until. Обратите внимание, что условие окончания одного оператора цикла является отрицанием условия другого оператора.

    i=1
    while i <= 5
      puts i; i += 1
    end
    
    # еще раз
    i=1
    until i > 5
      puts i; i += 1
    end

    Кроме этих двух операторов цикла в Ruby имеется большое число, так называемых, итераторов (iterate - повторять). Давайте посмотрим на примеры их использования. Конструкция

    3.times do
       print "Ау! "
    end

    использует итератор times. Цикл, заданный таким образом, выполнится ровно три раза.

    В случае, когда нужно выполнить некоторые действия, зависящие от изменяемой величины, несколько раз, можно использовать итератор upto, так в процессе выполнения программы

    0.upto(9) do |x| print x, " " end

    будет напечатано 0 1 2 3 4 5 6 7 8 9.

    Повтор от 0 до 12 с шагом 3 можно записать при помощи итератора step:

    0.step(12, 3) {|x| print x, " " } # 0 3 6 9 12

    При работе с массивами удобно использовать итератор each:

    [1, 1, 2, 3].each {|k| print k, " " } # 1 1 2 3

    Итератор for in очень похож на each, например, вывод, полученный в результате выполнения следующих двух конструкций одинаков.

    for i in  ["one", "two", "three"]
      print i, " "
    end
    
    # то же самое
    ["one", "two", "three"].each{ |i| print i, " "}

    Итератор for обычно используют там же, где и итератор each - при работе с массивами и диапазонами. Общий вид оператора for таков:

    for <переменная> in <выражение> [do]
       тело_итератора
    end

    Пример

    Перепишем программу печати чисел от 1 до 5 с использованием оператора for:

    for i in 1 .. 5
      puts i
    end

    В этом примере мы снова воспользовались оператором задания диапазона .. (двоеточие), который позволил нам создать список чисел от 1 до 5 (включительно). Похожий оператор ... (троеточие) при создании диапазона не включает в него правый операнд. Фрагмент программы, расположенный ниже, эквивалентен предыдущему.

    for i in 1 ... 6
      puts i
    end

    Пример

    Рассмотрим несколько вариантов программы, вычисляющей факториал введенного числа.

  • Использование оператора while
    print "Введите целое положительное число: "
    str = gets.chop! # ввели строку
    num = str.to_i   # преобразовали в число
    if num > 0
      i = 1
      fact = 1
      while i <= num
        fact *= i
        i += 1
      end
      puts "Факториал числа #{num} равен #{fact}"
    else
      puts "Вы ввели неположительное число"
    end
  • Использование оператора for
    print "Введите целое положительное число: "
    num = gets.to_i # строку сразу преобразовали в число
    if num > 0
      fact = 1
      for i in 1 .. num
        fact *= i
      end
      puts "Факториал числа #{num} равен #{fact}"
    else
      puts "Вы ввели неположительное число"
    end
  • Использование функции для определения факториала
    def fact(n)
      f = 1
      1.step(n,1) {|k| f *= k}
      return f
    end
    print "Введите целое положительное число: "
    if (num = gets.to_i) > 0
      print "#{num}! = #{fact(num)}\n"
    else
      puts "Факториал числа #{num} не определен\n"
    end
  • В последнем примере мы оформили вычисление факториала в виде функции и использовали итератор step, который обеспечил изменение переменной k с шагом 1.

    Пример

    Приведенная ниже программа запрашивает целое положительное число и определяет количество цифр в нем (обратите внимание на множественное присваивание).

    print "Введите целое положительное число: "
    a, k = gets.to_i, 0
    while a>0
      a /= 10; k += 1 # отбросили последнюю цифру
    end
    print "Количество цифр в введенном числе равно #{k}.\n"

    Пример

    Пусть нужно ввести с клавиатуры массив чисел и напечатать сумму всех элементов. Приведем несколько решений этой задачи.

  • Сначала введем количество элементов массива, а затем заполним его элементами, одним за другим. Обратите внимание, что первый элемент массива имеет индекс 0, а последний - на единицу меньший, чем размерность массива.
    print "Введите число элементов массива: "
    sn = gets.chop!; n = sn.to_i
    b = Array.new(n)   # создали массив из n элементов  
    s = 0              # обнулили сумму
    for i in 0 .. n - 1
      print "Введите #{i+1}-й элемент массива: "
      b[i] = gets.chop!.to_f; s = s + b[i]
    end
    print "Сумма всех элементов массива равна ", s, "\n"
  • В этой версии программы все числа вводятся сразу в виде одной строки, в которой числа отделяется друг от друга пробелом (например, 23 -34.67 100.5).
  • Встроенный метод split разделяет строку на элементы массива, аргументом этого метода является разделитель (если разделителем является пробел, то можно вызвать этот метод без аргумента). Таким образом, если бы мы договорились разделять числа, например, точкой с запятой, то вызов метода выглядел бы так: a.split(';').

    Для определения длины массива мы применили метод length (можно заменить на эквивалентный ему метод size ).

    puts "Введите массив чисел (разделяя их пробелами):"
    a = gets.chop!
    b = a.split # разбили строку на отдельные числа
    s = 0
    for i in 0 .. b.length - 1
      s += b[i].to_f
    end
    puts "Сумма всех элементов массива равна #{s}"

    В Ruby имеются четыре конструкции, задаваемые ключевыми словами break, redo, next и retry, которые изменяют обычный порядок выполнения циклов. Их действие описано в следующей таблице.

    break Немедленно прекращает выполнение цикла; управление передается на утверждение, расположенное сразу за циклом
    redo Повторяет тело цикла с начала, не пересчитывая условие выполнения цикла (не переходя к следующему элементу в случае итератора)
    next Пропускает часть тела цикла, следующую за ним, и переходит к следующей итерации
    retry Начинает выполнение цикла с самого начала

    Рассмотрим на примере итератора for действие указанных конструкций.

    for i in 1 .. 5
      print  i
      break if i == 3
      print "*"
    end
    Результат: 1*2*3
    for i in 1 .. 5
      print  i
      redo if i == 3
      print "*"
    end
    Результат: 1*2*33333 ... выполнение цикла не прекращается
    for i in 1 .. 5
      print  i
      next if i == 3   
      print "*"
    end
    Результат: 1*2*34*5*
    for i in 1 .. 5
      print  i
      retry if i == 3
      print "*"
    end
    Результат: 1*2*31*2*31*2*... выполнение цикла не прекращается

    Пример

    Следующая программа начинает повторение цикла сначала, если при вводе указать символ y.

    for i in 1 .. 5
      print "Now at #{i}. Restart?(y/n) "
      retry if gets.chop == "y"
    end

    Вот один из возможных вариантов выполнения этой программы:

    Now at 1. Restart?(y/n) n
    Now at 2. Restart?(y/n) y
    Now at 1. Restart?(y/n) n
            ...
    #!/usr/bin/env ruby
    i=1
    while i <= 5
      puts i; i += 1
    end
    puts
    
    # еще раз
    i=1
    until i > 5
      puts i; i += 1
    end
    puts
    
    3.times do
       print "Ау! "
    end
    puts
    
    0.upto(9) do |x| print x, " " end 
    puts
    
    0.step(12, 3) {|x| print x, " " } # 0 3 6 9 12
    puts
    
    [1, 1, 2, 3].each {|k| print k, " " } # 1 1 2 3 
    puts
    
    for i in  ["one", "two", "three"]
      print i, " "
    end
    puts
    
    # то же самое
    ["one", "two", "three"].each{ |i| print i, " "}
    puts
    
    for i in 1..5
      puts i
    end
    puts
    
    for i in 1 ... 6
      puts i
    end
    puts
    
    print "Введите целое положительное число: "
    str = gets.chop! # ввели строку
    num = str.to_i   # преобразовали в число
    if num > 0
      i = 1
      fact = 1
      while i <= num
        fact *= i
        i += 1
      end
      puts "Факториал числа #{num} равен #{fact}"
    else
      puts "Вы ввели неположительное число"
    end
    puts
    
    print "Введите целое положительное число: "
    num = gets.to_i # строку сразу преобразовали в число
    if num > 0
      fact = 1
      for i in 1 .. num
        fact *= i
      end
      puts "Факториал числа #{num} равен #{fact}"
    else
      puts "Вы ввели неположительное число"
    end
    puts
    
    def fact(n)
      f = 1
      1.step(n,1) {|k| f *= k}
      return f
    end
    print "Введите целое положительное число: "
    if (num = gets.to_i) > 0
      print "#{num}! = #{fact(num)}\n"
    else
      puts "Факториал числа #{num} не определен\n"
    end
    
    puts
    print "Введите целое положительное число: "
    a, k = gets.to_i, 0
    while a>0
      a /= 10; k += 1 # отбросили последнюю цифру
    end
    print "Количество цифр в введенном числе равно #{k}.\n"
    puts
    
    print "Введите число элементов массива: "
    sn = gets.chop!; n = sn.to_i
    b = Array.new(n)   # создали массив из n элементов  
    s = 0              # обнулили сумму
    for i in 0 .. n - 1
      print "Введите #{i+1}-й элемент массива: "
      b[i] = gets.chop!.to_f; s = s + b[i]
    end
    print "Сумма всех элементов массива равна ", s, "\n"
    puts
    
    puts "Введите массив чисел (разделяя их пробелами):"
    
    a = gets.chop!
    b = a.split # разбили строку на отдельные числа
    s = 0
    for i in 0 .. b.length - 1
      s += b[i].to_f
    end
    puts "Сумма всех элементов массива равна #{s}"
    puts
    
    =begin
    
    for i in 1 .. 5
      print  i
      break if i == 3
      print "*"
    end
    puts
    
    
    for i in 1 .. 5
      print  i
      redo if i == 3
      print "*"
    end
    puts
    
    for i in 1 .. 5
      print  i
      next if i == 3   
      print "*"
    end
    puts
    
    for i in 1 .. 5
      print  i
      retry if i == 3
      print "*"
    end
    puts
    
    =end
    
    for i in 1 .. 5
      print "Now at #{i}. Restart?(y/n) "
      retry if gets.chop == "y"
    end
    puts

    Задания

  • Напишите программу, вычисляющую сумму всех четных натуральных чисел, не превосходящих 1000.
  • Напишите программу, определяющую максимальный элемент массива чисел.
  • Примеры программ

    НОД и алгоритм Евклида

    При рассмотрении задач, связанных с обработкой целых чисел, часто приходится сталкиваться с понятием НОД - наибольшего общего делителя чисел. Известно много алгоритмов вычисления НОД, мы рассмотрим лишь два из них.

    Задача

    Напишите программу, вычисляющую НОД(a,b) - наибольший общий делитель двух введенных с клавиатуры неотрицательных целых чисел a и b, не равных нулю одновременно.

    Вариант 1

    Определим максимум из этих чисел и, последовательно уменьшая его, будем искать число, на которое делятся и a и b. Программа обязательно завершит свою работу, так как в самом неудачном случае, когда эти числа взаимно просты, k станет равно 1, а на 1 делятся все числа, следовательно выполнение цикла прекратится.

    print "Введите первое число: "; a = gets.to_i
    print "Введите второе число: "; b = gets.to_i
    
    k = a >= b ? a : b # теперь k -  максимум 
    until (a % k == 0) and (b % k == 0)
      k -= 1
    end
    print "НОД(#{a},#{b}) = #{k}\n"

    Вариант 2 - алгоритм Евклида

    Алгоритм Евклида основан на следующих свойствах НОД: для всех a и b, больших или равных 0, выполнены равенства

    НОД(a, b) = НОД(a - b, b)= НОД(a, b - a); 
    НОД(a, 0) = НОД(0, a) = a

    Ниже приведен текст программы, реализующей алгоритм Евклида.

    print "Введите первое число: "; a = gets.to_i
    print "Введите второе число: "; b = gets.to_i
    m, n = a, b
    while  !((m == 0) || (n == 0))
      if m >= n
        m = m - n
      else
        n = n - m
      end
    end
    
    k = m == 0 ? n : m
    print "НОД(#{a},#{b}) = #{k}\n"
    #!/usr/bin/env ruby
    
    # 1
    
    print "Введите первое число: "; a = gets.to_i
    print "Введите второе число: "; b = gets.to_i
    
    k = a >= b ? a : b # теперь k -  максимум 
    
    
    until (a%k == 0) (b%k == 0)
      k-=1
    end
    print "НОД(#{a},#{b}) = #{k}\n"
    
    #2 Алгоритм Евклида
    
    print "Введите первое число: "; a = gets.to_i
    print "Введите второе число: "; b = gets.to_i
    m, n = a, b
    while  !((m == 0) || (n == 0))
      if m >= n
        m = m - n
      else
        n = n - m
      end
    end
    
    k = m == 0 ? n : m
    print "НОД(#{a},#{b}) = #{k}\n"

    Аргументы командной строки

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

    =begin
    Напишите программу подсчета суммы всех нечетных
    чисел, заключенных в интервале от K до L, 
    где  K и L - аргументы командной строки
    =end
    
    if ARGV.length < 2 
      puts "Слишком мало входных данных"
      exit(1)
    end
    
    s = 0
    k = ARGV[0].to_i
    l = ARGV[1].to_i
    for i in k .. l
      s += i if i%2 != 0
    end
    puts "Сумма нечетных чисел в интервале " +
         "от #{k} до #{l} равна #{s}"

    Для экстренного прерывания процесса выполнения программы здесь использован встроенный метод exit.

    #!/usr/bin/env ruby
    =begin
    Напишите программу подсчета суммы всех нечетных
    чисел, заключенных в интервале от K до L, 
    где  K и L - аргументы командной строки
    =end
    
    if ARGV.length < 2 
      puts "Слишком мало входных данных"
      exit(1)
    end
    
    s = 0
    k = ARGV[0].to_i
    l = ARGV[1].to_i
    for i in k .. l
      s += i if i%2 != 0
    end
    puts "Сумма нечетных чисел в интервале " +
         "от #{k} до #{l} равна #{s}"

    Задача

    Числами Фибоначчи называется последовательность целых чисел, задаваемая соотношениями: а0 = 0, a1 = 1, an = an-1+an-2. Напишите программу, получающую в качестве аргумента командной строки целое число n, и печатающую nчисло Фибоначчи.

    Вариант 1 - рекурсивная функция

    В следующей программе первый элемент массива ARGV преобразовывается в целое число и передается в качестве аргумента рекурсивной функции fib.

    def fib(n)
      if n<2
        n
      else
        fib(n-2)+fib(n-1)
      end
    end
    n = ARGV[0].to_i
    puts "#{n}-е число Фибоначчи равно #{fib(n)}"

    Для вычисления с помощью этой программы, размещенной в файлe fib.rb, например, 30-го числа Фибоначчи достаточно выполнить команду

    ruby fib.rb 30

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

    #!/usr/bin/env ruby
    
    =begin
    Напишите программу, получающую с качестве аргумента командной строки целое
    число n, и печатающую n-е  число Фибоначчи. 
    =end
    
    def fib(n)
      if n<2
        n
      else
        fib(n-2)+fib(n-1)
      end
    end
    t1 = Time.now
    n = ARGV[0].to_i
    puts "#{n}-е число Фибоначчи равно #{fib(n)}"
    t2 = Time.now
    puts "Время расчета около #{(t2-t1).round} сек."

    Вариант 2 - использование массива

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

    n =ARGV[0].to_i
    f= [0, 1]
    fib = case n
          when   0
    	f[0]
          when  1
    	f[1]
          else
    	for i in 2 .. n
    	  f[i] = f[i-1] + f[i-2]
    	end
    	f[n]
          end
    
    puts "#{n}-е число Фибоначчи равно #{fib}"
    #!/usr/bin/env ruby
    
    =begin
    Напишите программу, получающую с качестве аргумента командной строки целое
    число n, и печатающую n-е  число Фибоначчи. 
    =end
    t1 = Time.now
    n =ARGV[0].to_i
    f= [0, 1]
    fib = case n
          when   0
    	f[0]
          when  1
    	f[1]
          else
    	for i in 2 .. n
    	  f[i] = f[i-1] + f[i-2]
    	end
    	f[n]
          end
    puts "#{n}-е число Фибоначчи равно #{fib}"
    t2 = Time.now
    puts "Время расчета около #{(t2-t1).round} сек."
    a = fib.to_s.split('")
    puts "Количество цифр в #{n}-м числе Фибоначчи равно #{a.size}."

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

    n =ARGV[0].to_i
    if n > 1
      prev, beforePrev = 1, 0
      for i in 2 .. n
        fib  = prev + beforePrev
        prev, beforePrev = fib, prev
      end
    else 
      fib = n
    end
    puts "#{n}-е число Фибоначчи равно #{fib}"
    #!/usr/bin/env ruby
    
    =begin
    Напишите программу, получающую с качестве аргумента командной строки целое
    число n, и печатающую n-е  число Фибоначчи. 
    =end
    t1 = Time.now
    n =ARGV[0].to_i
    if n > 1
      prev, beforePrev = 1, 0
      for i in 2 .. n
        fib  = prev + beforePrev
        prev, beforePrev = fib, prev
      end
    else 
      fib = n
    end
    puts "#{n}-е число Фибоначчи равно #{fib}"
    t2 = Time.now
    puts "Время расчета около #{(t2-t1).round} сек."
    a = fib.to_s.split("')
    puts "Количество цифр в #{n}-м числе Фибоначчи равно #{a.size}."

    Включите в рассмотренные программы операторы, информирующие о продолжительности вычислений, и сравните их эффективность. Для этого найдите с помощью первой из рассмотренных программ 30, 31 и 32 числа Фибоначчи. Пользуясь второй и, тем более, третьей вы сможете вычислить достаточно большое число Фибоначчи, например, для вычисления 60000-го числа Фибоначчи, содержащего 12539 цифр на компьютере с процессором Celeron-500, программе, размещающей промежуточные результаты в массиве, потребовалось около 19 секунд, в то время как последняя программа вычислила его за 2 секунды.

    Работа с файлами

    Ввод данных с клавиатуры в процессе выполнения программы удобен только в случае небольших объемов. В других ситуациях необходимо чтение информации из заранее подготовленного файла.

    Для работы с файлом при помощи метода new создается экземпляр класса File. Обязательным аргументом этого метода является строка, содержащая имя файла, например,

    f = File.new("myfile.txt")

    Другим (необязательным) аргументом является задание режима работы с файлом. По умолчанию этот параметр имеет значение " r ", что соответствует режиму "только чтение". Если требуется открыть файл с возможностью записи в него, то следует указать параметр " w ", например,

    f1 = File.new("newfile.txt", "w")

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

    Для вывода строки в выходной поток (в файл или на экран монитора) используется метод write, например,

    a = "Hello, world!"
    f1.write(a)

    Задача

    Имеется текстовый файл fio.txt, содержащий список фамилий, имен и отчеств учащихся. При этом каждая строка файла содержит данные только об одном человеке, например,

    Петров Сергей Васильевич
    Сидорова Ольга Петровна
    Иванова Марья Даниловна

    Напишите программу, которая читает информацию из файла и

  • печатает пронумерованный список учеников;
  • печатает пронумерованный список фамилий и инициалов.
  • Решение 1

    f = File.new("fio.txt")
    n = 1
    student = f.readlines
    student.each{ |i|
      print n, ". ", i
      n += 1
    }

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

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

    Решение 2

    f = File.new("fio.txt")
    n = 1
    student = f.readlines
    student.each{ |i|
      i.chop!
      fio = i.split
      print n, ". ", fio[0], " ", fio[0][0].chr, ". ",
        fio[1][0].chr, ".\n" 
      n += 1
    }
    #!/usr/bin/env ruby
    
    # 1
    f = File.new("fio.txt")
    n = 1
    student = f.readlines
    student.each{ |i|
      print n, ". ", i
      n += 1
    }
    
    puts
    # 2
    f = File.new("fio.txt")
    n = 1
    student = f.readlines
    student.each{ |i|
      i.chop!
      fio = i.split
      print n, ". ", fio[0], " ", fio[0][0].chr, ". ",
        fio[1][0].chr, ".\n" 
      n += 1
    }

    Случайные числа

    Моделирование случайных процессов на компьютере - достаточно распространенное явление. Как и в большинстве языков программирования в Ruby есть методы, генерирующие, так называемые, "псевдослучайные" числа (их нельзя считать чисто случайными, ведь создаются они при помощи некоторого алгоритма).

    Для получения случайного целого числа в диапазоне от 0 до n используется метод rand(n). Случайное число в диапазоне от 0 до 1 получается с помощью этого же метода без указания параметра.

    a1 = (1..10).collect { |j| rand(100)}
    a2 = (1..4).collect { |j| rand()}
    p a1, a2

    Ниже представлен возможный вывод:

    [64, 17, 21, 95, 58, 24, 29, 60, 47, 63]
    [0.2009671596, 0.8890923676, 0.3349569312, 0.8719313448]

    Одним из применений случайных чисел является компьютерное моделирование метода Монте-Карло для определения площади некоторой фигуры, рассмотренный нами ранее. Напомним, что для применения этого метода фигуру вписывают в другую, известной площади, и случайным образом "бросают" точки, подсчитывая число попаданий в фигуру. При достаточно большом числе испытаний отношение числа точек, попавших внутрь фигуры, к общему числу точек, стремится к отношению их площадей.

    Задача

    Напишите программу, вычисляющее приближенное значение числа PI при помощи метода Монте-Карло.

    Решение

    Число PI равно отношению площади круга к квадрату его радиуса. Впишем круг единичного радиуса с центром в начале координат в квадрат и методом Монте-Карло найдем его площадь. Координаты случайных точек, бросаемых в круг, принадлежат интервалу (-1; 1). Величина, случайно распределенная в этом интервале, задается выражением 2*rand() - 1.

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

    puts "Введите количество точек:"
    n1, n, t1  = 0, eval(gets.chop), Time.now
    for i in 1 .. n
      x = 2*rand() - 1
      y = 2*rand() - 1
    #      проверяем попадание внутрь круга
      n1 += 1 if (x**2 + y**2) < 1
    end
    puts "PI=#{4.0*n1/n}"
    t2 = Time.now
    puts "Число точек #{n}, время расчета " +
      "около #{(t2 - t1).round} сек."

    Возможные результаты работы программы будут похожи (случайность!) на следующие:

    Введите количество точек: 10**5    PI=3.14028
    Число точек 100000, время расчета около 2 сек.
    
    Введите количество точек: 10**6    PI=3.142204
    Число точек 1000000, время расчета около 20 сек.
    #!/usr/bin/env ruby
    
    # Метод Монте-Карло
    puts "Введите количество точек:"
    n1, n, t1  = 0, eval(gets.chop), Time.now
    for i in 1 .. n
      x = 2*rand() - 1
      y = 2*rand() - 1
    #      проверяем попадание внутрь круга
      n1 += 1 if (x**2 + y**2) < 1
    end
    puts "PI=#{4.0*n1/n}"
    t2 = Time.now
    puts "Число точек #{n}, время расчета " +
      "около #{(t2 - t1).round} сек."

    Разработка пользовательских классов

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

    Задача

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

    Решение

    Поместите в файл с именем car.rb следующую программу:

    class Car
        @@NUM_CARS = 0
        def initialize
            @@NUM_CARS = @@NUM_CARS +  1
            puts @@NUM_CARS
        end
    end
    class SportsCar < Car
    end
    class FamilyCar < Car
    end
    
    a = Car.new
    b = SportsCar.new
    c = FamilyCar.new

    Для запуска программы выполните команду ruby car.rb. При создании каждого автомобиля, независимо от того, к какому (родительскому или дочернему) классу он принадлежит, значение переменной класса @@NUM_CARS увеличивается на единицу.

    #!/usr/bin/env ruby
    
    class Car
        @@NUM_CARS = 0
        def initialize
            @@NUM_CARS = @@NUM_CARS +  1
            puts @@NUM_CARS
        end
    end
    class SportsCar < Car
    end
    class FamilyCar < Car
    end
    
    a = Car.new
    b = SportsCar.new
    c = FamilyCar.new

    Задания

  • Напишите программу, запрашивающую с клавиатуры натуральное число, большее 1, и печатающую список всех простых несократимых дробей, заключенных в интервале между 0 и 1, знаменатели которых не превышают введенное число. Например, если ввести число 4, то должна быть напечатана последовательность 1/2, 1/3, 2/3, 1/4, 3/4. Подсказка: используйте функцию нахождения НОД.
  • Создайте текстовый файл, в котором разместите фамилии учащихся и их рост в сантиметрах. Напишите программу
  • печатающую фамилию и рост самых высоких учеников;
  • по введенному числу - росту с сантиметрах - печатающую список всех учащихся, чей рост не превышает введенного числа.
  • Напишите программу, вычисляющую методом Монте-Карло площадь криволинейной трапеции, ограниченной графиками функций y=sin(x) и y=0 ( x изменяется в интервале от 0 до PI ).
  • Вернуться к учебному плану