К сожалению.Я имел в виду итераторы более высокого уровня уровня , а не высшего порядка . Каждый итератор, конечно, по определению является высшим порядком.
По сути, итерация является концепцией очень низкого уровня.Цель программирования - сообщить о намерениях другим заинтересованным лицам в команде.«Инициализация пустого массива, затем итерация по другому массиву и добавление текущего элемента этого массива в первый массив, если он делится на два без остатка» - это не намерение связи.«Выбор всех четных чисел» равен .
В общем, вы почти никогда не выполняете итерацию по коллекции только ради итерации.Вы либо хотите
- каким-либо образом преобразовать каждый элемент (обычно это называется
map
, в Ruby и Smalltalk это collect
, а в .NET и SQL - Select
), - уменьшает всю коллекцию до некоторого единственного значения, например, вычисляя сумму или среднее значение или стандартное отклонение списка футбольных оценок (в теории категорий это называется катаморфизм , в функциональном программировании этоэто
fold
или reduce
, в Smalltalk это inject:into:
, в Ruby это inject
и в .NET Aggregate
), - отфильтровывает все элементы, которые удовлетворяют определенному условию (
filter
в большинстве функциональных языков select
в Smalltalk и Ruby, find_all
в Ruby, Where
в .NET и SQL), - отфильтровывают все элементы, которые не удовлетворяютусловие (
reject
в Smalltalk и Ruby) - найти первый элемент, который удовлетворяет условию (
find
в Ruby) - считать элементы, которые удовлетворяют условию (
count
вRuby) - проверить, все ли элементы (
all?
),хотя бы один элемент (any?
) или ни одного элемента (none?
) удовлетворяет условию - группирует элементы в сегменты на основе некоторого дискриминатора (
group_by
в Ruby, .NET и SQL) - разбить коллекцию на две коллекции на основе некоторого предиката (
partition
) - отсортировать коллекцию (
sort
, sort_by
) - объединить несколько коллекций в одну (
zip
) - и так далее, и так далее ...
Почти никогда - ваша цель просто перебрать коллекцию.
В частностиreduce
ака.inject
ака.fold
ака.inject:into:
ака.Aggregate
ака. катаморфизм ваш друг.Есть причина, почему у него такое причудливое звучание математического названия: оно чрезвычайно мощное.Фактически, большинство из того, что я упомянул выше, может быть реализовано в терминах reduce
.
По сути, то, что делает reduce
, это «сводит» всю коллекцию к одному значению, используя некоторыефункция.У вас есть какое-то значение аккумулятора, а затем вы берете значение аккумулятора и первый элемент и вводите его в функцию.Результатом этой функции становится новый аккумулятор, который вы соединяете со вторым элементом и передаете в функцию и т. Д.
Наиболее очевидным примером этого является суммирование списка чисел:
[4, 8, 15, 16, 23, 42].reduce(0) {|acc, elem|
acc + elem
}
Итак, аккумулятор начинается как 0
, и мы передаем первый элемент 4
в функцию +
.Результат - 4
, который становится новым аккумулятором.Теперь мы передаем следующий элемент 8
и в результате получаем 12
.И это продолжается до последнего элемента, и в результате они все время были мертвы.Нет, подождите, результат равен 108
.
Ruby фактически позволяет нам использовать несколько ярлыков: если тип элемента совпадает с типом аккумулятора, вы можете опустить аккумулятор, а Ruby простопередайте первый элемент в качестве первого значения для аккумулятора:
[4, 8, 15, 16, 23, 42].reduce {|acc, elem|
acc + elem
}
Также мы можем использовать Symbol#to_proc
здесь:
[4, 8, 15, 16, 23, 42].reduce(&:+)
И на самом деле, если вы передадите reduce
aSymbol
аргумент, который будет обрабатываться как имя функции, используемой для операции сокращения:
[4, 8, 15, 16, 23, 42].reduce(:+)
Однако суммирование - это не все, что reduce
может сделать. На самом деле, я считаю этот пример немного опасным. Все, кому я это показал, сразу поняли: «Ааа, так что это , что такое reduce
», но, к сожалению, некоторые также подумали, что сумма суммирования равна all reduce
, и это определенно не тот случай. Фактически, reduce
- это общий метод итерации , под которым я подразумеваю, что reduce
может делать все, что может делать each
. В частности, вы можете хранить произвольное состояние в аккумуляторе.
Например, я писал выше, что reduce
уменьшает коллекцию до одного значения. Но, конечно, это «единственное значение» может быть сколь угодно сложным. Это может быть, например, сама коллекция. Или строка:
class Array
def mystery_method(foo)
drop(1).reduce("#{first}") {|s, el| s << foo.to_str << el.to_s }
end
end
Это пример того, как далеко вы можете пойти, играя трюками с аккумулятором. Если вы попробуете это, вы, конечно, узнаете это как Array#join
:
class Array
def join(sep=$,)
drop(1).reduce("#{first}") {|s, el| s << sep.to_str << el.to_s }
end
end
Обратите внимание, что нигде в этом "цикле" я не должен следить за тем, нахожусь ли я на последнем или втором на последнем элементе. Также нет никаких условных в коде. Здесь нет потенциальных ошибок для забора. Если вы думаете о том, как реализовать это с помощью each
, вам придется как-то отслеживать индекс и проверять, находитесь ли вы на последнем элементе, а затем иметь if
, чтобы предотвратить испуская разделитель в конце.
Поскольку я писал выше, что вся итерация может быть сделана с помощью reduce
, я с таким же успехом могу доказать это. Вот методы Ruby Enumerable
, реализованные в терминах reduce
вместо each
, как обычно. (Обратите внимание, что я только начал и только достиг g .)
module Enumerable
def all?
reduce(true) {|res, el| res && yield(el) }
end
def any?
reduce(false) {|res, el| res || yield(el) }
end
def collect
reduce([]) {|res, el| res << yield(el) }
end
alias_method :map, :collect
def count
reduce(0) {|res, el| res + 1 if yield el }
end
def detect
reduce(nil) {|res, el| if yield el then el end unless res }
end
alias_method :find, :detect
def drop(n=1)
reduce([]) {|res, el| res.tap {|res| res << el unless n -= 1 >= 0 }}
end
def drop_while
reduce([]) {|res, el| res.tap {|res| res << el unless yield el }}
end
def each
reduce(nil) {|_, el| yield el }
end
def each_with_index
tap { reduce(-1) {|i, el| (i+1).tap {|i| yield el, i }}}
end
def find_all
reduce([]) {|res, el| res.tap {|res| res << el if yield el }}
end
alias_method :select, :find_all
def grep(pattern)
reduce([]) {|res, el| res.tap {|res| res << yield(el) if pattern === el }}
end
def group_by
reduce(Hash.new {|hsh, key| hsh[key] = [] }) {|res, el| res.tap {|res|
res[yield el] = el
}}
end
def include?(obj)
reduce(false) {|res, el| break true if res || el == obj }
end
def reject
reduce([]) {|res, el| res.tap {|res| res << el unless yield el }}
end
end
[Примечание: для этого поста я сделал несколько упрощений. Например, согласно стандартному протоколу Ruby Enumerable, each
должен возвращать self
, поэтому вам придется добавить туда дополнительную строку; другие методы ведут себя немного по-разному, в зависимости от того, какого типа и сколько аргументов вы передаете и так далее. Я оставил их, потому что они отвлекают от того, что я пытаюсь сделать.]