10

I have a hash like

h = {1 => {"inner" => 45}, 2 => {"inner" => 46}, "inner" => 47}

How do I delete every pair that contains the key "inner"?
You can see that some of the "inner" pairs appear directly in h while others appear in pairs in h

Note that I only want to delete the "inner" pairs, so if I call my mass delete method on the above hash, I should get

h = {1 => {}, 2 => {}}

Since these pairs don't have a key == "inner"

1
  • First deleting "inner" from h, and then recursing through all nested hashes and deleting "inner" Commented May 20, 2012 at 19:51

5 Answers 5

10

Really, this is what reject! is for:

def f! x
  x.reject!{|k,v| 'inner' == k} if x.is_a? Hash
  x.each{|k,v| f! x[k]}
end
Sign up to request clarification or add additional context in comments.

Comments

9
def except_nested(x,key)
  case x
  when Hash then x = x.inject({}) {|m, (k, v)| m[k] = except_nested(v,key) unless k == key ; m }
  when Array then x.map! {|e| except_nested(e,key)}
  end
  x
end

2 Comments

I have been actually needed this solution because I had nested arrays as well! THank you so much!
This was the only one that worked for me. Thank you.
8
def f x 
  x.inject({}) do |m, (k, v)|
    v = f v if v.is_a? Hash  # note, arbitrarily recursive
    m[k] = v unless k == 'inner'
    m
  end
end

p f h

Update: slightly improved...

def f x
  x.is_a?(Hash) ? x.inject({}) do |m, (k, v)|
    m[k] = f v unless k == 'inner'
    m
  end : x
end

1 Comment

The multi-line ternary combined with the block form of inject makes your improved solution difficult to read (especially when you combine those with the use of single-letter, nondescript method and variable names). It'd be a bit easier to follow if you used if/else instead of the ternary, and meaningful variable names: ``` def reject_keys(input, rejected_key) if input.is_a?(Hash) input.inject({}) do |hash, (k, v)| hash[k] = reject_keys(v) unless k == rejected_key hash end else input end end puts reject_keys(h, "inner") ```
3

Here is what I came up with:

class Hash
  def deep_reject_key!(key)
    keys.each {|k| delete(k) if k == key || self[k] == self[key] }

    values.each {|v| v.deep_reject_key!(key) if v.is_a? Hash }
    self
  end
end

Works for a Hash or a HashWithIndifferentAccess

> x = {'1' => 'cat', '2' => { '1' => 'dog', '2' => 'elephant' }}
=> {"1"=>"cat", "2"=>{"1"=>"dog", "2"=>"elephant"}}

> y = x.with_indifferent_access
=> {"1"=>"cat", "2"=>{"1"=>"dog", "2"=>"elephant"}}

> x.deep_reject_key!(:"1")
=> {"1"=>"cat", "2"=>{"1"=>"dog", "2"=>"elephant"}}

> x.deep_reject_key!("1")
=> {"2"=>{"2"=>"elephant"}}

> y.deep_reject_key!(:"1")
=> {"2"=>{"2"=>"elephant"}}

Comments

0

Similar answer but it is a whitelist type approach. For ruby 1.9+

# recursive remove keys
def deep_simplify_record(hash, keep)
  hash.keep_if do |key, value|
    if keep.include?(key) 
      deep_simplify_record(value, keep) if value.is_a?(Hash)
      true
    end
  end
end

hash = {:a => 1, :b => 2, :c => {:a => 1, :b => 2, :c => {:a => 1, :b => 2, :c => 4}} }
deep_simplify_record(hash, [:b, :c])
# => {:b=>2, :c=>{:b=>2, :c=>{:b=>2, :c=>4}}}

Also here are some other methods which I like to use for hashes. https://gist.github.com/earlonrails/2048705

1 Comment

There's an edge case: h = {a: 1, b: {c: 1, a: {b: 2}}}; deep_simplify_record(h, %i[b c]) produces {:b=>{:c=>1, :a=>{:b=>2}}}. Not all :as were removed. The include? check needs to be done regardless of whether v is a Hash.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.