Saya memiliki array Ruby yang berisi beberapa nilai string. Aku ingin:
- Temukan semua elemen yang cocok dengan beberapa predikat
- Jalankan elemen yang cocok melalui transformasi
- Kembalikan hasilnya sebagai larik
Sekarang solusi saya terlihat seperti ini:
def example
matchingLines = @lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
Apakah ada metode Array atau Enumerable yang menggabungkan select dan map menjadi satu pernyataan logis?
Enumerable#grep
Metode tidak persis apa yang diminta dan telah di Ruby selama lebih dari sepuluh tahun. Dibutuhkan argumen predikat dan blok transformasi. @hirolau memberikan satu-satunya jawaban yang benar untuk pertanyaan ini.filter_map
untuk tujuan yang tepat ini. Info selengkapnya di sini .Jawaban:
Saya biasanya menggunakan
map
dancompact
bersama dengan kriteria pemilihan saya sebagai postfixif
.compact
menghilangkan nil.sumber
map
+compact
benar-benar akan berkinerja lebih baik daripadainject
dan memposting hasil benchmark saya ke utas terkait: stackoverflow.com/questions/310426/list-comprehension-in-ruby/…map
danselect
, hanya saja itucompact
adalah kasus khususreject
yang bekerja pada nils dan berkinerja agak lebih baik karena telah diterapkan langsung di C.Anda dapat menggunakan
reduce
untuk ini, yang hanya membutuhkan satu izin:Dengan kata lain, inisialisasi status menjadi apa yang Anda inginkan (dalam kasus kami, daftar kosong untuk diisi:
[]
, lalu selalu pastikan untuk mengembalikan nilai ini dengan modifikasi untuk setiap elemen dalam daftar asli (dalam kasus kami, elemen yang dimodifikasi didorong ke daftar).Ini adalah yang paling efisien karena hanya mengulang daftar dengan satu pass (
map
+select
ataucompact
membutuhkan dua pass).Dalam kasus Anda:
sumber
each_with_object
sedikit lebih masuk akal? Anda tidak perlu mengembalikan array di akhir setiap iterasi blok. Anda bisa melakukannyamy_array.each_with_object([]) { |i, a| a << i if i.condition }
.reduce
pertama 😊Ruby 2.7+
Ada sekarang!
Ruby 2.7 memperkenalkan
filter_map
untuk tujuan yang tepat ini. Ini idiomatis dan bagus, dan saya berharap itu menjadi norma segera.Sebagai contoh:
Berikut bacaan yang bagus tentang masalah ini .
Semoga bermanfaat bagi seseorang!
sumber
filter
,select
danfind_all
adalah sama, sepertimap
dancollect
yang, mungkin akan sulit untuk mengingat nama metode ini. Apakah itufilter_map
,select_collect
,find_all_map
ataufilter_collect
?Cara lain yang berbeda untuk mendekati ini adalah menggunakan yang baru (relatif terhadap pertanyaan ini)
Enumerator::Lazy
:The
.lazy
Metode mengembalikan enumerator malas. Memanggil.select
atau.map
pada pencacah malas mengembalikan pencacah malas lainnya. Hanya sekali Anda menelepon.uniq
apakah itu benar-benar memaksa pencacah dan mengembalikan array. Jadi yang terjadi secara efektif adalah panggilan Anda.select
dan.map
digabungkan menjadi satu - Anda hanya mengulangi@lines
sekali untuk melakukan keduanya.select
dan.map
.Naluri saya adalah bahwa
reduce
metode Adam akan sedikit lebih cepat, tetapi saya pikir ini jauh lebih mudah dibaca.Konsekuensi utama dari hal ini adalah tidak ada objek larik perantara yang dibuat untuk setiap panggilan metode berikutnya. Dalam
@lines.select.map
situasi normal ,select
mengembalikan larik yang kemudian dimodifikasi olehmap
, kembali mengembalikan larik. Sebagai perbandingan, evaluasi malas hanya membuat larik satu kali. Ini berguna ketika objek koleksi awal Anda berukuran besar. Ini juga memberdayakan Anda untuk bekerja dengan enumerator tak terbatas - misrandom_number_generator.lazy.select(&:odd?).take(10)
.sumber
reduce
sebagai "lakukan segalanya" transformasi selalu terasa cukup berantakan bagi saya.@lines
sekali untuk melakukan keduanya.select
dan.map
". Menggunakan.lazy
tidak berarti operasi operasi yang dirantai pada pencacah yang malas akan "diciutkan" menjadi satu iterasi tunggal. Ini adalah kesalahpahaman umum dari evaluasi malas operasi rantai wrt atas sebuah koleksi. (Anda dapat mengujinya dengan menambahkanputs
pernyataan di awal blokselect
danmap
pada contoh pertama. Anda akan menemukan bahwa mereka mencetak jumlah baris yang sama).lazy
cetakannya akan dicetak dalam jumlah yang sama. Itulah maksud saya —map
blok Anda dan blok Andaselect
dieksekusi dengan jumlah yang sama di versi lazy dan eager. Versi malas tidak "menggabungkan panggilan.select
dan Anda.map
"lazy
menggabungkan mereka karena elemen yang gagalselect
kondisi tidak diteruskan kemap
. Dengan kata lain: prependinglazy
kira-kira sama dengan menggantiselect
danmap
dengan singlereduce([])
, dan "secara cerdas" membuatselect
blok sebagai prasyarat untuk dimasukkan ke dalamreduce
hasil.Jika Anda memiliki
select
yang dapat menggunakancase
operator (===
),grep
adalah alternatif yang baik:Jika kita membutuhkan logika yang lebih kompleks, kita dapat membuat lambda:
sumber
Saya tidak yakin ada satu. The modul Enumerable , yang menambahkan
select
danmap
, tidak menunjukkan satu.Anda akan diminta untuk mengirimkan dua blok ke
select_and_transform
metode ini, yang akan menjadi IMHO sedikit tidak intuitif.Jelas, Anda bisa merangkainya, yang lebih mudah dibaca:
sumber
Jawaban Sederhana:
Jika Anda memiliki n catatan, dan Anda ingin
select
danmap
berdasarkan kondisi makaDi sini, atribut adalah apa pun yang Anda inginkan dari catatan dan kondisi, Anda dapat mencentangnya.
Compact adalah untuk membilas nil yang tidak perlu yang keluar dari kondisi tersebut
sumber
Tidak, tapi Anda bisa melakukannya seperti ini:
Atau bahkan lebih baik:
sumber
reject(&:nil?)
pada dasarnya sama dengancompact
.Saya pikir cara ini lebih mudah dibaca, karena membagi kondisi filter dan nilai yang dipetakan sambil tetap jelas bahwa tindakannya terhubung:
Dan, dalam kasus spesifik Anda, hilangkan
result
variabel semuanya:sumber
Di Ruby 1.9 dan 1.8.7, Anda juga dapat merangkai dan membungkus iterator hanya dengan tidak memberikan satu blok kepada mereka:
Tapi itu tidak benar-benar mungkin dalam kasus ini, karena tipe blok mengembalikan nilai
select
danmap
tidak cocok. Lebih masuk akal untuk sesuatu seperti ini:AFAICS, hal terbaik yang dapat Anda lakukan adalah contoh pertama.
Inilah contoh kecilnya:
Tapi yang benar - benar Anda inginkan adalah
["A", "B", "C", "D"]
.sumber
select
mengembalikan nilai Boolean-ish yang memutuskan apakah akan mempertahankan elemen atau tidak,map
mengembalikan nilai yang diubah. Nilai yang diubah itu sendiri mungkin akan menjadi benar, jadi semua elemen dipilih.Anda harus mencoba menggunakan perpustakaan saya Rearmed Ruby yang telah saya tambahkan metodenya
Enumerable#select_map
. Berikut contohnya:sumber
select_map
di perpustakaan ini hanya menerapkanselect { |i| ... }.map { |i| ... }
strategi yang sama dari banyak jawaban di atas.Jika Anda tidak ingin membuat dua larik yang berbeda, Anda dapat menggunakan
compact!
tetapi berhati-hatilah.Menariknya,
compact!
melakukan penghapusan di tempat nihil. Nilai kembalian daricompact!
array yang sama jika ada perubahan tetapi nihil jika tidak ada nil.Akan menjadi satu kapal.
sumber
Versi Anda:
Versi saya:
Ini akan melakukan 1 iterasi (kecuali pengurutan), dan memiliki bonus tambahan untuk menjaga keunikan (jika Anda tidak peduli tentang unik, maka buat saja hasilnya menjadi array dan
results.push(line) if ...
sumber
Berikut ini contohnya. Ini tidak sama dengan masalah Anda, tetapi mungkin apa yang Anda inginkan, atau dapat memberi petunjuk untuk solusi Anda:
sumber