문제

I have the following method to delete the specified values from a Ruby hash in my Rails application:

def remove_hash_values(hash, target = nil)
    hash.delete_if {|key, value| value == target}
    hash.each_value {|obj| remove_hash_values(obj, target) if obj.is_a?(Hash)}
end

Something like this works:

remove_hash_values(some_hash, :some_symbol)

However, this does not work:

remove_hash_values(some_hash, {})

It works in irb, which is what confuses me. I'm positive the correct hash is being passed (checked with many puts statements). My Ruby version is ruby-2.0.0-p247, and I'm using Rails 3. Any help would be greatly appreciated.

EDIT: This is not working, either:

def remove_hash_values(hash, target = nil)
    hash.each do |key, value|
        hash.delete(key) if value == target
    end
    hash.each_value {|obj| remove_hash_values(obj, target) if obj.is_a?(Hash)}
end

What the hell am I doing wrong?!

EDIT 2: Just realised that I'm actually using a HashWithIndifferentAccess, and not a Hash. That could be causing some trickery, so I'll try converting it to a Hash first and reporting back.

도움이 되었습니까?

해결책

I've tried something similar, e.g.

2.0.0-p195 :092 > class Hash
2.0.0-p195 :093?>   def delete_values! target
2.0.0-p195 :094?>     delete_if { |k, v| v == target }
2.0.0-p195 :095?>     each_value { |v| v.delete_values!(target) if v.is_a?(Hash) }
2.0.0-p195 :096?>   end
2.0.0-p195 :097?> end

Works ok:

2.0.0-p195 :098 > { a: 1, b: 2, c: 3 }.delete_values! 2
 => {:a=>1, :c=>3} 

Also works:

2.0.0-p195 :101 > { a: 1, b: 2, c: { d: {}, e: 5 } }.delete_values!({})
 => {:a=>1, :b=>2, :c=>{:e=>5}} 

Note that this approach won't delete an empty hash if you should make an empty hash further down in the structure--if you had a hash containing only empty hashes--you need to switch the order of the recursion and deletion, e.g.

2.0.0-p195 :092 > class Hash
2.0.0-p195 :093?>   def delete_values! target
2.0.0-p195 :094?>     each_value { |v| v.delete_values!(target) if v.is_a?(Hash) }
2.0.0-p195 :095?>     delete_if { |k, v| v == target }
2.0.0-p195 :096?>   end
2.0.0-p195 :097?> end

다른 팁

(Shown as answer rather than comment to enable display of code.)

Please share your failing test. The following works when run from the Rails 4.0 console with Ruby 2.0p247:

def remove_hash_values(hash, target = nil)
  hash.delete_if {|key, value| value == target}
  hash.each_value {|obj| remove_hash_values(obj, target) if obj.is_a?(Hash)}
end

some_hash = HashWithIndifferentAccess.new
some_hash[:foo] = :some_symbol
some_hash[:bar] = {}

remove_hash_values(some_hash, {})

puts some_hash.inspect   # => {"foo"=>:some_symbol}

Isaac, I like your idea of using recursion, because it deals with hashes nested to any level. I think I understand why your code doesn't work, and how you can fix it. Here's your code:

def remove_hash_values(hash, target = nil)
  hash.delete_if {|key, value| value == target}
  hash.each_value {|obj| remove_hash_values(obj, target) if obj.is_a?(Hash)}
end

Suppose

hash = {a: 10, b: {c: 10}}

and we invoke:

remove_hash_values(hash, 10)

I expect you want remove_hash_values(hash, 10) to return {}, but I believe it returns {b: {}}. Let's go through the calculations:

remove_hash_values(hash, 10)
hash.delete_if {|key, value| value == target}       # hash => {b: {c: 10}} 
hash.each_value {|obj| remove_hash_values(obj, target) if obj.is_a?(Hash)} calls (next line)

  remove_hash_values({c: 10})
  hash.delete_if {|key, value| value == target} # hash => {} 
  hash.each_value {|obj| remove_hash_values(obj, target) if obj.is_a?(Hash)} # hash => {}

hash => {b: {}}

Here's how I think you could do it, though I've haven't checked my code. First suppose target is not a hashor another structure, just a simple value. Try this:

def remove_hash_values(hash, target = nil)
  hash.keys.each do |key|
    value = hash[key]
    case value
    when Hash
      val = remove_hash_values(hash[key], target)  
      if val == {}
        hash.delete(key)
      else
        hash[key] = val
      end
    else
      hash.delete(key) if val == target
    end
  end
  hash
end

If you want to generalize this, so that target could be a (possibly nested) hash, I think this may work:

def remove_hash_values(hash, target = nil)
  hash.keys.each do |key|
    value = hash[key]
    if value.is_a? Hash
      if target.is_a? Hash && hash[key] == target
        hash.delete(key)
        next
      end
    else
      # value is not a hash
      hash.delete(key) if !(target.is_a? Hash) && if hash[key] == target)
      next
    end
    # Value is a hash, target may or may not be a hash, value != target
    val = remove_hash_values(hash[key], target)  
    if val == target
      hash.delete(key)
    else    
      hash(key)= val
    end
  end
  hash
end 
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top