Методы модуля 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]

Перевод оригинального поста

Tags: ,

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.