Ruby/Избранное с RubyNews: различия между версиями

Содержимое удалено Содержимое добавлено
м орфография
м <source> -> <syntaxhighlight> (phab:T237267)
 
Строка 11:
Увидел заметку на RubyGarden про конструкцию <code>case...when...else...end</code> и решил ею поделиться с вами. Мало кто знает, что конструкция <code>case</code> может принимать на вход любое количество аргументов.
* Начнем с малого. Продемонстрируем как <code>case</code> может обходиться вообще без аргументов:
<sourcesyntaxhighlight lang=ruby>a, b = 5, 6
 
case
Строка 18:
else
puts "нет совпадения"
end</sourcesyntaxhighlight>
: Конструкция <code>case</code> обходится без аргументов только тогда, когда выражение после <code>when</code> трактуется как логическое выражение. По сути, пример выше эквивалентен следующему примеру:
<sourcesyntaxhighlight lang=ruby>a, b = 5, 6
 
if a==b
Строка 26:
else
puts "нет совпадения"
end</sourcesyntaxhighlight>
 
* Чтобы конструкция <code>case</code> могла правильно воспринимать больше одного аргумента необходимо, чтобы количество выражений проверок <code>when</code> было точно таким же, то есть, если нужно, чтобы <code>case</code> обрабатывала два аргумента, то каждая проверка <code>when</code> также должна содержать два выражения. Обратите внимание, что аргументы <code>case</code> и <code>when</code> взяты в квадратные скобки. Если их не использовать, то интерпретатор будет выдавать сообщение о синтаксической ошибке. Рассмотрим это на примере:
<sourcesyntaxhighlight lang=ruby>a, b = 1,1
case [a,b] #!
when [1,1] #!
Строка 39:
else
puts "нет совпадений"
end</sourcesyntaxhighlight>
Думаю, что вам было интересно, т.к. меня эта возможность <code>case</code> очень даже впечатлила.
 
Строка 45:
 
Локальная переменная блока после выхода из своей области видимости, продолжает хранить прежнее значение, но только если она была определена до блока. Для начала рассмотрим работающий пример:
<sourcesyntaxhighlight lang=ruby>loop {
a = 5
break
}
puts a #-> Ошибка. Не могу найти метод или переменную a.</sourcesyntaxhighlight>
 
При попытке вывести значение переменной <code>a</code>, интерпретатор сообщает о том, что переменная <code>a</code> не определена. Предварим этот пример небольшим блоком <code>if</code>, который заранее никогда не выполняется:
<sourcesyntaxhighlight lang=ruby>if false
a = nil
end
Строка 60:
break
}
puts a #-> 5</sourcesyntaxhighlight>
 
И получаем нестандартную ситуацию в которой интерпретатор выводит значение <code>a</code>, которое по его мнению равно 5. Неверующим рекомендую проверить...
Строка 69:
 
Когда один из моих студентов занимался разработкой калькулятора на Ruby, то он столкнулся с одной интересной особенностью метода <code>eval</code>. В случае синтаксической ошибки в строке, переданой <code>eval</code> в качестве параметра, программа вылетала с ошибкой <code>SyntaxError</code>. Ниже идет пример, который демонстрирует эту ситуацию:
<sourcesyntaxhighlight lang=ruby>begin
eval('2+2+')
rescue
p "error"
end</sourcesyntaxhighlight>
Для контроля за ситуацией надо явно указать ошибку, которую вы хотите контролировать (в нашем случае <code>SyntaxError</code>).
<sourcesyntaxhighlight lang=ruby>begin
eval('2+2+')
rescue SyntaxError
p "error"
end</sourcesyntaxhighlight>
Теперь все работает! Осталось только заметить, что если строка приходит из внешнего источника, то желательно ее предварительно проверить на соответствие вашим ожиданиям. Иначе, вы будете кормить хакеров... :-) Как пояснил Yuri Leikind, этот эффект связан с тем, что rescue без параметра отлавливает ошибки выполнения программы (<code>Runtime Error</code>), а приведенный выше пример вызывает исключение (<code>Exception</code>). В общем, проблема все равно решается так, как я описал выше. :-)
 
Строка 89:
 
''Устаревшее решение'':
<sourcesyntaxhighlight lang=ruby>a = [1, 3, 4,67,-3]
max = a[0]
i = 1
Строка 98:
i = i+1
end
puts max</sourcesyntaxhighlight>
 
''Современное решение'':
<sourcesyntaxhighlight lang=ruby>p [1, 3, 4,67,-3].max</sourcesyntaxhighlight>
 
'''Задание 2''': Ввести с клавиатуры размерность массива - целое положительное число, после чего заполнить все его элементы действительными числами, введенными с клавиатуры.
 
''Устаревшее решение'':
<sourcesyntaxhighlight lang=ruby>n = -1
while n < 1
print "Введите размерность массива: "
Строка 118:
i = i+1
end
p a</sourcesyntaxhighlight>
 
''Современное решение'':
<sourcesyntaxhighlight lang=ruby>$stdout.sync = true # для корректного запуска из SciTE
p Array.new( ( print "Введите размерность массива: " ; gets.to_i ) ){ |i|
print "Введите #{i}-й элемент массива: " ; gets.to_f
}</sourcesyntaxhighlight>
 
'''Задание 3''': Ввести с клавиатуры массив целых чисел и определить номер минимального элемента массива (отсчет начинается с нуля).
 
''Устаревшее решение'':
<sourcesyntaxhighlight lang=ruby>n = -1
while n < 1
print "Input n: "
Строка 149:
end
end
puts "Number of minimum is #{numberMin}"</sourcesyntaxhighlight>
 
''Современное решение:''
<sourcesyntaxhighlight lang=ruby>$stdout.sync = true # для корректного запуска из SciTE
a = Array.new( ( print "Input n: " ; gets.to_i ) ){ |i|
print "Input #{i}-number: " ; gets.to_f
}
puts "Number of minimum is #{ a.index( a.min ) }"</sourcesyntaxhighlight>
 
== Генерация пароля или новый взгляд на метод <code>rand</code> ==
 
Рассмотрим классическую задачу генерации пароля. Алгоритм решения прост до безобразия: формируем словарь символов из которых будет состоять пароль и затем случайным образом выбираем символы из этого словаря. Результат формируется в виде строки и выводится на экран. Для решения данной задачи "в одну строчку" мы будем использовать возможность инициализации массива через блок, которая появилась в Ruby начиная с версии 1.8. Итак, сразу оговоримся, что наш пароль будет состоять из латинских букв верхнего и нижнего регистра, а также из арабских цифр. Решения данной задачи тогда будет выглядеть так:
<sourcesyntaxhighlight lang=ruby>chars = ['0'..'9','a'..'z','A'..'Z'].map{ |r| r.to_a }.flatten
puts Array.new(8){ chars[ rand( chars.size ) ] }.join</sourcesyntaxhighlight>
Теперь на примере решения данной задачи, хотелось бы подемонстрировать применение новых методов генерации псевдослучайной последовательности. Создадим мы эти методы для класса <code>Integer</code> (целые числа), <code>String</code> (строки), <code>Array</code> (массивы) и <code>Range</code> (диапазоны). Начнем с самого простого - с целых чисел:
<sourcesyntaxhighlight lang=ruby>class Integer
def rand
Kernel.rand( self )
end
end</sourcesyntaxhighlight>
Как видно из описания метода, он генерит случайное число от 0 до <code>self</code>, исключая self. Теперь применим вновь созданный метод к решению нашей задачи:
<sourcesyntaxhighlight lang=ruby>chars = ['0'..'9','a'..'z','A'..'Z'].map{ |r| r.to_a }.flatten
puts Array.new(8){ chars[ chars.size.rand ] }.join</sourcesyntaxhighlight>
Теперь рассмотрим метод rand для массивов. Дальше мы его будем использовать для описания методов других классов. Итак, описание метода rand для класса <code>Array</code>:
<sourcesyntaxhighlight lang=ruby>class Array
def rand
self[ size.rand ]
end
end</sourcesyntaxhighlight>
Как видно из описания метода, он возвращает произвольный элемент массива. Теперь перепишем решение задачи генераци пароля через вышеописанный метод:
<sourcesyntaxhighlight lang=ruby>chars = ['0'..'9','a'..'z','A'..'Z'].map{ |r| r.to_a }.flatten
puts Array.new(8){ chars.rand }.join</sourcesyntaxhighlight>
По-моему получилось неплохо. Давайте смотреть дальше. Теперь у нас на очереди метод <code>rand</code> для класса <code>String</code>:
<sourcesyntaxhighlight lang=ruby>class String
def rand
self.split("").rand
end
end</sourcesyntaxhighlight>
Как видно, данный метод возвращает произвольный символ строки. Для простоты реализации он реализован через использование метода <code>Array#rand</code>. Перепишем решение задачи генерации пароля через вровь написанный нами метод:
<sourcesyntaxhighlight lang=ruby>chars = ['0'..'9','a'..'z','A'..'Z'].map{ |r| r.to_a }.flatten.join
puts Array.new(8){ chars.rand }.join</sourcesyntaxhighlight>
Итак, остался последний класс, который еще пока не имеет метода <code>rand</code>. Для класса <code>Range</code> мы будем использовать тот же поход, что и для <code>String</code>, а именно сведением класса <code>Range</code> к массиву, а затем уже выборкой случайного элемента из этого диапазона. Смотрим реализацию:
<sourcesyntaxhighlight lang=ruby>class Range
def rand
to_a.rand
end
end</sourcesyntaxhighlight>
Переписываем решение исходной задачи через использование метода Range#rand (последний раз за сегодня):
<sourcesyntaxhighlight lang=ruby>chars = ['0'..'9','a'..'z','A'..'Z'].map{ |r| r.to_a }.flatten
puts Array.new(8){ chars[ (0...chars.size).rand ] }.join</sourcesyntaxhighlight>
Надеюсь, что я не сильно загрузил неискушенного читателя. Если загрузил, то включите музыку и разгрузитесь. :-)
 
Строка 204:
 
Многие из студентов уже встречались с такими словами и даже пытались самостоятельно понять, что они значат. Но вопрос все равно возникает, и хотелось бы его разобрать по кускам, чтобы он больше не возникал. Начнем с того, что подпрограммы, функции и методы - это все одно и тоже. Отличия лишь в незначительных деталях. Все это кусок программы, которые программист решил объявить всего лишь один раз, а потом вызывать столько сколько душе угодно. Например, программисту заказали программу подсчета суммы факториалов (помните <code>1*2*:*N</code>) двух чисел. Вот как он написал бы программу, если бы у него не было возможности составлять подпрограммы:
<sourcesyntaxhighlight lang=ruby>n1, n2 = 4, 5
p1, p2 = 1, 1
n1.times{ |i| p1 *= i + 1 }
n2.times{ |i| p2 *= i + 1 }
puts "#{n1}! + #{n2}! = #{p1 + p2}"</sourcesyntaxhighlight>
Он тут же смекнул, что писать кусок программы подсчета факториала числа он написал дважды (чисел, то две штуки) и решил написать подпрограмму, которая этот факториал числа вычислит. Вот как будет выглядеть более продвинутая программа:
<sourcesyntaxhighlight lang=ruby>def fact( int )
pr = 1
int.times{ |i| pr *= i + 1 }
Строка 216:
end
n1, n2 = 4, 5
puts "#{n1}! + #{n2}! = #{fact(n1) + fact(n2)}"</sourcesyntaxhighlight>
А если нужно было бы складывать три числа? Выигрыш в простоте очевиден. А теперь давайте разберемся, что же такое мы написал смекалистый программист. Разберем сначала верхний кусок. Он называется ОПРЕДЕЛЕНИЕМ ПОДПРОГРАММЫ или ОПРЕДЕЛЕНИЕМ МЕТОДА (от англ. define methods). Верхняя строчка состоит из ключевого слова <code>def</code>, названия метода (в нашем случае <code>fact</code>) и списка аргументов, то есть входных данных (в нашем случае <code>int</code>). Если в списке аргументов указано всего одно имя переменной, то и передавать в метод (при вызове) надо тоже один аргумент (обратите внимание на вызов метода <code>fact</code>, <code>fact(n1)</code>. Его не обязательно вызывать с переменной в качестве аргумента. Достаточно указать какое либо число, например <code>fact(4)</code> ). Помимо того, взгляните на <code>end</code> в конце тела метода. ЛЮБОЙ МЕТОД должен заканчиваться <code>end</code>. Типичный шаблон для создания метода выглядит примерно так:
<sourcesyntaxhighlight lang=ruby>def имя_метода( имя_первого_аргумента, имя_второго_аргумента, и т.д.)
кусок_программы_который_мы_хотим_поместить_в_подпрограмму
end</sourcesyntaxhighlight>
Вызывать наш метод надо примерно так:
<sourcesyntaxhighlight lang=ruby>имя_метода( значение_первого_аргумента, значение_второго_аргумента, и т.д.)</sourcesyntaxhighlight>
Теперь обратите внимание на то, что переменная pr в теле метода (на которую мы заменили переменные p1 и p2) расположена в последней строчке подпрограммы (т.е. непосредственно перед end). Вроде как она там прохлаждается и совсем не нужна, но это не так. Таким образом, мы говорим, что переменная pr является РЕЗУЛЬТАТОМ РАБОТЫ ПОДПРОГРАММЫ. И как раз результат перемножения хранится в этой переменной, т.е. данная переменная содержит факториал числа int (т.е. числа, которое передано в качестве первого аргумента). Отсюда и получается, что наша подпрограмма ВОЗВРАЩАЕТ факториал числа. Теперь ПРАВИЛО: программный КОД, который ПОВТОРЯЕТСЯ больше двух раз, ДОЛЖЕН быть ВЫНЕСЕН В ПОДПРОГРАММУ. А сейчас немного о различиях между подпрограммой, функцией и методом. Дело в том, что ПОДПРОГРАММА - это ОБЩЕЕ НАЗВАНИЕ методов, функций и процедур, т.е. объединяющее понятие. Если программист говорит подпрограмма, то он может иметь в виду как метод, так и процедуру. Теперь про различия между методами, функциями и процедурами. Процедуры - это устаревшая конструкция, от которой многие языки программирования отказались. На данный момент она используется только в языке Pascal. СЕЙЧАС в основном ОСТАЛИСЬ только МЕТОДЫ, и ФУНКЦИИ. От процедур их отличает то, что ОНИ ВОЗВРАЩАЮТ РЕЗУЛЬТАТ. Иными словами, ПРОЦЕДУРА - это метод или функция, которые НЕ ВОЗВРАЩАЮТ РЕЗУЛЬТАТА. Теперь пор различия между методами и функциями. МЕТОДЫ есть только в ОБЪЕКТНО-ОРИЕНТИРОВАННЫХ ЯЗЫКАХ. ФУНКЦИИ во всех ОСТАЛЬНЫХ. Отсюда можно заключить, что мы написали с вами подпрограмму, которая является методом, т.к. Ruby - ОБЪЕКТНО-ОРИЕНТИРОВАННЫЙ ЯЗЫК. PS. Большие буквы -- это влияние самоучителя Драгункина... :-)
 
Строка 228:
 
Просматривая исходники некоторых стандартных библиотек Ruby наткнулся на интересную тенденцию в описании классов. Мое наблюдение касается объявление методов класса. Напомню, что такое метод класса... Метод класса - это метод, который вызывается не относительно объекта, а относительно самого класса. Пример:
<sourcesyntaxhighlight lang=ruby>puts Dir.getwd # getwd -- метод класса
Dir.new("testdir").each{|x| puts "Got #{x}" } # each -- метод объекта, а new -- метод класса.</sourcesyntaxhighlight>
В первом случае происходит вызов метода класса, а во втором - метода объекта (метод класса <code>new</code> создает объект, от которого вызывается метод <code>each</code>). Перед вызовом метода класса всегда идет название класса (в нашем случае <code>Dir</code>). В чем же их отличие при объявлении? Ниже представлен пример объявления класса метода и класса объекта:
<sourcesyntaxhighlight lang=ruby>class Dir
def Dir.getwd
# тело метода
Строка 238:
# тело метода
end
end</sourcesyntaxhighlight>
Теперь про тенденцию... Если методов класса много, то в стандартных библиотеках просто создают еще один блок, в который помещают методы класса. Пример:
<sourcesyntaxhighlight lang=ruby>class Dir
class << self
def getwd
Строка 249:
# тело метода
end
end</sourcesyntaxhighlight>
Вот такое интересное наблюдение. Думаю, что эта методика закреплена в каком либо регламентирующем документе и в дальнейшем будет стандартом.
 
Строка 255:
 
Какие методы в Ruby самые популярные? Естественно, что ответ на подобный вопрос зависит от опытности программиста и его собственного стиля. А ведь хочется узнать ответ "в общем"... Для чего мне это? Ну как же, детей надо обучать методам первой необходимости (остальные сами выучат). Им же лень учить все! Поэтому при проектировании учебных пособий следует учитывать частоту использования тех или иных методов. Метод <code>[]</code> (он же <code>.at</code>) учитывать не будем, ибо ясно, что он один из популярнейших. Чтобы найти наши 100 излюбленных методов Руби мы напишем простенькую программу (которая имеет право глючить) и натравим ее на каталог <tt>ruby</tt> (для узости можно натравить только на каталог <tt>ruby/lib</tt>). Методы будем искать и в строках и в коментариях. Для тех, кто хочет чистоты эксперимента, может удалять из обработки строки и коментарии. Я их оставил умышленно. Итак, код программы:
<sourcesyntaxhighlight lang=ruby>require 'find'
result = []
Find.find('c:/ruby/'){ |path|
Строка 264:
puts (result - ['.com','.jp','.org','.rb','.rbw','.amazon']).inject( Hash.new(0) ){ |result,elem|
result[ elem ] = result[ elem ].succ result }.sort_by{ |array| array[1]
}.reverse[0...100].map{ |array| array.reverse.join(' : ') }</sourcesyntaxhighlight>
Как видно из кода программы, она предельно проста и хватает даже доменные зоны в качестве методов (а чего?! метод .com очень даже ничего). Результат ее работы примерно такой (цифра слева — это частота использования):
<tt>11866 : .new
Строка 371:
 
Правил я программку для mikrit'a и применил достаточно интересный подход для составных условий вида <code>type == const1 || type == const2</code>. Итак, чтобы было понятно, продемонстрирую пример кода:
<sourcesyntaxhighlight lang=ruby>CONST1, CONST2 = 45, 37
var = gets.to_i
puts( if CONST1 == var || CONST2 == var then "yes" else "no" end )</sourcesyntaxhighlight>
В данном примере наглядно видно дублирование кода. Попытаемся от него избавиться:
<sourcesyntaxhighlight lang=ruby>CONST1, CONST2 = 45, 37
var = gets.to_i
puts( if [CONST1,CONST2].include?( var ) then "yes" else "no" end )</sourcesyntaxhighlight>
Данный способ позволяет не только убрать дублирование кода, но и (в случае необходимости) добавить еще одно подобное условие.
 
Строка 383:
 
Во время программирования окошек всегда приходится создавать массу констант. Чтобы потом на эти константы вешать обработчики событий. И вот какая меня посетила идея по этому поводу. Чаще всего первые строчки типичной оконной программы выглядят примерно так:
<sourcesyntaxhighlight lang=ruby>ID_FRAME = 1
ID_DIALOG_1 = 2
ID_DIALOG_2 = 3
ID_DIALOG_3 = 4</sourcesyntaxhighlight>
Иногда можно увидеть, как разработчик выравнивает объявления констант в одну строчку:
<sourcesyntaxhighlight lang=ruby>ID_FRAME, ID_DIALOG_1, ID_DIALOG_2, ID_DIALOG_3 = 1,2,3,4</sourcesyntaxhighlight>
А теперь продолжим мысль и заменим правую часть более короткой записью:
<sourcesyntaxhighlight lang=ruby>ID_FRAME, ID_DIALOG_1, ID_DIALOG_2, ID_DIALOG_3 = (1..4).to_a</sourcesyntaxhighlight>
Теперь используем оператор <code>*</code> вместо метода </code>.to_a</code>, что позволит запись сделать более изящной, но и более непонятной:
<sourcesyntaxhighlight lang=ruby>ID_FRAME, ID_DIALOG_1, ID_DIALOG_2, ID_DIALOG_3 = *1..4</sourcesyntaxhighlight>
[[Категория:Ruby|Избранное с RubyNews]]