Итак, что делать человеку, который весь день застрял дома с простудой? Напишите сообщение в блоге Medium, конечно! Хотя с момента моего последнего поста прошло всего 48 часов, я решил, что, не теряя времени, покажу вам, читатель, (не очень) долгожданную третью главу моей серии из трех частей о циклическом просмотре коллекций в ruby. Как я писал в своих предыдущих статьях здесь и здесь, эта серия из трех частей предназначена для того, чтобы служить простым резюме трех (почти) методов класса перечислителя, с которыми я часто сталкиваюсь при работе с коллекциями (в основном массивами) и манипулируя ими. , но иногда хэши и диапазоны) в Ruby. Три метода: #Map
(или #Collect
), #Select
(наоборот, #Reject
) и #Reduce
(или #Inject
). Эти методы перечисления, по-видимому, наиболее широко используются при работе с коллекциями и, таким образом, важны для любого фундаментального понимания Ruby. Вообще говоря, функция #Map
манипулирует элементами коллекции на основе заданных параметров блока; функция #Select
фильтрует коллекцию на основе параметров блока и возвращает новый массив на основе этих фильтров; метод #Reduce
возвращает новое значение, которое объединяет элементы коллекции на основе параметров блока. И просто для ясности, когда я говорю параметры блока, я имею в виду следующее:
{|item| block } Where the "item" refers to each element of the collection, and the "block" is the what is actually "being done" to each element in the array
Этот пост посвящен методам #reduce
и #inject
. Я приведу несколько примеров того, как можно использовать эти методы, но сначала давайте начнем с некоторых массивов для манипулирования (вы уже знаете упражнение):
Для начала скажу следующее: из трех моих постов в блоге о манипулировании коллекциями документация Ruby по #reduce/inject
кажется особенно запутанной. Как известно любому хорошему рубисту, прекрасной (или разочаровывающей) особенностью языка является использование нескольких форм синтаксиса/взаимозаменяемых общедоступных методов экземпляров, которые используются для выполнения одинаковых задач. Здесь я сделаю все возможное, чтобы объяснить, как я, будучи нубом в программировании, понимаю общие нюансы синтаксиса. Лучшее, что может сделать начинающий программист при изучении нового метода/функции/синтаксиса, — это открыть Sublime и просто начать играть с примерами, приведенными на страницах официальной документации для каждого языка. Понятно? Ладно, начнем.
Это «сокращенный» синтаксис для сокращения/вставки, который не принимает блочный аргумент. Я бы сказал, что этот синтаксис довольно прост; во всех этих четырех примерах метод #reduce/inject
принимает два аргумента (первый из которых является необязательным) и возвращает объединенные элементы коллекции (в данном случае массив) на основе операции во втором аргументе. Первый аргумент — это initial, который, как вы, вероятно, догадались, является начальным элементом, который «вталкивается» в обрабатываемый массив; другими словами, массив number_array
равен [1, 5, 7, 12]
, но когда указан начальный аргумент (в моих примерах это 2 или 3), то это число «подталкивается» в начало number_array
, что по существу создает копию, которая будет выглядеть как [initial, 1, 5, 7, 12]
. Довольно просто, правда? Теперь давайте посмотрим на другой синтаксис, который на этот раз принимает блочный аргумент:
Как мы видим здесь, это просто «длинная» версия предыдущего синтаксиса. Вторая функция здесь содержит initial, равный 3. В документации Ruby в качестве начального элемента блока используется |memo|
, что является аббревиатурой от «memory». В этих случаях |memo|
также может быть записано как «сумма» или «произведение» соответственно, потому что, по сути, это то, что оно представляет: «сумма» всех элементов в обрабатываемом массиве. Чтобы лучше понять |memo|
, давайте взглянем на массив строк ниже (этот пример в значительной степени заимствован с официального сайта документации Ruby):
Здесь многое происходит, я знаю. Я сделаю все возможное, чтобы разобрать это шаг за шагом, но, надеюсь, наше понимание |memo|
начнет прояснять, что именно здесь происходит. Начнем сверху:
shortest = %w{cat sheep superduper bear}
это просто причудливый способ написания
shortest = [“cat”, “sheep”, “superduper”, “bear”]
Понятно? Хорошо. Теперь давайте рассмотрим, что происходит на каждой итерации массива (надеюсь, мое понимание здесь правильное). Блок |memo|
, как я упоминал выше, в основном представляет собой «хранилище» памяти, которое «сохраняет» значение каждого элемента в массиве по мере того, как функция выполняет свой цикл. Глядя на функцию longest
, мы видим, что происходит. Первый цикл по массиву сохраняет “cat”
в блочном элементе |memo|
и сравнивает его, в данном случае, с “cat”
, который также является блочным элементом |word|
. Затем логика спрашивает: “cat”
длиннее, чем “cat”
? В данном случае это не так; таким образом, если бы цикл заканчивался прямо здесь, “cat”
как элемент блока |word|
был бы выведен на консоль. Следующий цикл по массиву спросит: является ли “cat”
(теперь сохраненный как элемент блока |memo|
) длиннее, чем “sheep”
(элемент блока |word|
)? Это не так, поэтому эта итерация «заменит» предыдущее сравнение; “sheep”
пока самое длинное слово в массиве. Следующая итерация спросит, является ли “sheep”
(теперь сохраненный как элемент блока |memo|
) длиннее, чем “superduper”
(теперь элемент блока |word|
)? Опять же, это не так, что означает, что “superduper”
будет возвращено как элемент блока слов.
Просто помните, что синтаксис тернарного оператора:
memo.length > word.length ? memo : word
читается как:
if memo.length > word.length then return memo else return word
Итак, вот что происходит в каждом цикле:
cat.length > cat.length?
Нет, поэтому верните “cat”
(слово)
cat.length > sheep.length?
Нет, поэтому верни “sheep”
(слово)
sheep.length > superduper.length?
Нет, поэтому верните “superduper”
(слово)
superduper.length > bear.length?
ДА! Так что верни “superduper”
(памятка)
Вуаля! Довольно просто, когда разбито вот так, да? Наконец, в качестве хорошей практики, вот как вы пишете функцию #reduce/inject
без использования этих методов:
И это обертка, ребята. Спасибо, что настроились на эту серию из трех частей. Ну и что дальше? Я планирую написать хорошую длинную статью о различиях Scope между Ruby и Javascript. Быть в курсе!