Есть ли у Ruby что-то вроде понимания списков Python?

Python имеет приятную функцию:

print([j**2 for j in [2, 3, 4, 5]]) # => [4, 9, 16, 25]

В Ruby это еще проще:

puts [2, 3, 4, 5].map{|j| j**2}

но если это касается вложенных циклов, Python выглядит более удобным.

В Python мы можем сделать это:

digits = [1, 2, 3]
chars = ['a', 'b', 'c']    
print([str(d)+ch for d in digits for ch in chars if d >= 2 if ch == 'a'])    
# => ['2a', '3a']

Эквивалент в Ruby:

digits = [1, 2, 3]
chars = ['a', 'b', 'c']
list = []
digits.each do |d|
    chars.each do |ch|
        list.push d.to_s << ch if d >= 2 && ch == 'a'
    end
end
puts list

Есть ли у Ruby что-то подобное?

Ответ 1

Обычный способ Ruby состоит в том, чтобы правильно комбинировать Enumerable и Array методы для достижения то же самое:

digits.product(chars).select{ |d, ch| d >= 2 && ch == 'a' }.map(&:join)

Это всего лишь 4 символа длиной дольше, чем понимание списка и так же выразительно (ИМХО, конечно, но поскольку понимание списков - это просто специальное приложение монады списка, можно утверждать, что, возможно, можно адекватно перестроить это использование Ruby), не требуя особого синтаксиса.

Ответ 2

Как вы знаете, у Ruby нет синтаксического сахара для восприятия списков, поэтому чем ближе вы можете получить, тем они будут использовать блоки в воображаемом образе. Люди предложили разные идеи, посмотрите lazylist и verstehen, поддерживают поддержку вложенных понятий с условиями:

require 'lazylist'
list { [x, y] }.where(:x => [1, 2], :y => [3, 4]) { x+y>4 }.to_a
#=> [[1, 4], [2, 3], [2, 4]]

require 'verstehen'
list { [x, y] }.for(:x).in { [1, 2] }.for(:y).in { [3, 4] }.if { x+y>4 }.comprehend
#=> [[1, 4], [2, 3], [2, 4]]

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

Ответ 3

Как было предложено RBK выше, List comprehension в Ruby предоставляет целый ряд различных способов сделать что-то вроде подобных спискам в Ruby.

Ни один из них явно не описывает вложенные циклы, но по крайней мере некоторые из них могут быть вложены довольно легко.

Например, принятый ответ Роберта Гэмбла предлагает добавить метод Array # comprehend.

class Array
  def comprehend(&block)
    return self if block.nil?
    self.collect(&block).compact
  end
end

Сделав это, вы можете написать свой код как:

digits.comprehend{|d| chars.comprehend{|ch| d.to_s+ch if ch =='a'} if d>=2}

Сравните с кодом Python:

[str(d)+ch for d in digits for ch in chars if d >= 2 if ch == 'a']

Различия довольно незначительные:

  • Код Ruby немного длиннее. Но это главным образом тот факт, что "постижение" изложено; вы всегда можете назвать его короче, если хотите.
  • Код Ruby помещает вещи в другом порядке - массивы появляются в начале, а не в середине. Но если вы думаете об этом, то, что вы ожидаете и хотите, из-за философии "все является методом".
  • Для Ruby-кода требуются вложенные фигурные скобки для вложенных понятий. Я не могу придумать очевидный путь вокруг этого, который не ухудшает ситуацию (вы не хотите называть "[str, цифры].comprehend2" или что-то еще...).

Конечно, настоящая сила Python заключается в том, что если вы решите, что хотите оценить список лениво, вы можете преобразовать свое понимание в выражение генератора, просто удалив скобки (или превратив их в круглые скобки, в зависимости от контекста), Но даже там вы можете создать массив # lazycomprehend или что-то еще.