Ruby: Cara termudah untuk Menyaring Kunci Hash?

225

Saya memiliki hash yang terlihat seperti ini:

params = { :irrelevant => "A String",
           :choice1 => "Oh look, another one",
           :choice2 => "Even more strings",
           :choice3 => "But wait",
           :irrelevant2 => "The last string" }

Dan saya ingin cara sederhana untuk menolak semua kunci yang bukan pilihan + int. Itu bisa berupa choice1, atau choice1 hingga choice10. Bervariasi.

Bagaimana cara memilih kunci hanya dengan pilihan kata dan digit atau digit setelahnya?

Bonus:

Ubah hash menjadi string dengan tab (\ t) sebagai pembatas. Saya melakukan ini, tetapi butuh beberapa baris kode. Biasanya master Rubician dapat melakukannya dalam satu atau lebih baris.

Derek
sumber
Dalam hal pertanyaan bonus, dapatkah Anda menjelaskan seperti apa bentuk string yang Anda inginkan.
mikej
Tentu, contoh di atas hash akan menghasilkan: "Oh, lihat, satu lagi \ tBahkan lebih banyak string \ tTapi tunggu" (dengan tidak \ t pada akhir string, hanya di antara mereka)
Derek
String contoh Anda telah terpotong di komentar. Anda dapat menggunakan tautan edit untuk mengedit pertanyaan Anda dan menambahkan contoh di sana. Anda juga dapat memposting ruby ​​yang telah Anda buat sejauh sebagai contoh dari apa yang ingin Anda capai.
mikej
2
kemungkinan rangkap dari Filter Ruby Hash
jbochi

Jawaban:

281

Edit ke jawaban asli : Meskipun ini adalah jawaban (pada saat komentar ini) adalah jawaban yang dipilih, versi asli dari jawaban ini sudah usang.

Saya menambahkan pembaruan di sini untuk membantu orang lain menghindari teralihkan oleh jawaban ini seperti yang saya lakukan.

Seperti jawaban lain yang disebutkan, Ruby> = 2.5 menambahkan Hash#slicemetode yang sebelumnya hanya tersedia di Rails.

Contoh:

> { one: 1, two: 2, three: 3 }.slice(:one, :two)
=> {:one=>1, :two=>2}

Akhir pengeditan. Berikut ini adalah jawaban asli yang saya kira akan berguna jika Anda menggunakan Ruby <2,5 tanpa Rails, meskipun saya membayangkan kasus ini sangat jarang pada saat ini.


Jika Anda menggunakan Ruby, Anda dapat menggunakan selectmetode ini. Anda harus mengonversi kunci dari Simbol ke String untuk melakukan kecocokan regexp. Ini akan memberi Anda Hash baru dengan hanya pilihan di dalamnya.

choices = params.select { |key, value| key.to_s.match(/^choice\d+/) }

atau Anda dapat menggunakan delete_ifdan memodifikasi Hash misalnya

params.delete_if { |key, value| !key.to_s.match(/choice\d+/) }

atau jika itu hanya kunci dan bukan nilai yang Anda inginkan maka Anda dapat melakukannya:

params.keys.select { |key| key.to_s.match(/^choice\d+/) }

dan ini akan memberikan hanya Array kunci misalnya [:choice1, :choice2, :choice3]

mikej
sumber
1
Luar biasa! Begitu saya melihatnya, ini sangat sederhana! Terima kasih banyak, dan terima kasih kepada yang lain yang telah meluangkan waktu untuk menjawab juga.
Derek
2
Simbol dapat diurai dengan ekspresi reguler saat ini IIRC.
Andrew Grimm
@Andrew, saya pikir Anda benar. Saya berada di 1.8.7 ketika saya menguji jawaban untuk pertanyaan ini.
mikej
1
Yang menjadi lawan .selectadalah .reject, jika itu membuat kode Anda lebih idiomatis.
Joe Atzberger
The #slicecara bekerja untuk saya pada 2.5.3 bahkan tanpa ActiveSupport.
graywolf
305

Di Ruby, pilih Hash # adalah opsi yang tepat. Jika Anda bekerja dengan Rails, Anda dapat menggunakan Hash # slice dan Hash # slice !. mis. (rel 3.2.13)

h1 = {:a => 1, :b => 2, :c => 3, :d => 4}

h1.slice(:a, :b)         # return {:a=>1, :b=>2}, but h1 is not changed

h2 = h1.slice!(:a, :b)   # h1 = {:a=>1, :b=>2}, h2 = {:c => 3, :d => 4}
lfx_cool
sumber
12
hati-hati dengan kunci yang diketikkan..h1 = {'a' => 1,: b => 2} hanya akan mengembalikan {: b => 2} dengan h1.slice (: a
,:
Solusi yang luar biasa. Saya menggunakan ini dalam mengembalikan hasil dalam rspec, di mana saya ingin menguraikan nilai hash dalam hash. `` `test = {: a => 1,: b => 2, c => {: A => 1,: B => 2}} solusi ==> test.c.slice (: B)` ` `
PriyankaK
Ganti Rails oleh ActiveSupport dan ini sempurna;)
Geoffroy
5
ini tidak menjawab pertanyaan, karena dia ingin cara untuk mengekstrak nilai-nilai untuk kunci yang "choice + int". Solusi Anda hanya dapat mengekstrak kunci yang sudah diketahui sebelumnya.
metakungfu
46

Cara termudah adalah memasukkan gem 'activesupport'(atau gem 'active_support').

Kemudian, di kelas Anda, Anda hanya perlu melakukannya

require 'active_support/core_ext/hash/slice'

dan untuk menelepon

params.slice(:choice1, :choice2, :choice3) # => {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}

Saya percaya itu tidak layak untuk mendeklarasikan fungsi lain yang mungkin memiliki bug, dan lebih baik menggunakan metode yang telah di-tweak selama beberapa tahun terakhir.

Nuno Costa
sumber
Activerecord tidak diperlukan untuk ini setidaknya pada 2.5.
graywolf
13

Cara termudah adalah memasukkan gem 'activesupport' (atau gem 'active_support').

params.slice(:choice1, :choice2, :choice3)

翟英昌
sumber
4
Silakan tambahkan rincian lebih lanjut untuk jawaban Anda.
Mohit Jain
13

Jika Anda bekerja dengan rel dan memiliki kunci di daftar terpisah, Anda dapat menggunakan *notasi:

keys = [:foo, :bar]
hash1 = {foo: 1, bar:2, baz: 3}
hash2 = hash1.slice(*keys)
=> {foo: 1, bar:2}

Seperti jawaban lain yang dinyatakan, Anda juga dapat menggunakan slice!untuk memodifikasi hash di tempat (dan mengembalikan kunci / nilai yang terhapus).

Robert
sumber
9

Ini adalah satu baris untuk menyelesaikan pertanyaan asli lengkap:

params.select { |k,_| k[/choice/]}.values.join('\t')

Tetapi sebagian besar solusi di atas adalah memecahkan kasus di mana Anda perlu mengetahui kunci sebelumnya, menggunakan sliceatau regexp sederhana.

Berikut adalah pendekatan lain yang berfungsi untuk kasus penggunaan yang sederhana dan lebih kompleks, yang dapat ditukar saat runtime

data = {}
matcher = ->(key,value) { COMPLEX LOGIC HERE }
data.select(&matcher)

Sekarang tidak hanya ini memungkinkan untuk logika yang lebih kompleks pada pencocokan kunci atau nilai, tetapi juga lebih mudah untuk menguji, dan Anda dapat menukar logika pencocokan saat runtime.

Mantan untuk memecahkan masalah asli:

def some_method(hash, matcher) 
  hash.select(&matcher).values.join('\t')
end

params = { :irrelevant => "A String",
           :choice1 => "Oh look, another one",
           :choice2 => "Even more strings",
           :choice3 => "But wait",
           :irrelevant2 => "The last string" }

some_method(params, ->(k,_) { k[/choice/]}) # => "Oh look, another one\\tEven more strings\\tBut wait"
some_method(params, ->(_,v) { v[/string/]}) # => "Even more strings\\tThe last string"
metakungfu
sumber
1
Saya sangat suka korek api
Calin
7

Dengan Hash::select:

params = params.select { |key, value| /^choice\d+$/.match(key.to_s) }
Arnaud Le Blanc
sumber
6

Jika Anda ingin hash yang tersisa:

params.delete_if {|k, v| ! k.match(/choice[0-9]+/)}

atau jika Anda hanya ingin kunci:

params.keys.delete_if {|k| ! k.match(/choice[0-9]+/)}
ayckoster
sumber
Bergantung pada berapa banyak choiceX dan irrelevantX yang Anda pilih ATAU delete_if mungkin lebih baik. Jika Anda memiliki lebih banyak choiceX delete_if lebih baik.
ayckoster
6

Masukkan ini ke inisialisasi

class Hash
  def filter(*args)
    return nil if args.try(:empty?)
    if args.size == 1
      args[0] = args[0].to_s if args[0].is_a?(Symbol)
      self.select {|key| key.to_s.match(args.first) }
    else
      self.select {|key| args.include?(key)}
    end
  end
end

Maka Anda bisa melakukannya

{a: "1", b: "b", c: "c", d: "d"}.filter(:a, :b) # =>  {a: "1", b: "b"}

atau

{a: "1", b: "b", c: "c", d: "d"}.filter(/^a/)  # =>  {a: "1"}
montrealmike
sumber
5
params.select{ |k,v| k =~ /choice\d/ }.map{ |k,v| v}.join("\t")
Puhlze
sumber
4

Adapun pertanyaan bonus:

  1. Jika Anda memiliki output dari #selectmetode seperti ini (daftar array 2-elemen):

    [[:choice1, "Oh look, another one"], [:choice2, "Even more strings"], [:choice3, "But wait"]]

    maka cukup ambil hasil ini dan jalankan:

    filtered_params.join("\t")
    # or if you want only values instead of pairs key-value
    filtered_params.map(&:last).join("\t")
  2. Jika Anda memiliki keluaran dari #delete_ifmetode seperti ini (hash):

    {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}

    kemudian:

    filtered_params.to_a.join("\t")
    # or
    filtered_params.values.join("\t")
MBO
sumber
Solusi hebat. Satu pertanyaan: Apakah filtered_params.map (&: last) .join ("\ t") adalah kependekan dari filtered_params.map (| i | i.last) .join ("\ t")? Jika demikian, apa yang 'terakhir'? bisakah saya menggunakan nilai &: untuk mendapatkan nilai hash?
Derek
mapDiblokir karena itu argumen. Anda dapat membuat blok dengan cepat, dengan &:methodkonstruksi, yang akan membuat blok {|v| v.method }. Dalam kasus saya &:lastdisebut argumen array (array 2-elemen). Jika Anda ingin bermain dengannya, pertama-tama periksa argumen apa yang Anda terima ke dalam blok (dapatkan 1 argumen, jangan dekonstruksi menjadi beberapa argumen!) Dan cobalah untuk menemukan 1 metode (Anda tidak dapat mengaitkan metode dengan &:method) yang akan mengembalikan apa yang Anda inginkan. ingin. Jika memungkinkan, maka Anda dapat menggunakan pintasan, jika tidak, maka Anda perlu blok penuh
MBO
2
params = { :irrelevant => "A String",
           :choice1 => "Oh look, another one",
           :choice2 => "Even more strings",
           :choice3 => "But wait",
           :irrelevant2 => "The last string" }

choices = params.select { |key, value| key.to_s[/^choice\d+/] }
#=> {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}
Arup Rakshit
sumber
0

Saya punya masalah yang sama, dalam kasus saya solusinya adalah liner satu yang bekerja bahkan jika kunci bukan simbol, tetapi Anda harus memiliki kunci kriteria dalam array

criteria_array = [:choice1, :choice2]

params.select { |k,v| criteria_array.include?(k) } #=> { :choice1 => "Oh look another one",
                                                         :choice2 => "Even more strings" }

Contoh lain

criteria_array = [1, 2, 3]

params = { 1 => "A String",
           17 => "Oh look, another one",
           25 => "Even more strings",
           49 => "But wait",
           105 => "The last string" }

params.select { |k,v| criteria_array.include?(k) } #=> { 1 => "A String"}
Jose Paez
sumber