Хэл Фултон - Программирование на языке Ruby
- Название:Программирование на языке Ruby
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2007
- Город:Москва
- ISBN:5-94074-357-9
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Хэл Фултон - Программирование на языке Ruby краткое содержание
Ruby — относительно новый объектно-ориентированный язык, разработанный Юкихиро Мацумото в 1995 году и позаимствовавший некоторые особенности у языков LISP, Smalltalk, Perl, CLU и других. Язык активно развивается и применяется в самых разных областях: от системного администрирования до разработки сложных динамических сайтов.
Книга является полноценным руководством по Ruby — ее можно использовать и как учебник, и как справочник, и как сборник ответов на вопросы типа «как сделать то или иное в Ruby». В ней приведено свыше 400 примеров, разбитых по различным аспектам программирования, и к которым автор дает обстоятельные комментарии.
Издание предназначено для программистов самого широкого круга и самой разной квалификации, желающих научиться качественно и профессионально работать на Ruby.
Программирование на языке Ruby - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
8.3. Перечисляемые структуры в общем
Что делает набор перечисляемым? Вообще-то сам тот факт, что это набор. Модуль Enumerable
требует, чтобы был определен стандартный итератор each
. Последовательность обхода не имеет значения, так как даже неупорядоченные наборы, например хэш, могут обладать итераторами.
Кроме того, если предполагается пользоваться методами min
, max
и sort
, то для набора должен быть определен метод сравнения ( <=>
). Все это достаточно очевидно.
Итак, перечисляемая структура представляет собой набор, в котором можно производить поиск, который можно обойти и, быть может, отсортировать. В любой определенный пользователем набор, не являющийся подклассом существующего системного класса, имеет смысл подмешивать модуль Enumerable
.
Имейте в виду — все сказанное о какой-то одной перечисляемой структуре относится ко всем. В качестве примеров таких структур можно назвать массив, хэш, дерево и т.д.
Конечно, у каждой структуры есть свои нюансы. Массив — это упорядоченный набор отдельных элементов, а хэш — неупорядоченный набор пар ключ-значение. Понятно, что в каких-то отношениях они будут вести себя по-разному.
Многие методы, с которыми мы познакомились при изучении массивов и хэшей (например, map
и find
), на самом деле определены в модуле Enumerable
. Часто было трудно решить, как подать материал. Любая путаница или неточность — моя вина!..
Массив — наиболее часто употребляемый набор, подмешивающий этот модуль. Поэтому по умолчанию я буду пользоваться в примерах именно массивами.
8.3.1. Метод inject
Метод inject
пришел в Ruby из языка Smalltalk (впервые он появился в версии Ruby 1.8). Его поведение интересно, хотя с первого раза понять его нелегко.
Он отражает тот факт, что мы часто хотим обойти список и по ходу «аккумулировать» некоторый результат. Конечно, самый естественный пример — суммирование чисел в списке. Но и для других операций обычно есть некий «аккумулятор» (которому присваивается начальное значение) и применяемая функция (в Ruby она представлена блоком).
В качестве тривиального примера рассмотрим массив чисел, которые нужно просуммировать:
nums = [3,5,7,9,11,13]
sum = nums.inject(0) {|x,n| x+n }
Обратите внимание, что начальное значение аккумулятора равно 0 («нейтральный элемент» для операции сложения). Затем блок получает текущее значение аккумулятора и значение текущего элемента списка. Действие блока заключается в прибавлении нового значения к текущей сумме.
Ясно, что этот код эквивалентен следующему:
sum = 0
nums.each {|n| sum += n }
В данном случае уровень абстракции лишь немногим выше. Если идея метода inject
не укладывается у вас в голове, не пользуйтесь им. Но если удалось преодолеть первоначальное непонимание, то вы сможете найти ему новые элегантные применения.
Начальное значение аккумулятора задавать необязательно. Если оно опущено, то в качестве такового используется значение первого элемента, который при последующих итерациях пропускается,
sum = nums.inject {|x,n| x+n }
# To же самое, что:
sum = nums[0]
nums[1..-1].each {|n| sum + = n }
Другой похожий пример — вычисление произведения чисел. В данном случае аккумулятору следует присвоить начальное значение 1 (нейтральный элемент для операции умножения).
prod = nums.inject(1) {|x,n| x*n }
# или
prod = nums.inject {|x,n| x*n }
В следующем немного более сложном примере мы находим самое длинное слово в списке:
words = %w[ alpha beta gamma delta epsilon eta theta ]
longest_word = words.inject do |best,w|
w.length > best.length ? w : best
end
# Возвращается значение "epsilon".
8.3.2. Кванторы
Кванторы any?
и all?
появились в версии Ruby 1.8, чтобы было проще проверять некоторые свойства набора. Оба квантора принимают в качестве параметр блок (который должен возвращать значение true
или false
).
Nums = [1,3,5,8,9]
# Есть ли среди чисел четные?
flag1 = nums.any? {|x| x % 2 == 0 } # true
# Все ли числа четные?
flag2 = nums.all? {|x| x % 2 == 0 } # false
Если блок не задан, то просто проверяется значение истинности каждого элемента. Иными словами, неявно добавляется блок {|x| x }
.
flag1 = list.all? # list не содержит ни одного false или nil.
flag1 = list.any? # list содержит хотя бы одно истинное значение
# не nil и не false).
8.3.3. Метод partition
Как говорится, «в мире есть два сорта людей: те, что делят людей по сортам, и те, что не делят». Метод partition
относится не к людям (хотя мы можем представить их в Ruby как объекты), но тоже делит набор на две части.
Если при вызове partition
задан блок, то он вычисляется для каждого элемента набора. В результате создаются два массива: в первый попадают элементы, для которых блок вернул значение true
, во второй — все остальные. Метод возвращает массив, двумя элементами которого являются эти массивы.
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
odd_even = nums.partition {|x| x % 2 == 1 }
# [[1,3,5,7,9],[2,3,4,6,8]]
under5 = nums.partition {|x| x < 5 }
# [[1,2,3,4],[5,6,7,8,9]]
squares = nums.partition {|x| Math.sqrt(x).to_i**2 == x }
# [[1,4,9], [2,3,5,6,7,8]]
Если нужно разбить набор больше чем на две группы, придется написать собственный метод. Я назвал его classify
по аналогии с методом из класса Set
.
module Enumerable
def classify(&block)
hash = {}
self.each do |x|
result = block.call(x)
(hashfresult] ||= []) << x
end
hash
end
end
nums = [1,2,3,4,5,6,7,8,9]
mod3 = nums.classify {|x| x % 3 }
# { 0=>[3,6,9], 1=>[1,4,7], 2=>[2,5,8] }
words = %w( area arboreal brick estrous clear donor ether filial
patina ]
vowels = words.classify {|x| x.count("aeiou") }
# {1=>["brick"], 2=>["clear", "donor", "ether"],
# 3=>["area", "estrous", "filial", "patina"], 4=>["arboreal"]}
initials = words.classify {|x| x[0..0] }
# {"a"=>["area", "arboreal"], "b"=>["brick"], "c"=>["clear"],
# "d"=>["donor"], "p"=>["patina"], "e"=>["estrous", "ether"],
# "f"=>["filial"]}
8.3.4. Обход с группировкой
До сих пор мы обходили список по одному элементу за раз. Но иногда желательно на каждой итерации анализировать по два, три или более элементов.
Итератор each_slice
принимает в качестве параметра число n, равное числу просматриваемых на каждой итерации элементов. (Для работы с ним нужна библиотека enumerator
.) Если не осталось достаточного количества элементов, размер последнего фрагмента будет меньше.
require 'enumerator'
arr = [1,2,3,4,5,6,7,8,9,10]
arr.each_slice(3) do |triple|
puts triple.join(",")
end
# Выводится:
# 1,2,3
# 4,5,6
# 7,8,9
# 10
Имеется также итератор each_cons
, который позволяет обходить набор методом «скользящего окна» заданного размера. (Если название кажется вам странным, знайте, что это наследие языка Lisp.) В таком случае фрагменты всегда будут иметь одинаковый размер.
Интервал:
Закладка: