Skip to content

Instantly share code, notes, and snippets.

@O-I
Last active August 2, 2023 17:31
Show Gist options
  • Select an option

  • Save O-I/c40d89c4aeb16ae2fc3f to your computer and use it in GitHub Desktop.

Select an option

Save O-I/c40d89c4aeb16ae2fc3f to your computer and use it in GitHub Desktop.
[TIx 4] Remove key-value pairs from a hash and return the hash

Note: As of Ruby 3.0.0, Hash#except is now part of the language. However, Ruby does not implement Hash#except!.

Sometimes I want to remove a specific key-value pair from a Ruby hash and get the resulting hash back. If you're using Rails or ActiveSupport you can accomplish this using Hash#except:

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

# note, the original hash is not modified
hash                # => { a: 1, b: 2, c: 3 }

# it also works with multiple key-value pairs
hash.except(:a, :c) # => { b: 2 }

# and returns the original hash if the key doesn't exist
hash.except(:q)    # => { a: 1, b: 2, c: 3 }

# there's also Hash#except!
hash.except!(:b)   # => { a: 1, c: 3 }

# this method does modify the original hash
hash               # => { a: 1, c: 3 }

# and also returns the original hash if the key doesn't exist
hash.except!(:b)   # => { a: 1, c: 3 }

Great. But what if we want to do this with plain old Ruby?

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

# the destructive case with removing exactly one key-value pair is easy
hash.tap { |h| h.delete(:a) }                         # => { b: 2, c: 3, d: 4 }
hash                                                  # => { b: 2, c: 3, d: 4 }

# the non-destructive case with one key-value pair isn't much harder
hash.dup.tap { |h| h.delete(:c) }                     # => { b: 2, d: 4 }
hash                                                  # => { b: 2, c: 3, d: 4 }

# but when we want to remove several key-value pairs, it gets a little messy
hash.dup.tap { |h| [:b, :c].map { |k| h.delete(k) } } # => { d: 4 }
hash                                                  # => { b: 2, c: 3, d: 4 }

# using reject makes it more palatable
hash.reject { |k, _| [:b, :c].include? k }            # => { d: 4 }
hash                                                  # => { b: 2, c: 3, d: 4 }

# and, of course, we can also use reject!
hash.reject! { |k, _| [:b, :c].include? k }           # => { d: 4 }
hash                                                  # => { d: 4 }

# not so fast...reject! doesn't behave the way we want when the keys aren't there :-(
hash.reject! { |k, _| [:b, :c].include? k }           # => nil
hash                                                  # => { d: 4 }

# so maybe tap-map-delete is the best we can do here
hash.tap { |h| [:b, :c].map { |k| h.delete(k) } }     # => { d: 4 }
hash                                                  # => { d: 4 }

How would you choose to do it?

@brooks
Copy link
Copy Markdown

brooks commented Feb 21, 2020

delete_if

@buraga-dmitrii
Copy link
Copy Markdown

But if want to remove keys in nested hash?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment