Subtract values in hash from corresponding values

2020-02-14 01:54发布

I'd like to be able to subtract two hashes and get a third hash in Ruby.

The two hashes look like this:

h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0

h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0

I'd like to be able to call a method on h1 like this:

h1.difference(h2)

and get this hash as a result:

{"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}

I'd like to create a new hash with keys from both Hashes and the values of the new hash to be the value of the key in the first hash minus the value of that key in the second hash. The catch is that I'd like this Hash method to work regardless of the case of the keys. In other words, I'd like "Cat" to match up with "cat".

Here's what I have so far:

class Hash
  def difference(another_hash)
    (keys + another_hash.keys).map { |key| key.strip }.uniq.inject(Hash.new(0)) { |acc, key| acc[key] = (self[key] - another_hash[key]); acc }.delete_if { |key, value| value == 0 }
  end
end

This is OK, but, unfortunately, the result isn't what I want.

Any help would be appreciated.

标签: ruby hash
5条回答
爷的心禁止访问
2楼-- · 2020-02-14 02:36

How about converting the hashes to sets.

require 'set'

h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0

h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0

p (h1.to_set - h2.to_set)
#=> #<Set: {["Cat", 100], ["Dog", 5], ["Bird", 2]}>
查看更多
We Are One
3楼-- · 2020-02-14 02:40

Sorry that due to the time limit (I have to take care of my baby boy now), only figured out this stupid but working code:

h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
h3 = {"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}

class Hash
  def difference(subtrahend)
    diff = {}
    self.each_pair do |k1, v1|
      flag = false
      subtrahend.each_pair do |k2, v2|
        if k1.downcase == k2.downcase
          flag = true
          v_diff = v1 - v2
          break if v_diff == 0
          v_diff > 0 ? diff[k1] = v_diff : diff[k2] = v_diff
        end
      end
      diff[k1] = v1 unless flag
    end
    subtrahend.each_pair do |k2, v2|
      flag = false
      self.each_pair do |k1, v1|
        if k1.downcase == k2.downcase
          flag = true
          break
        end
      end
      diff[k2] = -v2 unless flag
    end
    return diff
  end
end

h1.difference(h2) == h3 ? puts("Pass") : puts("Fail") #=> "Pass"
查看更多
老娘就宠你
4楼-- · 2020-02-14 02:43

I got this to the resque https://github.com/junegunn/insensitive_hash

then follow your procedure but slighly tweaked as requirement

require 'insensitive_hash'

h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}.insensitive
h1.default = 0

h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}.insensitive
h2.default = 0


class Hash
  def difference(another_hash)
    (keys + another_hash.keys).map { |key|
      key.downcase }.uniq.inject(Hash.new(0)) do |acc, key|
      val = self[key] - another_hash[key]
      acc[key] = val if val!= 0
      acc
    end
  end
end

h1.difference(h2)
# => {"cat"=>50, "dog"=>2, "bird"=>-2, "mouse"=>-75} 
查看更多
祖国的老花朵
5楼-- · 2020-02-14 02:48

As a recommendation...

I've used something like this in the past:

class Hash
  def downcase_keys
    Hash[map{ |k,v| [k.downcase, v]}]
  end

  def difference(other)
    Hash[self.to_a - other.to_a]
  end
  alias :- :difference
end

which lets me do things like:

irb(main):206:0> h1.downcase_keys - h2.downcase_keys
{
     "cat" => 100,
     "dog" => 5,
    "bird" => 2
}
irb(main):207:0> h2.downcase_keys - h1.downcase_keys
{
      "cat" => 50,
      "dog" => 3,
     "bird" => 4,
    "mouse" => 75
}

The alias gives you the nice syntax of using - instead of difference, similar to using - for Arrays and Sets. You can still use difference though:

irb(main):210:0> h1.downcase_keys.difference(h2.downcase_keys)
{
     "cat" => 100,
     "dog" => 5,
    "bird" => 2
}
irb(main):211:0> h2.downcase_keys.difference(h1.downcase_keys)
{
      "cat" => 50,
      "dog" => 3,
     "bird" => 4,
    "mouse" => 75
}

I always normalize my hash keys, and don't allow variants to leak in. It makes processing the hashes much too difficult when you don't know what the keys are called, so I'd highly recommend doing that as a first step. It's a code-maintenance issue.

Otherwise, you could create a map of the original key names and their normalized names, but you run into problems if your hash contains two unique-case keys, such as 'key' and 'KEY', because normalizing will stomp on one.

查看更多
放荡不羁爱自由
6楼-- · 2020-02-14 02:59

This time I would like to provide another solution: normalized -> store original key value pairs -> grab the original key who has larger value as the key for the difference.

h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
h3 = {"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}


class Hash
  def difference(subtrahend)
    # create a hash which contains all normalized keys
    all_pairs = (self.keys.map{|x| x.downcase} + subtrahend.keys.map{|x| x.downcase}).uniq.inject({}) do |pairs, key|
      pairs[key] = []
      pairs
    end
    #=> {"mouse"=>[], "cat"=>[], "snake"=>[], "bird"=>[], "dog"=>[]}

    # push original key value pairs into array which is the value of just created hash
    [self, subtrahend].each_with_index do |hsh, idx|
      hsh.each_pair { |k, v| all_pairs[k.downcase].push([k, v]) }
      all_pairs.each_value { |v| v.push([nil, 0]) if v.size == idx }
    end
    #=> {"mouse"=>[[nil, 0], ["Mouse", 75]], "cat"=>[["Cat", 100], ["cat", 50]], "snake"=>[["Snake", 10], ["Snake", 10]], "bird"=>[["Bird", 2], ["BIRD", 4]], "dog"=>[["Dog", 5], ["dog", 3]]}

    results = {}
    all_pairs.each_value do |values|
      diff = values[0][1] - values[1][1]
      # always take the key whose value is larger
      if diff > 0
        results[values[0][0]] = diff
      elsif diff < 0
        results[values[1][0]] = diff
      end
    end
    return results
  end
end

puts h1.difference(h2).inspect #=> {"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
h1.difference(h2) == h3 ? puts("Pass") : puts("Fail") #=> "Pass"

According to what you described, this one does a pretty good job. The result is exactly what you've shown (key is not normalized in the final result, but depends on whose value is bigger).

查看更多
登录 后发表回答