从一个哈希中减去相应值的另一个哈希中的值

6

我希望能够在Ruby中计算两个哈希值的差,并得到一个第三个哈希值。

这两个哈希值看起来像这样:

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

我希望能够这样调用h1的方法:

h1.difference(h2)

并将此哈希作为结果获得:

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

我想创建一个新的哈希表,其中键来自两个哈希表,而新哈希表的值是第一个哈希表中该键的值减去第二个哈希表中该键的值。但是,有一个限制:我希望这个哈希方法能够不考虑键的大小写而正常工作。换句话说,我希望"Cat"与"cat"匹配。
以下是我的初步代码:
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

这还可以,但很遗憾,结果不是我想要的。
欢迎任何帮助。

示例中,键名大小写不同(例如第一个是Dog而第二个是dog),这是错误还是预期的呢? - Pritesh Jain
这是预期的结果。我有哈希表,其中一个哈希表中可能全部为小写字母,而另一个哈希表则为混合大小写字母。我想能够匹配例如"Dog"和"dog",或者"Dog"和"DOG"等。 - user1561696
2
如果h1={"cat" => 1, "Cat" => 2},h2={"cAt" => 3}会发生什么? - tokland
将键进行预处理以使其规范化是不可行的? - tokland
我没有遇到过这样的情况,所以我没有考虑到那个。这是一个好问题。我认为一种方法是在结果哈希表中不为“cat”(或“Cat”或“cAt”)提供任何值,因为h1中“cat”和“Cat”的值等于h2中“cAt”的值。 - user1561696
显示剩余2条评论
5个回答

4
将哈希转换为集合,您觉得如何?
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]}>

我不喜欢减法的结果与预期不同。我期望得到一个哈希值,但实际上得到了一个集合。除此之外,这个答案比其他答案更简洁。 - Marty Cortez
3
这不会减去键的值,这正是 OP 所请求的。比如,在所期望的结果中,"Dog" => 2 - Kyle Strand

3
作为一项推荐... 我过去使用过类似这样的东西:
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

这让我能够做一些如下的事情:

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
}
< p> alias 为您提供了使用 - 代替 difference 的简洁语法,类似于在数组和集合中使用 - 。 您仍然可以使用 difference :
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
}

我经常对哈希键进行规范化处理,不允许变量泄漏。如果你不知道键的名称,处理哈希值将变得非常困难,因此我强烈建议将此作为第一步。这是一个代码维护问题。

否则,你可以创建原始键名及其规范化名称的映射表,但如果你的哈希包含两个唯一大小写的键,例如 'key' 和 'KEY',那么规范化就会覆盖其中一个键,从而导致问题


1
你重新定义了数组减法吗?内置方法的行为类似于集合减法。 - Kyle Strand

0

非常抱歉,由于时间有限(我现在必须照顾我的宝贝男孩),只想出了这个愚蠢但有效的代码:

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"

不用谢。我认为你可以接受符合你要求的最佳答案。顺便说一下,我刚刚发布了另一个更好的答案。 - Jing Li

0

我找到了这个救星 https://github.com/junegunn/insensitive_hash

然后按照您的步骤进行了一些微调以满足要求

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} 

0
这次我想提供另一种解决方案:标准化 -> 存储原始键值对 -> 获取具有较大值的原始键作为差异的键。
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"

根据您的描述,这个做得相当不错。结果与您展示的完全一致(关键字在最终结果中未规范化,但取决于哪个值更大)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接