625

要向哈希添加一个新对,我会:

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

有没有类似的方法从 Hash 中删除一个键?

这有效:

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

但我希望有类似的东西:

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

重要的是返回值将是剩余的哈希,因此我可以执行以下操作:

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

在一行中。

4

16 回答 16

817

Rails has an except/except! method that returns the hash with those keys removed. If you're already using Rails, there's no sense in creating your own version of this.

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
于 2012-06-19T17:03:50.327 回答
230

Oneliner plain ruby, it works only with 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} 

Tap method always return the object on which is invoked...

Otherwise if you have required active_support/core_ext/hash (which is automatically required in every Rails application) you can use one of the following methods depending on your needs:

➜  ~  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} 

except uses a blacklist approach, so it removes all the keys listed as args, while slice uses a whitelist approach, so it removes all keys that aren't listed as arguments. There also exist the bang version of those method (except! and slice!) which modify the given hash but their return value is different both of them return an hash. It represents the removed keys for slice! and the keys that are kept for the 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} 
于 2012-01-27T00:35:21.487 回答
225

为什么不直接使用:

hash.delete(key)

hash现在是您正在寻找的“剩余哈希”。

于 2011-06-03T13:23:16.290 回答
116

There are many ways to remove a key from a hash and get the remaining hash in Ruby.

  1. .slice => It will return selected keys and not delete them from the original hash. Use slice! if you want to remove the keys permanently else use simple 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 => It will delete the selected keys from the original hash(it can accept only one key and not more than one).

    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 => It will return the remaining keys but not delete anything from the original hash. Use except! if you want to remove the keys permanently else use simple 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 => In case you need to remove a key based on a value. It will obviously remove the matching keys from the original hash.

    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 => It is used to remove all nil values from the hash. Use compact! if you want to remove the nil values permanently else use simple 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}
    

Results based on Ruby 2.2.2.

于 2016-08-30T15:05:36.147 回答
39

If you want to use pure Ruby (no Rails), don't want to create extension methods (maybe you need this only in one or two places and don't want to pollute namespace with tons of methods) and don't want to edit hash in place (i.e., you're fan of functional programming like me), you can 'select':

>> 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}
于 2012-12-19T13:55:03.267 回答
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'

我已经进行了设置,以便 .remove 返回哈希的副本,其中删除了键,而删除!修改哈希本身。这符合 ruby​​ 约定。例如,从控制台

>> 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)
=> {}
于 2011-06-03T14:42:11.283 回答
27

您可以except!facetsgem 中使用:

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

原始哈希不会改变。

EDIT: as Russel says, facets has some hidden issues and is not completely API-compatible with ActiveSupport. On the other side ActiveSupport is not as complete as facets. In the end, I'd use AS and let the edge cases in your code.

于 2011-10-13T00:25:32.663 回答
20

Instead of monkey patching or needlessly including large libraries, you can use refinements if you are using 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

You can use this feature without affecting other parts of your program, or having to include large external libraries.

class FabulousCode
  using HashExtensions

  def incredible_stuff
    delightful_hash.except(:not_fabulous_key)
  end
end
于 2014-12-12T19:19:50.333 回答
17

in pure Ruby:

{:a => 1, :b => 2}.tap{|x| x.delete(:a)}   # => {:b=>2}
于 2012-09-07T04:25:13.770 回答
17

Hash#except (Ruby 3.0+)

Starting from Ruby 3.0, Hash#except is a build-in method.

As a result, there is no more need to depend on ActiveSupport or write monkey-patches in order to use it.

h = { a: 1, b: 2, c: 3 }
p h.except(:a) #=> {:b=>2, :c=>3}

Sources:

于 2020-10-04T12:29:17.123 回答
13

See Ruby on Rails: Delete multiple hash keys

hash.delete_if{ |k,| keys_to_delete.include? k }
于 2014-04-07T04:18:19.640 回答
4

It's was great if delete return the delete pair of the hash. I'm doing this:

hash = {a: 1, b: 2, c: 3}
{b: hash.delete(:b)} # => {:b=>2}
hash  # => {:a=>1, :c=>3} 
于 2018-09-02T09:48:38.633 回答
3

Try the except! method.

{:a => 1, :b => 2}.except!(:a)   #=> {:b => 2}
于 2021-04-04T17:18:06.760 回答
1

This is a one line way to do it, but it's not very readable. Recommend using two lines instead.

use_remaining_hash_for_something(Proc.new { hash.delete(:key); hash }.call)
于 2014-01-12T15:33:02.123 回答
1

Multiple ways to delete Key in Hash. you can use any Method from below

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

So many ways is there, you can look on Ruby doc of Hash here.

Thank you

于 2019-05-14T07:27:52.397 回答
-12

This would also work: hash[hey] = nil

于 2014-02-02T19:29:10.690 回答