Существует метод выравнивания массива в Ruby: Array#flatten
:
newarr = arr.flatten(1)
Из вашего описания на самом деле выглядит, что вы больше не заботитесь о arr
, поэтому нет необходимости сохранять старое значение arr
, мы можем просто изменить его:
arr.flatten!(1)
(В Ruby есть правило, которое гласит, что если у вас есть два метода, которые делают одно и то же, но один делает это несколько удивительным образом, вы называете этот метод таким же, как и другой метод, но с восклицательным знаком в конце. В этом случае оба метода выравнивают массив, но версия с восклицательным знаком делает это, уничтожая исходный массив.)
Однако, хотя в данном конкретном случае на самом деле существует метод, который делает именно то, что вам нужно, в вашем коде действует более общий принцип: у вас есть последовательность вещей, и вы повторяете ее и пытаетесь «уменьшить» "это в одну вещь. В этом случае это трудно увидеть, потому что вы начинаете с массива, а заканчиваете массивом. Но, изменив всего пару мелких деталей в вашем коде, все это внезапно становится ослепительно очевидным:
sum = 0
arr.each {|item| sum += item } # assume arr is an array of numbers
Это точно такой же шаблон.
То, что вы пытаетесь сделать, известно как катаморфизм в теории категорий, кратность в математике, уменьшение в функциональном программировании, inject:into:
в Smalltalk и реализуется Enumerable#inject
и его псевдонимом Enumerable#reduce
(или в данном случае на самом деле Array#inject
и Array#reduce
) в Ruby.
Очень легко обнаружить: всякий раз, когда вы инициализируете переменную-накопитель вне цикла, а затем присваиваете ей или изменяете объект, на который она ссылается во время каждой итерации цикла, у вас есть случай для reduce
.
В данном конкретном случае ваш аккумулятор равен newarr
, и операция добавляет к нему массив.
Итак, ваш цикл может быть идиоматически переписан так:
newarr = arr.reduce(:+)
Опытный Рубист, конечно, сразу это увидит. Тем не менее, даже новичок в конечном итоге доберется до места, выполнив несколько простых шагов по рефакторингу, вероятно, похожих на это:
Во-первых, вы понимаете, что на самом деле составляет сгиб:
newarr = arr.reduce([]) {|acc, el| acc += el }
Далее вы понимаете, что присваивать acc
совершенно не нужно, потому что reduce
перезаписывает содержимое acc
в любом случае на значение результата каждой итерации:
newarr = arr.reduce([]) {|acc, el| acc + el }
В-третьих, нет необходимости вставлять пустой массив в качестве начального значения для первой итерации, поскольку все элементы arr
в любом случае уже являются массивами:
newarr = arr.reduce {|acc, el| acc + el }
Это, конечно, можно еще упростить с помощью Symbol#to_proc
:
newarr = arr.reduce(&:+)
И на самом деле нам здесь не нужно Symbol#to_proc
, потому что reduce
и inject
уже принимают параметр символа для операции:
newarr = arr.reduce(:+)
Это действительно - это общий шаблон. Если вы помните приведенный выше пример sum
, он будет выглядеть так:
sum = arr.reduce(:+)
В коде нет изменений, кроме имени переменной.