Итак, что делать человеку, который весь день застрял дома с простудой? Напишите сообщение в блоге 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. Быть в курсе!