673

To add a new pair to Hash I do:

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

Is there a similar way to delete a key from Hash ?

This works:

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

but I would expect to have something like:

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

It is important that the returning value will be the remaining hash, so I could do things like:

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

in one line.

Scott Schupbach
  • 1,284
  • 9
  • 21
Misha Moroshko
  • 166,356
  • 226
  • 505
  • 746
  • 1
    You can always extend (open at runtime) the built in Hash to add this custom method if you really need it. – dbryson Jun 03 '11 at 13:45
  • Ruby 3 will have this. https://www.ruby-lang.org/en/news/2020/09/25/ruby-3-0-0-preview1-released/ – B Seven Oct 24 '20 at 14:54

18 Answers18

870

For those of you who just came here to know how to delete a key/value pair from a hash, you can use:
hash.delete(key)

For the rest of you who came here to read a wall of text about something entirely different, you can read the rest of this answer:

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
Timothy Alexis Vass
  • 2,526
  • 2
  • 11
  • 30
Peter Brown
  • 50,956
  • 18
  • 113
  • 146
  • 57
    You don't have to use the full Rails stack. You can include include ActiveSupport in any Ruby application. – Fryie Sep 27 '13 at 15:46
  • 12
    To add to Fryie's answer, you don't even need to load all of ActiveSupport; you can just include them then `require "active_support/core_ext/hash/except"` – GMA May 13 '16 at 07:33
  • too late to edit: I meant "include the gem" not "include them" – GMA May 13 '16 at 07:39
  • 2
    @GMA: when your five-minutes-of-editing are up, you can always copy, delete, modify, and repost a comment. – iconoclast May 13 '20 at 23:17
  • **A nice example to try:** `User.first.as_json.except("id")` – stevec Feb 12 '23 at 02:41
254

Why not just use:

hash.delete(key)

hash is now the "remaining hash" you're looking for.

Srini
  • 1,626
  • 2
  • 15
  • 25
dbryson
  • 6,047
  • 2
  • 20
  • 20
  • I would like to do `foo(h.reject!{ |k| k == :a })`. With your suggestion, I will have to do it in two lines. – Misha Moroshko Jun 03 '11 at 13:33
  • 4
    @dbryson: I agree that sometimes it doesn't worth it. I just wonder why there are `merge`, `merge!`, `delete`, but no `detele!`... – Misha Moroshko Jun 03 '11 at 13:49
  • 1
    if you realy need it as a one liner do: `foo(hash.delete(key) || hash)` – Bert Goethals Jun 04 '11 at 12:40
  • 30
    It would be more consistent with Ruby conventions if `delete` did **not** modify its parameter and if `delete!` existed and did modify its parameter. – David J. Jul 27 '12 at 05:21
  • 95
    This doesn't return the remaining hash as mentioned in the question, it will return the value associated with the deleted key. – MhdSyrwan Jul 09 '15 at 13:16
  • 1
    delete returns the key but it does also alter the hash. As to why there is no delete!, my guess is that it semantically doesn't make sense to call delete on something and not actually delete it. calling hash.delete() as opposed to hash.delete!() would be a no-op. – eggmatters Aug 07 '15 at 19:24
  • Readability win, and predictability fail. I'd advise people to *not* take this approach. I upvoted the answer anyway because it was helpful. – Jezen Thomas Jan 27 '16 at 16:41
  • 4
    @DavidJ. contrary to popular belief, an exclamation point does not indicate mutation, but rather "unusual behavior." I imagine there isn't a `delete!` because a key-deleting method that mutated seemed like the only "expected" behavior. – Jackson Sep 27 '17 at 20:12
  • 1
    This answer just wrong. The question explicitly says "It is important that the returning value will be the remaining hash". Hash.delete() does not return the hash. And very unfortunately, it mutates the hash. As others note, it should have been modeled after merge and merge!. – michael_teter Feb 05 '23 at 06:50
243

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} 
Fabio
  • 18,856
  • 9
  • 82
  • 114
  • 19
    +1 It's worth mentioning that this method is destructive on `h`. `Hash#except` will not modify the original hash. – Mulan Jul 22 '13 at 19:15
  • 9
    Use `h.dup.tap { |hs| hs.delete(:a) }` to avoid modifying the original hash. – Jimbali Nov 13 '18 at 16:15
134

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.

techdreams
  • 5,371
  • 7
  • 42
  • 63
  • 21
    `slice` and `except` are added by using `ActiveSupport::CoreExtensions::Hash`. They are not part of Ruby core. They can be used by `require 'active_support/core_ext/hash'` – Madis Nõmme Oct 09 '16 at 18:45
  • 8
    Since Ruby 2.5 `Hash#slice` is in the standard library. https://ruby-doc.org/core-2.5.0/Hash.html#method-i-slice Yay! – Madis Nõmme Nov 12 '19 at 15:03
  • Thanks for you very comprehensive answer. – Pablo Feb 26 '21 at 07:02
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}
Yura Taras
  • 1,293
  • 14
  • 26
33

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:

Marian13
  • 7,740
  • 2
  • 47
  • 51
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'

I've set this up so that .remove returns a copy of the hash with the keys removed, while remove! modifies the hash itself. This is in keeping with ruby conventions. eg, from the console

>> 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
  • 32,435
  • 31
  • 130
  • 197
27

You can use except! from the facets gem:

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

The original hash does not change.

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.

rewritten
  • 16,280
  • 2
  • 47
  • 50
  • Just `require 'facets/hash/except'` and their are no "issues" (not sure what issues they would be anyway other than not 100% AS API). If you are doing a Rails project using AS makes sense, if not Facets has a much smaller footprint. – trans Jan 05 '16 at 17:36
  • @trans ActiveSupport nowadays has a quite small footprint too, and you can require only parts of it. Just like facets, but with many more eyes on it (so I suppose it gets better reviews). – rewritten Jan 06 '16 at 18:23
21

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
Mohamad
  • 34,731
  • 32
  • 140
  • 219
18

in pure Ruby:

{:a => 1, :b => 2}.tap{|x| x.delete(:a)}   # => {:b=>2}
gamov
  • 3,789
  • 1
  • 31
  • 28
13

See Ruby on Rails: Delete multiple hash keys

hash.delete_if{ |k,| keys_to_delete.include? k }
Community
  • 1
  • 1
Nakilon
  • 34,866
  • 14
  • 107
  • 142
5

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} 
frenesim
  • 703
  • 9
  • 10
4

Try the except! method.

{:a => 1, :b => 2}.except!(:a)   #=> {:b => 2}
Sachin Singh
  • 993
  • 8
  • 16
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)
frediy
  • 1,766
  • 15
  • 16
  • 1
    `Hash#except` and `Hash#except!` have been mentioned enough already. The `Proc.new` version is not very readable as you mention and also more complicated than `use_remaining_hash_for_something(begin hash.delete(:key); hash end)`. Maybe just delete this answer. – Michael Kohl Jan 12 '14 at 16:13
  • 1
    Shortened my answer and removed what had already been said. Keeping my answer along with your comment because they answer the question and make good recommendations for use. – frediy Jan 14 '14 at 12:00
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

Ketan Mangukiya
  • 360
  • 1
  • 7
1

Use delete, except, or except!

sample_hash = {hey: 'hey', hello: 'hello'}

Delete:

sample_hash.delete(:hey)
=> 'hey'

sample_hash
=> {hello: 'hello'}

Returns value of the key and deletes the key in the original object, returns nil if no such key

Except:

sample_hash.except(:hey)
=> {hello: 'hello'}

sample_hash
=> {hey: 'hey', hello: 'hello'}

Returns the entire hash without the specified keys, but does not update the original hash

Except!: except! is the same as except but it permanently changes the state of the original hash like all bang operated methods do

sample_hash.except!(:hey)
=> {hello: 'hello'}

sample_hash
=> {hello: 'hello'}
Jibran Usman
  • 93
  • 1
  • 5
0

I want to delete a list of keys, and get back the deleted "slice" of the hash:

Rails:

hash = {a: 1, b: 2, c: 3}

def delete_slice!(hash, *keys)
  hash.slice(*keys).tap { hash.except!(*keys) }
end

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

Pure Ruby:

hash = {a: 1, b: 2, c: 3}

def delete_slice!(hash, *keys)
  hash.slice(*keys).tap { keys.each{ hash.delete _1 } }
end

delete_slice!(hash, :a, :b)
# => {a: 1, b: 2}
hash
# => {c: 3}
Narfanator
  • 5,595
  • 3
  • 39
  • 71
-13

This would also work: hash[hey] = nil

fdghdfg
  • 1
  • 2
  • 3
    h = {:a => 1, :b => 2, :c => 3}; h[:a]=nil; h.each{|k,v| puts k} Is not the same as: h = {:a => 1, :b => 2, :c => 3}; h.delete(:a); h.each{|k,v| puts k} – obaqueiro May 02 '14 at 20:48
  • 1
    To remove a key from a hash isn't the same as removing the value of a key from a hash. As this might lead people to confuse, it'd be better to remove this answer. – Sebastián Palma Nov 09 '18 at 13:15