Методы модуля Enumerable.
мая 24, 2010 | Published in Ruby, Основы
Модуль Enumerable в Ruby представляет собой хранилище методов для работы с исчисляемыми типами данных: массивами, хэшами и диапазонами и включается в определение всех этих классов. Enumerable содержит огромное количество методов для работы с исчисляемыми типами и решения различных задач, в случае, если вам понадобится объявить свой метод, который будет доступен для всех исчисляемых типов, вы можете сделать это в определении модуля Enumerable:
module Enumerable def my_method ... end end
Но сейчас беседа пойдет не об этом, а о том, как могут быть полезны предоставляемые модулем Enumerable методы.
В следующих примерах мы будем использовать следующий массив, который содержит названия различных транспортных средств:
vehicles = %w[ car truck boat plane helicopter bike ]
Обычная итерация с each
Стандартная итерация выполняется с использованием метода each. Это типичная для многих языков задача, в случае с PHP с этой задачей справляется конструкция for each. В Ruby решение этой задачи выглядит более красиво и лаконично и реализовывается методом-итератором each. each не является встроенным в Ruby методом, он находится в определении модуля Enumerable, который представляется ничем иным, как расширением функционала исчисляемых (и не только) типов данных. Следующий пример демонстрирует вызов метода each для массива vehicles и вывода значений содержащихся в массиве:
vehicles.each do |vehicle| puts vehicle end
Поиск значений с помощью grep
Метод grep реализовывает очень полезный функционал — поиск по содержанию на основе соответствия содержания регулярному выражению. Метод grep просто выводит все значения, которые соответствуют шаблону, а блок, передаваемый в grep позволяет проделывать над этими значениями некоторые операции.
vehicles.grep /a/ # => ["car", "boat", "plane"] vehicles.grep(/a/) { |v| v.upcase } # => ["CAR", "BOAT", "PLANE"]
Оценка всех значений, используя итератор all?
Метод all? просто принимает блок кода с содержащимся в нем условием и возвращает true или false в зависимости от того, все ли значения в массиве соответствуют содержащемуся в блоке условию:
vehicles.all? { |v| v.length >= 3 } #для каждого транспортного средства проверяем больше или равна ли длина его названия 3 символам # => true vehicles.all? { |v| v.length > 2 } # => false
Проверка на единичное соответствие используя any?
Метод any? представляет собой итератор, который очень похож на метод all?, но проверяет соответствует ли хотя бы один элемент массива переданному в блоке условию. Если хотя бы для одного элемента массива условие верно, то итератор any? после прохождения по всему массиву вернет true.
vehicles.any? { |v| v.length == 3 } # => true vehicles.any? { |v| v.length > 10 } # => false
Работаем со сложными структурами данных
В примерах выше вы уже убедились в удобстве использования методов содержащихся в модуле Enumerable, однако мы работали только с массивом данных, при этом забыли, что в большинстве случаев, особенно это касается сложных структур данных, используются хэши. Из примеров ниже вы убедитесь в удобстве использования методов из Enumerable применительно к хэшам. В качестве подопытного мы будем использовать следующий массив хэшей:
vehicles = [] vehicles << { :name => 'Car', :wheels => 4, :classes => [:ground], } vehicles << { :name => 'Truck', :wheels => 4, :classes => [:ground], } vehicles << { :name => 'Boat', :wheels => 0, :classes => [:water], } vehicles << { :name => 'Plane', :wheels => 0, :classes => [:air], } vehicles << { :name => 'Helicopter', :wheels => 0, :classes => [:air], } vehicles << { :name => 'Bike', :wheels => 2, :classes => [:ground], } vehicles << { :name => 'Sea Plane', :wheels => 0, :classes => [:air, :ground], }
Создание списка
C этой задачей отлично справляется метод collect, который собственно и создан для решения подобных заданий. Collect принимает блок кода, в котором указывается, что конкретно нужно выбрать. Collect обычно используют в связке с методом join для создания строк из выбранных данных.
vehicles.collect { |v| v[:name] } # => ["Car", "Truck", "Boat", "Plane", "Helicopter", "Bike", "Sea Plane"] vehicles.collect { |v| v[:name] }.join ', ' # => "Car, Truck, Boat, Plane, Helicopter, Bike, Sea Plane"
Поиск используя методы find и find_all
Методы find и find_all очень похожи, единственное и очень значительное различие заключается в том, что метод find возвращает первое значение, которое соответствует условию поиска и завершает поиск, а метод find_all производит поиск до конца и возвращает все соответствующие условию поиска значения.
В следующих примерах мы будем искать транспорт, который в своем названии содержит слово «Plane», транспорт с колесами и транспорт, который передвигается по земле, по воде. Метод collect в примерах используется для сборки списка (массива) с названиями транспортных средств, который будет для нас самой удобной формой представления результатов поиска.
vehicles.find { |v| v[:name] =~ /Plane/ }[:name] # => "Plane" vehicles.find_all { |v| v[:name] =~ /Plane/ }.collect { |v| v[:name] } # => ["Plane", "Sea Plane"] vehicles.find_all { |v| v[:wheels] > 0 }.collect { |v| v[:name] } # => ["Car", "Truck", "Bike"] vehicles.find_all { |v| v[:classes].include? :ground }.collect { |v| v[:name] } # => ["Car", "Truck", "Bike", "Sea Plane"] vehicles.find_all { |v| v[:classes].include? :air }.collect { |v| v[:name] } # => ["Plane", "Helicopter", "Sea Plane"]
Итерирование с хранением используя inject
Итератором, который умеет хранить данные с предыдущей итерации является итератор inject. Это очень полезный метод, который позволяет, например, найти сумму всех чисел записанных в массив, или найти все уникальные значения содержащиеся в массиве. В качестве аргументов метод inject получает 0 или [] в зависимости от того, что конкретно нужно запомнить, число или массив данных, и блок кода, который может использовать сохраненные с предыдущей итерации данные, данные переданные в настоящую итерацию и проводить с ними определенные операции. В примерах ниже мы находим суммарное количество колес у всех транспортных средств и все уникальные типы транспорта: воздушный, морской, сухопутный.
p vehicles.inject(0) { |total_wheels, v| total_wheels += v[:wheels] } # => 10 p vehicles.inject([]) { |classes, v| classes + v[:classes] }.uniq # => [:ground, :water, :air]
Перевод оригинального поста