Как я могу выполнить идиоматическое нерекурсивное сглаживание в ruby? - PullRequest
4 голосов
/ 09 июня 2010

У меня есть метод, который возвращает массив массивов. Для удобства я использую коллекцию в коллекции, чтобы собрать их вместе.

arr = collection.collect {|item| item.get_array_of_arrays}

Теперь я хотел бы иметь один массив, который содержит все массивы. Конечно, я могу перебрать массив и использовать для этого оператор +.

newarr = []    
arr.each {|item| newarr += item}

Но это некрасиво, есть ли лучший способ?

Ответы [ 3 ]

28 голосов
/ 09 июня 2010

Существует метод выравнивания массива в 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(:+)

В коде нет изменений, кроме имени переменной.

1 голос
/ 09 июня 2010
arr.inject([]) { |main, item| main += item }
0 голосов
/ 09 июня 2010

Кажется, я не совсем понимаю вопрос ... Array # сглаживает то, что вы ищете?

[[:a,:b], [1,2,3], 'foobar'].flatten
# => [:a, :b, 1, 2, 3, 'foobar']
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...