Bagaimana cara menghapus kunci dari Hash dan mendapatkan sisa hash di Ruby / Rails?

560

Untuk menambahkan pasangan baru ke Hash saya lakukan:

{:a => 1, :b => 2}.merge!({:c => 3})   #=> {:a => 1, :b => 2, :c => 3}

Apakah ada cara serupa untuk menghapus kunci dari Hash?

Ini bekerja:

{:a => 1, :b => 2}.reject! { |k| k == :a }   #=> {:b => 2}

tapi saya berharap memiliki sesuatu seperti:

{:a => 1, :b => 2}.delete!(:a)   #=> {:b => 2}

Penting bahwa nilai yang dikembalikan akan menjadi hash yang tersisa, jadi saya bisa melakukan hal-hal seperti:

foo(my_hash.reject! { |k| k == my_key })

dalam satu baris.

Misha Moroshko
sumber
1
Anda selalu dapat memperluas (buka saat runtime) bawaan di Hash untuk menambahkan metode khusus ini jika Anda benar-benar membutuhkannya.
dbryson

Jawaban:

750

Rails memiliki tanda kecuali / kecuali! metode yang mengembalikan hash dengan kunci-kunci itu dihapus. Jika Anda sudah menggunakan Rails, tidak ada gunanya membuat versi Anda sendiri tentang ini.

class Hash
  # Returns a hash that includes everything but the given keys.
  #   hash = { a: true, b: false, c: nil}
  #   hash.except(:c) # => { a: true, b: false}
  #   hash # => { a: true, b: false, c: nil}
  #
  # This is useful for limiting a set of parameters to everything but a few known toggles:
  #   @person.update(params[:person].except(:admin))
  def except(*keys)
    dup.except!(*keys)
  end

  # Replaces the hash without the given keys.
  #   hash = { a: true, b: false, c: nil}
  #   hash.except!(:c) # => { a: true, b: false}
  #   hash # => { a: true, b: false }
  def except!(*keys)
    keys.each { |key| delete(key) }
    self
  end
end
Peter Brown
sumber
51
Anda tidak harus menggunakan tumpukan Rails penuh. Anda dapat menyertakan ActiveSupport dalam aplikasi Ruby apa pun.
Fryie
10
Untuk menambah jawaban Fryie, Anda bahkan tidak perlu memuat semua ActiveSupport; Anda bisa memasukkan merekarequire "active_support/core_ext/hash/except"
GMA
terlambat mengedit: Maksud saya "sertakan permata" tidak "sertakan"
GMA
@ GMA: ketika pengeditan lima menit Anda selesai, Anda selalu dapat menyalin, menghapus, memodifikasi, dan mengirim ulang komentar.
iconoclast
212

Oneliner ruby ​​biasa, hanya berfungsi dengan ruby> 1.9.x:

1.9.3p0 :002 > h = {:a => 1, :b => 2}
 => {:a=>1, :b=>2} 
1.9.3p0 :003 > h.tap { |hs| hs.delete(:a) }
 => {:b=>2} 

Metode ketuk selalu mengembalikan objek yang dipanggil ...

Kalau tidak, jika Anda diharuskan active_support/core_ext/hash(yang secara otomatis diperlukan di setiap aplikasi Rails), Anda dapat menggunakan salah satu metode berikut tergantung pada kebutuhan Anda:

  ~  irb
1.9.3p125 :001 > require 'active_support/core_ext/hash' => true 
1.9.3p125 :002 > h = {:a => 1, :b => 2, :c => 3}
 => {:a=>1, :b=>2, :c=>3} 
1.9.3p125 :003 > h.except(:a)
 => {:b=>2, :c=>3} 
1.9.3p125 :004 > h.slice(:a)
 => {:a=>1} 

kecuali menggunakan pendekatan daftar hitam, sehingga menghapus semua kunci yang terdaftar sebagai args, sedangkan slice menggunakan pendekatan daftar putih, sehingga menghapus semua kunci yang tidak terdaftar sebagai argumen. Ada juga versi bang dari metode tersebut ( except!dan slice!) yang memodifikasi hash yang diberikan tetapi nilai kembalinya berbeda keduanya mengembalikan hash. Ini mewakili kunci yang dihapus untuk slice!dan kunci yang disimpan untuk except!:

1.9.3p125 :011 > {:a => 1, :b => 2, :c => 3}.except!(:a)
 => {:b=>2, :c=>3} 
1.9.3p125 :012 > {:a => 1, :b => 2, :c => 3}.slice!(:a)
 => {:b=>2, :c=>3} 
Fabio
sumber
18
+1 Perlu disebutkan bahwa metode ini merusak h. Hash#excepttidak akan mengubah hash asli.
Terima kasih
3
Gunakan h.dup.tap { |hs| hs.delete(:a) }untuk menghindari memodifikasi hash asli.
Magicode
181

Mengapa tidak menggunakan saja:

hash.delete(key)
dbryson
sumber
2
@dbryson: Saya setuju bahwa terkadang itu tidak sepadan. Aku hanya ingin tahu mengapa ada merge, merge!, delete, tapi tidak ada detele!...
Misha Moroshko
1
jika Anda benar-benar membutuhkannya sebagai kapal satu lakukan:foo(hash.delete(key) || hash)
Bert Goethals
13
Akan lebih konsisten dengan konvensi Ruby jika deletetidak tidak mengubah parameter dan jika delete!ada dan melakukan memodifikasi parameter-nya.
David J.
60
Ini tidak mengembalikan hash yang tersisa seperti yang disebutkan dalam pertanyaan, itu akan mengembalikan nilai yang terkait dengan kunci yang dihapus.
MhdSyrwan
1
hapus mengembalikan kunci tetapi itu juga mengubah hash. Mengenai mengapa tidak ada delete !, tebakan saya adalah bahwa secara semantik tidak masuk akal untuk memanggil delete pada sesuatu dan tidak benar-benar menghapusnya. memanggil hash.delete () sebagai lawan dari hash.delete! () akan menjadi no-op.
pembuat telur
85

Ada banyak cara untuk menghapus kunci dari hash dan mendapatkan hash yang tersisa di Ruby.

  1. .slice=> Ini akan mengembalikan kunci yang dipilih dan tidak menghapusnya dari hash asli. Gunakan slice!jika Anda ingin menghapus kunci secara permanen menggunakan yang lain slice.

    2.2.2 :074 > hash = {"one"=>1, "two"=>2, "three"=>3}
     => {"one"=>1, "two"=>2, "three"=>3} 
    2.2.2 :075 > hash.slice("one","two")
     => {"one"=>1, "two"=>2} 
    2.2.2 :076 > hash
     => {"one"=>1, "two"=>2, "three"=>3} 
  2. .delete => Ini akan menghapus kunci yang dipilih dari hash asli (hanya dapat menerima satu kunci dan tidak lebih dari satu).

    2.2.2 :094 > hash = {"one"=>1, "two"=>2, "three"=>3}
     => {"one"=>1, "two"=>2, "three"=>3} 
    2.2.2 :095 > hash.delete("one")
     => 1 
    2.2.2 :096 > hash
     => {"two"=>2, "three"=>3} 
  3. .except=> Ini akan mengembalikan kunci yang tersisa tetapi tidak menghapus apa pun dari hash asli. Gunakan except!jika Anda ingin menghapus kunci secara permanen menggunakan yang lain except.

    2.2.2 :097 > hash = {"one"=>1, "two"=>2, "three"=>3}
     => {"one"=>1, "two"=>2, "three"=>3} 
    2.2.2 :098 > hash.except("one","two")
     => {"three"=>3} 
    2.2.2 :099 > hash
     => {"one"=>1, "two"=>2, "three"=>3}         
  4. .delete_if=> Jika Anda perlu menghapus kunci berdasarkan nilai. Ini jelas akan menghapus kunci yang cocok dari hash asli.

    2.2.2 :115 > hash = {"one"=>1, "two"=>2, "three"=>3, "one_again"=>1}
     => {"one"=>1, "two"=>2, "three"=>3, "one_again"=>1} 
    2.2.2 :116 > value = 1
     => 1 
    2.2.2 :117 > hash.delete_if { |k,v| v == value }
     => {"two"=>2, "three"=>3} 
    2.2.2 :118 > hash
     => {"two"=>2, "three"=>3} 
  5. .compact=> Digunakan untuk menghapus semua nilnilai dari hash. Gunakan compact!jika Anda ingin menghapus nilnilai - nilai secara permanen menggunakan yang lain compact.

    2.2.2 :119 > hash = {"one"=>1, "two"=>2, "three"=>3, "nothing"=>nil, "no_value"=>nil}
     => {"one"=>1, "two"=>2, "three"=>3, "nothing"=>nil, "no_value"=>nil} 
    2.2.2 :120 > hash.compact
     => {"one"=>1, "two"=>2, "three"=>3}

Hasil berdasarkan Ruby 2.2.2.

techdreams
sumber
16
slicedan exceptditambahkan dengan menggunakan ActiveSupport::CoreExtensions::Hash. Mereka bukan bagian dari inti Ruby. Mereka dapat digunakan olehrequire 'active_support/core_ext/hash'
Madis Nõmme
3
Karena Ruby 2.5 Hash#sliceada di perpustakaan standar. ruby-doc.org/core-2.5.0/Hash.html#method-i-slice Yay!
Madis Nõmme
38

Jika Anda ingin menggunakan Ruby murni (tanpa Rails), tidak ingin membuat metode ekstensi (mungkin Anda membutuhkan ini hanya di satu atau dua tempat dan tidak ingin mencemari namespace dengan banyak metode) dan tidak ingin sunting hash di tempat (yaitu, Anda penggemar pemrograman fungsional seperti saya), Anda dapat 'memilih':

>> x = {:a => 1, :b => 2, :c => 3}
=> {:a=>1, :b=>2, :c=>3}
>> x.select{|x| x != :a}
=> {:b=>2, :c=>3}
>> x.select{|x| ![:a, :b].include?(x)}
=> {:c=>3}
>> x
=> {:a=>1, :b=>2, :c=>3}
Yura Taras
sumber
30
#in lib/core_extensions.rb
class Hash
  #pass single or array of keys, which will be removed, returning the remaining hash
  def remove!(*keys)
    keys.each{|key| self.delete(key) }
    self
  end

  #non-destructive version
  def remove(*keys)
    self.dup.remove!(*keys)
  end
end

#in config/initializers/app_environment.rb (or anywhere in config/initializers)
require 'core_extensions'

Saya sudah mengatur ini sehingga .remove mengembalikan salinan hash dengan kunci dihapus, sambil menghapus! memodifikasi hash itu sendiri. Ini sesuai dengan konvensi batu delima. misal, dari konsol

>> hash = {:a => 1, :b => 2}
=> {:b=>2, :a=>1}
>> hash.remove(:a)
=> {:b=>2}
>> hash
=> {:b=>2, :a=>1}
>> hash.remove!(:a)
=> {:b=>2}
>> hash
=> {:b=>2}
>> hash.remove!(:a, :b)
=> {}
Max Williams
sumber
26

Anda dapat menggunakan except!dari facetspermata:

>> require 'facets' # or require 'facets/hash/except'
=> true
>> {:a => 1, :b => 2}.except(:a)
=> {:b=>2}

Hash asli tidak berubah.

EDIT: seperti yang dikatakan Russel, aspek memiliki beberapa masalah tersembunyi dan tidak sepenuhnya kompatibel dengan API dengan ActiveSupport. Di sisi lain ActiveSupport tidak selengkap segi. Pada akhirnya, saya akan menggunakan AS dan membiarkan kasus tepi dalam kode Anda.

ditulis ulang
sumber
Adil require 'facets/hash/except'dan tidak ada "masalah" (tidak yakin masalah apa yang akan terjadi, selain yang tidak 100% AS API). Jika Anda melakukan proyek Rails menggunakan AS masuk akal, jika tidak Facet memiliki jejak yang jauh lebih kecil.
trans
@trans ActiveSupport saat ini memiliki tapak yang cukup kecil juga, dan Anda hanya memerlukan sebagian saja. Sama seperti segi, tetapi dengan lebih banyak mata di atasnya (jadi saya kira itu mendapat ulasan yang lebih baik).
ditulis ulang
19

Alih-alih menambal monyet atau termasuk perpustakaan besar yang tidak perlu, Anda dapat menggunakan penyempurnaan jika Anda menggunakan Ruby 2 :

module HashExtensions
  refine Hash do
    def except!(*candidates)
      candidates.each { |candidate| delete(candidate) }
      self
    end

    def except(*candidates)
      dup.remove!(candidates)
    end
  end
end

Anda dapat menggunakan fitur ini tanpa mempengaruhi bagian lain dari program Anda, atau harus menyertakan perpustakaan eksternal yang besar.

class FabulousCode
  using HashExtensions

  def incredible_stuff
    delightful_hash.except(:not_fabulous_key)
  end
end
Muhammad
sumber
17

dalam Ruby murni:

{:a => 1, :b => 2}.tap{|x| x.delete(:a)}   # => {:b=>2}
gamov
sumber
13

Lihat Ruby on Rails: Hapus beberapa kunci hash

hash.delete_if{ |k,| keys_to_delete.include? k }
Nakilon
sumber
keys_to_delete.each {| k | hash.delete (k)} jauh lebih cepat untuk dataset besar. koreksi saya jika salah.
Vignesh Jayavel
@ VigneshJayavel, Anda benar tetapi OP ingin hash dikembalikan. eachakan mengembalikan array.
Nakilon
3

Sangat bagus jika delete mengembalikan pasangan delete dari hash. Saya sedang melakukan ini:

hash = {a: 1, b: 2, c: 3}
{b: hash.delete(:b)} # => {:b=>2}
hash  # => {:a=>1, :c=>3} 
frenesim
sumber
1

Ini adalah cara satu baris untuk melakukannya, tetapi tidak terlalu mudah dibaca. Sebaiknya gunakan dua baris.

use_remaining_hash_for_something(Proc.new { hash.delete(:key); hash }.call)
the_minted
sumber
1
Hash#exceptdan Hash#except!sudah cukup disebutkan. The Proc.newVersi ini tidak sangat mudah dibaca seperti yang Anda sebutkan dan juga lebih rumit daripada use_remaining_hash_for_something(begin hash.delete(:key); hash end). Mungkin hapus saja jawaban ini.
Michael Kohl
1
Memperpendek jawaban saya dan menghapus apa yang sudah dikatakan. Menjaga jawaban saya bersama dengan komentar Anda karena mereka menjawab pertanyaan dan membuat rekomendasi yang baik untuk digunakan.
the_minted
0

Berbagai cara untuk menghapus Key di Hash. Anda dapat menggunakan Metode apa pun dari bawah

hash = {a: 1, b: 2, c: 3}
hash.except!(:a) # Will remove *a* and return HASH
hash # Output :- {b: 2, c: 3}

hash = {a: 1, b: 2, c: 3}
hash.delete(:a) # will remove *a* and return 1 if *a* not present than return nil

Begitu banyak cara di sana, Anda dapat melihat Ruby doc of Hash di sini .

Terima kasih

Ketan Mangukiya
sumber
-12

Ini juga akan berfungsi: hash[hey] = nil

fdghdfg
sumber
3
h = {: a => 1,: b => 2,: c => 3}; h [: a] = nil; h.each {| k, v | menempatkan k} Tidak sama dengan: h = {: a => 1,: b => 2,: c => 3}; h.delete (: a); h.each {| k, v | menempatkan k}
obaqueiro
1
Untuk menghapus kunci dari hash tidak sama dengan menghapus nilai kunci dari hash. Karena hal ini dapat menyebabkan orang bingung, akan lebih baik untuk menghapus jawaban ini.
Sebastian Palma