how to make a deep_slice in a hash on ruby

1.9k Views Asked by At

I was looking around for a clean way to do this and I found some workarounds but did not find anything like the slice (some people recommended to use a gem but I think is not needed for this operations, pls correct me if I am wrong), so I found myself with a hash that contains a bunch of hashes and I wanted a way to perform the Slice operation over this hash and get also the key/value pairs from nested hashes, so the question:

Is there something like deep_slice in ruby?

Example:

input: a = {b: 45, c: {d: 55, e: { f: 12}}, g: {z: 90}}, keys = [:b, :f, :z]

expected output: {:b=>45, :f=>12, :z=>90}

Thx in advance!

3

There are 3 best solutions below

0
r4cc00n On BEST ANSWER

After looking around for a while I decided to implement this myself, this is how I fix it:

a = {b: 45, c: {d: 55, e: { f: 12}}, g: {z: 90}}
keys = [:b, :f, :z]
def custom_deep_slice(a:, keys:)
    result = a.slice(*keys)
    a.keys.each do |k|
        if a[k].class == Hash
            result.merge! custom_deep_slice(a: a[k], keys: keys)
        end
    end
    result
end
c_deep_slice = custom_deep_slice(a: a, keys: keys)
p c_deep_slice

The code above is a classic DFS, which takes advantage of the merge! provided by the hash class.

You can test the code above here

7
Cary Swoveland On
require 'set'

def recurse(h, keys)
  h.each_with_object([]) do |(k,v),arr|
    if keys.include?(k)
      arr << [k,v]
    elsif v.is_a?(Hash)
      arr.concat(recurse(v,keys))
    end
  end
end

hash = { b: 45, c: { d: 55, e: { f: 12 } }, g: { b: 21, z: 90 } }
keys = [:b, :f, :z]
arr = recurse(hash, keys.to_set)
  #=> [[:b, 45], [:f, 12], [:b, 21], [:z, 90]]

Notice that hash differs slightly from the example hash given in the question. I added a second nested key :b to illustrate the problem of returning a hash rather than an array of key-value pairs. Were we to convert arr to a hash the pair [:b, 45] would be discarded:

arr.to_h
  #=> {:b=>21, :f=>12, :z=>90}

If desired, however, one could write:

arr.each_with_object({}) { |(k,v),h| (h[k] ||= []) << v }
  #=> {:b=>[45, 21], :f=>[12], :z=>[90]}

I converted keys from an array to a set merely to speed lookups (keys.include?(k)).

A slightly modified approach could be used if the hash contained nested arrays of hashes as well as nested hashes.

0
qosh_dev On

My version

maybe it should help


def deep_slice( obj, *args )
    deep_arg = {}
    slice_args = []
    args.each do |arg|
        if arg.is_a? Hash
            arg.each do |hash|
                key, value = hash
                if obj[key].is_a? Hash
                    deep_arg[key] = deep_slice( obj[key], *value ) 
                elsif obj[key].is_a? Array
                    deep_arg[key] = obj[key].map{ |arr_el| deep_slice( arr_el, *value) }
                end
            end
        elsif arg.is_a? Symbol
            slice_args << arg
        end
    end
    obj.slice(*slice_args).merge(deep_arg)
end

Object to slice

obj = {
  "id": 135,
  "kind": "transfer",
  "customer": {
    "id": 1,
    "name": "Admin",
  },
  "array": [
        {
            "id": 123,
            "name": "TEST",
            "more_deep": {
                "prop": "first",
                "prop2": "second"
            }
        },
        {
            "id": 222,
            "name": "2222"
        }
    ]
}

Schema to slice

deep_slice(
    obj,
    :id,
    customer: [
        :name
    ],
    array: [
        :name,
        more_deep: [
            :prop2
        ]
    ]
)

Result

{
    :id=>135, 
    :customer=>{
        :name=>"Admin"
    }, 
    :array=>[
        {
         :name=>"TEST", 
         :more_deep=>{
            :prop2=>"second"
         }
        }, 
        {
            :name=>"2222"
        }
    ]
}