Моя собственная версия метода `слияния` для хэшей - PullRequest
0 голосов
/ 25 октября 2018

Я пытаюсь создать свою версию метода 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 голосов
/ 25 октября 2018

Давайте посмотрим на ваш код:

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

Поскольку вы не собираетесь передавать блок, вам не нужно явно указывать аргумент блока.Вы можете просто определить его как:

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

3 аргумента, которые мы передаем через yield, это ключ, старое значение и новыйзначение.


Возможно, вы заметили шаблон в своем коде:

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
0 голосов
/ 25 октября 2018

См. 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...