Рекурсивная установка ключей хеша из массива ключей
Я хочу функцию, которая может принимать массив как [:a, :b, :c]
и рекурсивно устанавливать ключи хеша, создавая то, что ему нужно, по ходу дела.
hash = {}
hash_setter(hash, [:a, :b, :c], 'value')
hash #=> {:a => {:b => {:c => 'value' } } }
hash_setter(hash, [:a, :b, :h], 'value2')
hash #=> {:a => {:b => {:c => 'value', :h => 'value2' } } }
Я знаю, что Ruby 2.3 dig
может быть использован для получения таким образом, хотя это не совсем дает вам ответ. Если бы был сеттер, эквивалентный копанию, это было бы тем, что я искал.
3 ответа
Решил это с рекурсией:
def hash_setter(hash, key_arr, val)
key = key_arr.shift
hash[key] = {} unless hash[key].is_a?(Hash)
key_arr.length > 0 ? hash_setter(hash[key], key_arr, val) : hash[key] = val
end
Код
def nested_hash(keys, v, h={})
return subhash(keys, v) if h.empty?
return h.merge(subhash(keys, v)) if keys.size == 1
keys[0..-2].reduce(h) { |g,k| g[k] }.update(keys[-1]=>v)
h
end
def subhash(keys, v)
*first_keys, last_key = keys
h = { last_key=>v }
return h if first_keys.empty?
first_keys.reverse_each.reduce(h) { |g,k| g = { k=>g } }
end
Примеры
h = nested_hash([:a, :b, :c], 14) #=> {:a=>{:b=>{:c=>14}}}
i = nested_hash([:a, :b, :d], 25, h) #=> {:a=>{:b=>{:c=>14, :d=>25}}}
j = nested_hash([:a, :b, :d], 99, i) #=> {:a=>{:b=>{:c=>14, :d=>99}}}
k = nested_hash([:a, :e], 104, j) #=> {:a=>{:b=>{:c=>14, :d=>99}, :e=>104}}
nested_hash([:f], 222, k) #=> {:a=>{:b=>{:c=>14, :d=>99}, :e=>104}, :f=>222}
Обратите внимание, что значение :d
переопределяется при расчете j
, Также обратите внимание, что:
subhash([:a, :b, :c], 12)
#=> {:a=>{:b=>{:c=>12}}}
Это мутирует хеш h
, Если это не желательно, можно вставить строку
f = Marshal.load(Marshal.dump(h))
после линии return subhash(keys, v) if h.empty?
и изменить последующие ссылки на h
в f
, Методы из модуля Marshal могут использоваться для создания глубокой копии хэша, чтобы исходный хеш не подвергался мутации.
def set_value_for_keypath(initial, keypath, value)
temp = initial
for key in keypath.first(keypath.count - 1)
temp = (temp[key] ||= {})
end
temp[keypath.last] = value
return initial
end
initial = {:a => {:b => {:c => 'value' } } }
set_value_for_keypath(initial, [:a, :b, :h], 'value2')
initial
Или, если вы предпочитаете что-то более нечитаемое:
def set_value_for_keypath(initial, keypath, value)
keypath.first(keypath.count - 1).reduce(initial) { |hash, key| hash[key] ||= {} }[keypath.last] = value
end