Untuk melakukan yang setara dengan pemahaman daftar Python, saya melakukan yang berikut:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
Apakah ada cara yang lebih baik untuk melakukan ini ... mungkin dengan satu pemanggilan metode?
Untuk melakukan yang setara dengan pemahaman daftar Python, saya melakukan yang berikut:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
Apakah ada cara yang lebih baik untuk melakukan ini ... mungkin dengan satu pemanggilan metode?
Jawaban:
Jika Anda benar-benar ingin, Anda dapat membuat metode Array #rehend seperti ini:
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
Cetakan:
6
12
18
Saya mungkin hanya akan melakukannya seperti yang Anda lakukan.
[nil, nil, nil].comprehend {|x| x }
yang kembali []
.
compact!
mengembalikan nol daripada array ketika tidak ada item yang diubah, jadi saya rasa itu tidak berhasil.
Bagaimana jika:
some_array.map {|x| x % 2 == 0 ? x * 3 : nil}.compact
Sedikit lebih bersih, setidaknya menurut selera saya, dan menurut uji benchmark cepat sekitar 15% lebih cepat dari versi Anda ...
some_array.map{|x| x * 3 unless x % 2}.compact
, yang bisa dibilang lebih mudah dibaca / ruby-esque.
unless x%2
tidak berpengaruh karena 0 benar dalam ruby. Lihat: gist.github.com/jfarmer/2647362
Saya membuat patokan cepat yang membandingkan ketiga alternatif dan map-compact tampaknya menjadi pilihan terbaik.
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
di benchmark ini juga (lihat stackoverflow.com/a/17703276 ).
inject
==reduce
Sepertinya ada beberapa kebingungan di antara programmer Ruby di thread ini tentang pengertian list. Setiap respons tunggal mengasumsikan beberapa larik yang sudah ada sebelumnya untuk diubah. Tetapi kekuatan pemahaman daftar terletak pada larik yang dibuat dengan cepat dengan sintaks berikut:
squares = [x**2 for x in range(10)]
Berikut ini adalah analog di Ruby (satu-satunya jawaban yang memadai di utas ini, AFAIC):
a = Array.new(4).map{rand(2**49..2**50)}
Dalam kasus di atas, saya membuat array bilangan bulat acak, tetapi blok itu bisa berisi apa saja. Tapi ini akan menjadi pemahaman daftar Ruby.
Saya membahas topik ini dengan Rein Henrichs, yang memberi tahu saya bahwa solusi dengan kinerja terbaik adalah
map { ... }.compact
Ini masuk akal karena menghindari pembuatan Array perantara seperti penggunaan yang tidak dapat diubah dari Enumerable#inject
, dan menghindari menumbuhkan Array, yang menyebabkan alokasi. Ini umum seperti yang lainnya kecuali koleksi Anda dapat berisi elemen nihil.
Saya belum membandingkan ini dengan
select {...}.map{...}
Ada kemungkinan implementasi Ruby C Enumerable#select
juga sangat bagus.
Solusi alternatif yang akan bekerja di setiap implementasi dan berjalan dalam waktu O (n) bukan O (2n) adalah:
some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
2
sesuatu n
kali daripada waktu 1
benda n
dan kemudian 1
hal lain n
kali :) Satu keuntungan penting dari inject
/ reduce
adalah mempertahankan nil
nilai apa pun dalam urutan masukan yang merupakan perilaku yang lebih memahami daftar
Saya baru saja menerbitkan permata pemahaman ke RubyGems, yang memungkinkan Anda melakukan ini:
require 'comprehend'
some_array.comprehend{ |x| x * 3 if x % 2 == 0 }
Itu ditulis dalam C; larik hanya dilintasi sekali.
Enumerable memiliki grep
metode yang argumen pertamanya bisa menjadi proc predikat, dan argumen kedua opsionalnya adalah fungsi pemetaan; jadi berikut ini bekerja:
some_array.grep(proc {|x| x % 2 == 0}) {|x| x*3}
Ini tidak terbaca seperti beberapa saran lain (saya suka permata sederhana select.map
atau histokrat anoiaque), tetapi kekuatannya adalah bahwa itu sudah menjadi bagian dari pustaka standar, dan single-pass dan tidak melibatkan pembuatan array perantara sementara , dan tidak memerlukan nilai di luar batas seperti yang nil
digunakan dalam compact
saran -menggunakan.
Ini lebih singkat:
[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]
Itu berhasil untuk saya. Itu juga bersih. Ya, itu sama dengan map
, tapi menurut saya collect
membuat kodenya lebih mudah dimengerti.
select(&:even?).map()
sebenarnya terlihat lebih baik, setelah melihatnya di bawah.
Seperti yang disebutkan Pedro, Anda dapat menggabungkan panggilan berantai ke Enumerable#select
dan Enumerable#map
, menghindari traversal pada elemen yang dipilih. Ini benar karena Enumerable#select
merupakan spesialisasi dari lipatan atau inject
. Saya memposting perkenalan yang tergesa - gesa ke topik di subreddit Ruby.
Penggabungan transformasi Array secara manual bisa membosankan, jadi mungkin seseorang bisa bermain-main dengan comprehend
implementasi Robert Gamble untuk membuat pola select
/ ini map
lebih cantik.
Sesuatu seperti ini:
def lazy(collection, &blk)
collection.map{|x| blk.call(x)}.compact
end
Sebut saja:
lazy (1..6){|x| x * 3 if x.even?}
Yang mengembalikan:
=> [6, 12, 18]
lazy
di Array dan kemudian:(1..6).lazy{|x|x*3 if x.even?}
Ini adalah salah satu cara untuk mendekati ini:
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
jadi pada dasarnya kita mengubah string menjadi sintaks ruby yang tepat untuk loop maka kita dapat menggunakan sintaks python dalam string untuk melakukan:
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]
atau jika Anda tidak menyukai tampilan string atau harus menggunakan lambda, kami dapat mengabaikan upaya untuk mencerminkan sintaks python dan melakukan sesuatu seperti ini:
S = [for x in 0...9 do $* << x*2 if x.even? end, $*][1]
# [0, 4, 8, 12, 16]
Ruby 2.7 diperkenalkan filter_map
yang cukup banyak mencapai apa yang Anda inginkan (peta + ringkas):
some_array.filter_map { |x| x * 3 if x % 2 == 0 }
Anda dapat membaca lebih lanjut di sini .
https://rubygems.org/gems/ruby_list_comprehension
steker tak tahu malu untuk permata Pemahaman Daftar Ruby saya untuk memungkinkan pemahaman daftar Ruby idiomatik
$l[for x in 1..10 do x + 2 end] #=> [3, 4, 5 ...]
Saya pikir pemahaman daftar paling-esque adalah sebagai berikut:
some_array.select{ |x| x * 3 if x % 2 == 0 }
Karena Ruby memungkinkan kita untuk menempatkan kondisional setelah ekspresi, kita mendapatkan sintaks yang mirip dengan versi Python dari daftar pemahaman. Juga, karena select
metode tidak menyertakan apa pun yang sama dengan false
, semua nilai nihil dihapus dari daftar yang dihasilkan dan tidak diperlukan panggilan untuk memadatkan seperti yang akan terjadi jika kita telah menggunakan map
atau collect
sebagai gantinya.