Non destructive way of deleting a key from a hash
Asked Answered
R

5

9

Is there a non-destructive way of deleting a key value pair from a hash?

For example, if you did

original_hash = {:foo => :bar}
new_hash = original_hash
new_hash = new_hash.reject{|key, _| key == :foo}

or

original_hash = {:foo => :bar}
new_hash = original_hash
new_hash = new_hash.dup
new_hash.delete(:foo)

then original_hash is unchanged, and new_hash is changed, but they're a tad verbose. However, if you did

original_hash = {:foo => :bar}
new_hash = original_hash
new_hash.delete(:foo)

then original_hash is changed, which isn't what I want.

Is there a single method that does what I want?

Restoration answered 21/11, 2012 at 2:53 Comment(1)
original_hash is changed in your first example using delete_if. Also, new_hash in your second example using delete gets the value associated with :foo, i.e. :bar, not the hash itself.Husband
K
11

ActiveSupport provides a hash extension: Hash#except. It allows you to return a new hash except specified keys without modifying the original.

Assuming you have installed the active_support gem:

ruby-1.9.3> require 'active_support/core_ext/hash/except.rb'
 => true
 ruby-1.9.3> a = {x: 2, y: 1, z: 3}
 => {:x=>2, :y=>1, :z=>3} 
ruby-1.9.3> b = a.except(:x)
 => {:y=>1, :z=>3} 
ruby-1.9.3> c = a.except(:x, :y)
 => {:z=>3} 
ruby-1.9.3> a
 => {:x=>2, :y=>1, :z=>3} 
ruby-1.9.3> b
 => {:y=>1, :z=>3} 
ruby-1.9.3> c
 => {:z=>3} 
Karb answered 21/11, 2012 at 3:9 Comment(1)
I don't use ActiveSupport, but the fact that they supply such a method is good evidence that Ruby itself doesn't have such a method.Restoration
T
14

Yes, you want reject:

new_hash = original_hash.reject{|key, _| key == :foo}
Toneless answered 21/11, 2012 at 3:8 Comment(0)
K
11

ActiveSupport provides a hash extension: Hash#except. It allows you to return a new hash except specified keys without modifying the original.

Assuming you have installed the active_support gem:

ruby-1.9.3> require 'active_support/core_ext/hash/except.rb'
 => true
 ruby-1.9.3> a = {x: 2, y: 1, z: 3}
 => {:x=>2, :y=>1, :z=>3} 
ruby-1.9.3> b = a.except(:x)
 => {:y=>1, :z=>3} 
ruby-1.9.3> c = a.except(:x, :y)
 => {:z=>3} 
ruby-1.9.3> a
 => {:x=>2, :y=>1, :z=>3} 
ruby-1.9.3> b
 => {:y=>1, :z=>3} 
ruby-1.9.3> c
 => {:z=>3} 
Karb answered 21/11, 2012 at 3:9 Comment(1)
I don't use ActiveSupport, but the fact that they supply such a method is good evidence that Ruby itself doesn't have such a method.Restoration
A
1

The problem is that new_hash is a reference unless you explicitly tell Ruby to duplicate the object. You're on the right track using dup, the only thing I might recommend is to do

new_hash = original_hash.dup 

because I think thats more explicit about what you're doing.

Aron answered 21/11, 2012 at 3:2 Comment(0)
A
0

What about chaining the dup to make it less verbose?

new_hash = original_hash.dup.delete_if{|key, _| key == :foo}
Amontillado answered 21/11, 2012 at 3:3 Comment(0)
O
0
original_hash.clone.tap {|h| h.delete key}

tap is arguably less clunky than declaring a new variable, mostly because it's on the same line.

clone is, I think, more explicitly a flat copy than dup, though of course for hashes it makes little difference.

Oleum answered 9/3, 2017 at 5:20 Comment(1)
While this code snippet may solve the question, including an explanation really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion.Psychologize

© 2022 - 2024 — McMap. All rights reserved.