9

Basically I want to assign an array using #dig.

My has has to be like this:

hash = {
   :first => {
      :second => [1,2,3,4]
  }
}

and I would use Hash#dig

hash.dig(:first, :second) = [1,2,3,4]

How can I assign this value?

fphilipe
  • 9,739
  • 1
  • 40
  • 52
sparkle
  • 7,530
  • 22
  • 69
  • 131
  • Do you mean, "given, `:first`, `:second` and `[1,2,3,4]`, how can I create a hash `{ :first=>{:second=>[1,2,3,4] } }`"? If so, that has nothing to do with `dig`. – Cary Swoveland Jun 17 '19 at 17:23

3 Answers3

8

You can create a hash that behaves like what you want. Hash.new takes a block which is invoked whenever a key lookup fails. We can create an empty hash when that happens:

hash = Hash.new { |hash, key| hash[key] = Hash.new(&hash.default_proc) }

hash[:first][:second] = [1, 2, 3, 4]

hash # => {:first=>{:second=>[1, 2, 3, 4]}}

Note though that merely accessing an inexistent key will result in the creation of a new hash:

hash.dig(:a, :b, :c) # => {}

hash # => {:first=>{:second=>[1, 2, 3, 4]}, :a=>{:b=>{:c=>{}}}}

hash[:foo].nil? # => false
fphilipe
  • 9,739
  • 1
  • 40
  • 52
  • 2
    `Hash.new { |h,k| h[k] = Hash.new(&h.default_proc) }` lets you define nested keys indefinitely, not just at the first 2 levels – max pleaner Jun 17 '19 at 17:59
  • 2
    Attaching a default proc to the hash has consequences that must be recognised. For example, one cannot execute `hash[:cat].nil?` to determine whether hash has a key `:cat`, for `hash[:cat] #=> {}`. (Of course one could use `hash.key?(:cat) #=> false`, which is good practice anyway.). Another example is `hash[:fisrt][:second] == [1,2,3,4] #=> false`, masking the fact that I've misspelled `:first`. Without the default proc an exception would be raised. My preference would be to avoid having the default proc once the hash has been constructed, but of course it cannot be simply removed. – Cary Swoveland Jun 17 '19 at 19:53
  • Thanks for the input, guys. I've edited my answer accordingly. – fphilipe Jun 18 '19 at 05:24
5

dig cannot be used to assign a value to a Hash, this method has been built only for accessing a value.

For your case, you can do one of both things :

hash = { first: { second: [1, 2, 3, 4] } }

Or :

hash[:first] = { second: [1, 2, 3, 4] }

You can also use the approach in that post : How to set dynamically value of nested key in Ruby hash

They create a new hash method to dynamically assign nested values to a Hash.

Uelb
  • 3,947
  • 2
  • 21
  • 32
4

I've assumed that the answer to the question I raised in a comment on the question is "yes".

One could use Enumerable#reduce (a.k.a. inject):

def undig(*keys, value)
  keys[0..-2].reverse_each.reduce (keys.last=>value) { |h,key| { key=>h } }   
end

undig(:first, :second, [1,2,3,4])
  #=> {:first=>{:second=>[1, 2, 3, 4]}} 

or recursion:

def undig(*keys, value)
  keys.empty? ? value : { keys.first=>undig(*keys.drop(1), value) }
end

undig(:first, :second, [1,2,3,4])
  #=> {:first=>{:second=>[1, 2, 3, 4]}} 
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • 4
    this was proposed to and rejected from ruby core in the form of `Hash#bury` .. https://bugs.ruby-lang.org/issues/11747 – max pleaner Jun 17 '19 at 17:57
  • 1
    also, it'd probably be good if your `undig` method here could be called on an existing hash – max pleaner Jun 17 '19 at 18:00
  • `dig` isn't limited to hashes, it can also retrieve values from arrays (or mixed hash-array structures). `undig` should probably work likewise. – Stefan Jun 18 '19 at 08:07
  • @Stefan, see my first sentence. If the OP is moved to answer my question, we will know more. Perhaps `undig` was a poor choice for the name of my method. Btw, you forgot [OpenStruct#dig](https://ruby-doc.org/stdlib-2.5.1/libdoc/ostruct/rdoc/OpenStruct.html#method-i-dig). – Cary Swoveland Jun 18 '19 at 15:27