我自己的散列的`merge`方法版本

时间:2018-10-25 03:49:59

标签: ruby

我正在尝试为哈希创建merge方法的版本。这是测试之一:

test_hash_1 = { a: 10, b: 2 }
test_hash_2 = { a: 5, b: 10, c: 3 }
test_hash_1.my_merge(test_hash_2) { |key, oldval, newval| newval - oldval } #=> {a: -5, b: 8, c: 3}

类似于Hash#merge,代码需要返回一个特定键的所有值的数组。例如:

test_hash_1 = { a: 10, b: 2 }
test_hash_2 = { b: 3, c: 4 }
expect { |b| test_hash_1.my_merge(test_hash_2, &b)}.to yield_successive_args([:b, 2, 3])

这就是我所拥有的:

def my_merge(hash2, &blk)
    new_hash = self    
    if block_given?
      hash2.each do |k1, v1|
        new_hash[k1] = blk.call
      end
    else
      hash2.each do |k2, v2|
        new_hash[k2] = v2
      end
    end
    new_hash
  end
end

我很难理解积木的工作原理。我的代码与预期结果不符。我将不胜感激。

2 个答案:

答案 0 :(得分:5)

让我们看一下您的代码:

def my_merge(hash2, &blk)
  # ...
end

由于您不会传递该块,因此不必显式指定block参数。您可以将其定义为:

def my_merge(hash2)
  # ...
end

并使用yield(...)代替blk.call(...)


您通过以下方式创建new_hash

  new_hash = self

,它将使new_hash[k1] = ...等效于self[k1] = ...。为避免修改接收者,请改为通过dup创建一个副本:

  new_hash = dup

您的第一个条件检查是否给出了块。但是根据文档,仅对重复条目调用该块。因此实际的条件是:密钥是否存在 是给定的块。并且由于必须考虑密钥,因此我们必须将其移至each块中:

  hash2.each do |k, v|
    if new_hash.key?(k) && block_given?
      new_hash[k] = yield(k, new_hash[k], v)
    else
      new_hash[k] = v
    end
  end

我们通过yield传递的3个参数是键,旧值和新值。


您可能已经注意到代码中的模式:

def m(ary)
  obj = initial_value
  ary.each do |e|
    # modify obj
  end
  obj
end

可以使用each_with_object将其表达得更加简洁:

def m(ary)
  ary.each_with_object(initial_value) do |e, o|
    # modify obj
  end
end

整个代码:

class Hash
  def my_merge(hash)
    hash.each_with_object(dup) do |(k, v), h|
      if h.key?(k) && block_given?
        h[k] = yield(k, h[k], v)
      else
        h[k] = v
      end
    end
  end
end

答案 1 :(得分:1)

有关要求,请参见Hash#merge

class Hash
  def my_merge(h)
    keys.each_with_object({}) do |k,g|
      g[k] = if h.key?(k)
               block_given? ? yield(k, self[k], h[k]) : h[k]
             else
               self[k]
             end
    end.tap { |g| (h.keys-keys).each { |k| g[k] = h[k] } }
  end
end

h = { a: 1, b: 2, c: 3 }
g = {       b: 3, c: 4, d: 5 }        

h.my_merge(g)                 #=> {:a=>1, :b=>3, :c=>4, :d=>5}
h.merge(g)                    #=> {:a=>1, :b=>3, :c=>4, :d=>5}

h.my_merge(g) { |_,o,n| o+n } #=> {:a=>1, :b=>5, :c=>7, :d=>5}
h.merge(g)    { |_,o,n| o+n } #=> {:a=>1, :b=>5, :c=>7, :d=>5}

merge不返回公用键的值的数组,但可以与特定的块一起使用来完成此操作:

h.my_merge(g) { |_,o,v| [o, v] } #=> {:a=>1, :b=>[2, 3], :c=>[3, 4], :d=>5}
h.merge(g)    { |_,o,v| [o, v] } #=> {:a=>1, :b=>[2, 3], :c=>[3, 4], :d=>5}

对于不熟悉Object#tap的读者,如果没有它,我将需要编写类似于以下内容的方法。

def my_merge(h)
  g = keys.each_with_object({}) do |k,g|
    g[k] = if h.key?(k)
             block_given? ? yield(k, self[k], h[k]) : h[k]
           else
             self[k]
           end
  end
  (h.keys-keys).each { |k| g[k] = h[k] }
  g
end