Чтобы сделать эквивалент понимания списков Python, я делаю следующее:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
Есть ли лучший способ сделать это ... возможно, с помощью одного вызова метода?
ruby
list-comprehension
Только чтение
источник
источник
Ответы:
Если вы действительно хотите, вы можете создать метод Array # comprehend следующим образом:
class Array def comprehend(&block) return self if block.nil? self.collect(&block).compact end end some_array = [1, 2, 3, 4, 5, 6] new_array = some_array.comprehend {|x| x * 3 if x % 2 == 0} puts new_array
Печать:
6 12 18
Я бы, наверное, просто поступил так же, как ты.
источник
[nil, nil, nil].comprehend {|x| x }
который возвращается[]
.compact!
возвращает nil вместо массива, когда никакие элементы не меняются, поэтому я не думаю, что это работает.Как насчет:
some_array.map {|x| x % 2 == 0 ? x * 3 : nil}.compact
Немного чище, по крайней мере, на мой вкус, и, согласно быстрому тесту, примерно на 15% быстрее, чем ваша версия ...
источник
some_array.map{|x| x * 3 unless x % 2}.compact
, который, возможно, более читаемый / рубиновый.unless x%2
не действует, так как 0 в рубине является истинным. См .: gist.github.com/jfarmer/2647362Я провел быстрый тест, сравнивая три альтернативы, и map-compact действительно кажется лучшим вариантом.
Тест производительности (Rails)
require 'test_helper' require 'performance_test_help' class ListComprehensionTest < ActionController::PerformanceTest TEST_ARRAY = (1..100).to_a def test_map_compact 1000.times do TEST_ARRAY.map{|x| x % 2 == 0 ? x * 3 : nil}.compact end end def test_select_map 1000.times do TEST_ARRAY.select{|x| x % 2 == 0 }.map{|x| x * 3} end end def test_inject 1000.times do TEST_ARRAY.inject([]) {|all, x| all << x*3 if x % 2 == 0; all } end end end
Полученные результаты
/usr/bin/ruby1.8 -I"lib:test" "/usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb" "test/performance/list_comprehension_test.rb" -- --benchmark Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader Started ListComprehensionTest#test_inject (1230 ms warmup) wall_time: 1221 ms memory: 0.00 KB objects: 0 gc_runs: 0 gc_time: 0 ms .ListComprehensionTest#test_map_compact (860 ms warmup) wall_time: 855 ms memory: 0.00 KB objects: 0 gc_runs: 0 gc_time: 0 ms .ListComprehensionTest#test_select_map (961 ms warmup) wall_time: 955 ms memory: 0.00 KB objects: 0 gc_runs: 0 gc_time: 0 ms . Finished in 66.683039 seconds. 15 tests, 0 assertions, 0 failures, 0 errors
источник
reduce
в этом тесте (см. Stackoverflow.com/a/17703276 ).inject
==reduce
Похоже, что в этой ветке Ruby-программисты не понимают, что такое понимание списков. Каждый ответ предполагает преобразование некоторого уже существующего массива. Но сила понимания списка заключается в массиве, созданном на лету со следующим синтаксисом:
squares = [x**2 for x in range(10)]
Следующее будет аналогом в Ruby (единственный адекватный ответ в этой ветке AFAIC):
a = Array.new(4).map{rand(2**49..2**50)}
В приведенном выше случае я создаю массив случайных целых чисел, но блок может содержать что угодно. Но это было бы понимание списка Ruby.
источник
Я обсуждал эту тему с Рейном Хенрихсом, который сказал мне, что наиболее эффективным решением является
Это имеет смысл, поскольку позволяет избежать создания промежуточных массивов, как при неизменном использовании
Enumerable#inject
, и избежать роста массива, который вызывает выделение. Он такой же общий, как и любые другие, если ваша коллекция не может содержать нулевых элементов.Я не сравнивал это с
Возможно, реализация языка C в Ruby
Enumerable#select
тоже очень хороша.источник
Альтернативное решение, которое будет работать в каждой реализации и работать за O (n) вместо O (2n) времени:
some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
источник
2
вещи ,n
раз вместо того , чтобы1
вещьn
раз , а потом еще1
вещьn
раз :) Одним из важных преимуществinject
/ вreduce
том , что он сохраняет всеnil
значения в последовательности ввода , которая является более список-comprehensionly поведениеЯ только что опубликовал в RubyGems гем comprehend , который позволяет вам делать это:
require 'comprehend' some_array.comprehend{ |x| x * 3 if x % 2 == 0 }
Написано на C; массив проходит только один раз.
источник
Enumerable имеет
grep
метод, первый аргумент которого может быть процедурой предиката, а второй необязательный аргумент - функцией сопоставления; так что работает следующее:some_array.grep(proc {|x| x % 2 == 0}) {|x| x*3}
Это не так удобно для чтения, как пара других предложений (мне нравится gem от anoiaque simple
select.map
или histocrat), но его сильные стороны в том, что он уже входит в стандартную библиотеку, является однопроходным и не требует создания временных промежуточных массивов. , и не требует значения, выходящего за пределы, какnil
вcompact
предложениях -using.источник
Это более кратко:
[1,2,3,4,5,6].select(&:even?).map{|x| x*3}
источник
[1,2,3,4,5,6].select(&:even?).map(&3.method(:*))
[1, 2, 3, 4, 5, 6].collect{|x| x * 3 if x % 2 == 0}.compact => [6, 12, 18]
Это подходит для меня. Это тоже чисто. Да, то же самое
map
, но, думаю,collect
делает код более понятным.select(&:even?).map()
на самом деле выглядит лучше, увидев это ниже.
источник
Как упоминал Педро, вы можете объединить связанные вызовы с
Enumerable#select
иEnumerable#map
, избегая обхода выбранных элементов. Это правда, потому чтоEnumerable#select
это специализация fold orinject
. Я разместил поспешное введение в эту тему в сабреддите Ruby.Слияние преобразований Array вручную может быть утомительным, поэтому, возможно, кто-то сможет поиграть с реализацией Роберта Гэмбла,
comprehend
чтобы сделать этотselect
/map
шаблон красивее.источник
Что-то вроде этого:
def lazy(collection, &blk) collection.map{|x| blk.call(x)}.compact end
Назови это:
lazy (1..6){|x| x * 3 if x.even?}
Что возвращает:
=> [6, 12, 18]
источник
lazy
в Array, а затем:(1..6).lazy{|x|x*3 if x.even?}
Другое решение, но, возможно, не лучшее
some_array.flat_map {|x| x % 2 == 0 ? [x * 3] : [] }
или
some_array.each_with_object([]) {|x, list| x % 2 == 0 ? list.push(x * 3) : nil }
источник
Это один из способов приблизиться к этому:
c = -> x do $*.clear if x['if'] && x[0] != 'f' . y = x[0...x.index('for')] x = x[x.index('for')..-1] (x.insert(x.index(x.split[3]) + x.split[3].length, " do $* << #{y}") x.insert(x.length, "end; $*") eval(x) $*) elsif x['if'] && x[0] == 'f' (x.insert(x.index(x.split[3]) + x.split[3].length, " do $* << x") x.insert(x.length, "end; $*") eval(x) $*) elsif !x['if'] && x[0] != 'f' y = x[0...x.index('for')] x = x[x.index('for')..-1] (x.insert(x.index(x.split[3]) + x.split[3].length, " do $* << #{y}") x.insert(x.length, "end; $*") eval(x) $*) else eval(x.split[3]).to_a end end
так что в основном мы преобразуем строку в правильный синтаксис ruby для цикла, тогда мы можем использовать синтаксис python в строке, чтобы сделать:
c['for x in 1..10'] c['for x in 1..10 if x.even?'] c['x**2 for x in 1..10 if x.even?'] c['x**2 for x in 1..10'] # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # [2, 4, 6, 8, 10] # [4, 16, 36, 64, 100] # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
или если вам не нравится внешний вид строки или необходимость использования лямбда-выражения, мы могли бы отказаться от попытки зеркального отражения синтаксиса Python и сделать что-то вроде этого:
S = [for x in 0...9 do $* << x*2 if x.even? end, $*][1] # [0, 4, 8, 12, 16]
источник
Представлен Ruby 2.7,
filter_map
который в значительной степени обеспечивает то, что вы хотите (карта + компактность):some_array.filter_map { |x| x * 3 if x % 2 == 0 }
Вы можете прочитать об этом здесь .
источник
https://rubygems.org/gems/ruby_list_comprehension
бесстыдный плагин для моего гема понимания списка Ruby, чтобы позволить идиоматическое понимание списка Ruby
$l[for x in 1..10 do x + 2 end] #=> [3, 4, 5 ...]
источник
Я думаю, что наиболее подходящим для понимания списком будет следующий:
some_array.select{ |x| x * 3 if x % 2 == 0 }
Поскольку Ruby позволяет нам помещать условное выражение после выражения, мы получаем синтаксис, аналогичный версии Python для понимания списка. Кроме того, поскольку
select
метод не включает ничего, что приравнивается кfalse
, все значения nil удаляются из результирующего списка, и не требуется вызова compact, как в случае, если бы мы использовалиmap
илиcollect
вместо него.источник