Home ruby select inner hash from nested hash
Reply: 2

ruby select inner hash from nested hash

margo
1#
margo Published in 2017-11-10 22:01:02Z

I need to filter a nested hash to return items for a particular combination of attributes. If the attribute is present it returns that hash, if the attribute is not present it returns the default. If the attribute is set to 'none' it returns nothing. Consider the following hash:

{
  "size"=>{
    "default"=>{
      "jeans"=>"boyfriend"
     }, 
   "blue"=>"none"
 }, 
 "style"=>{
   "default"=>{
     "shoes"=>"boots"
    },
   "blue"=>{
     "jeans"=>"jeggings"
    }
  }
}

if the color is 'black', then

{
  "size"=>{
    "jeans"=>"boyfriend"
  }, 
  "style"=>{
    "shoes"=>"boots"
  }
}

or if the color is 'blue', then

{
  "size"=>{
  }, 
  "style"=>{
    "jeans"=>"jeggings"
  }
}

What is best way to do this? I have tried various combinations of select and delete but either end up with an array or a hash with the color key included.

Cary Swoveland
2#
Cary Swoveland Reply to 2017-11-11 01:16:06Z

Letting h be the hash given in the question, the following method will return the desired hash if my understanding of the question is correct.

def doit(h, color)
  h.each_with_object({}) do |(k,f),g|
    c,v = f.find { |kk,_| kk != "default" }
    if c == color
      g[k] = v.is_a?(Hash) ? v : {}
    else
      g[k] = f["default"]
    end
  end
end

doit(h, 'black')
  #=> {"size"=>{"jeans"=>"boyfriend"}, "style"=>{"shoes"=>"boots"}}
doit(h, 'blue')
  #=> {"size"=>{}, "style"=>{"jeans"=>"jeggings"}}

The steps for the second example are as follows.

color = 'blue'

enum = h.each_with_object({})
  #=> #<Enumerator: {"size"=>{"default"=>{"jeans"=>"boyfriend"},
  #     "blue"=>"none"}, "style"=>{"default"=>{"shoes"=>"boots"},
  #     "blue"=>{"jeans"=>"jeggings"}}}:each_with_object({})>

The first value of this enumerator is generated:

x = enum.next
  #=> [["size", {"default"=>{"jeans"=>"boyfriend"}, "blue"=>"none"}], {}]

and passed to the block. The block variables are set equal to x and their values are determined by "disambiguation":

(k,f),g = x
k #=> "size"
f ##=> {"default"=>{"jeans"=>"boyfriend"}, "blue"=>"none"}
g #=> {}

The block calculation is now performed.

c,v = f.find { |kk,_| kk != "default" }
  #=> ["blue", "none"]
c #=> "blue"
v #=> "none"

As

c == color
  #=> "blue" == "blue" => true

we compute

v.is_a?(Hash)
  #=> false

and therefore perform the assignment

g[k] = {}
  #=> {}

so that now

g #=> {"size"=>{}}

The second and last element of h is now generated and passed to the block.

x = enum.next
  #=> [["style", {"default"=>{"shoes"=>"boots"},
  #     "blue"=>{"jeans"=>"jeggings"}}], {"style"=>{"jeans"=>"jeggings"}}]
(k,f),g = x
k #=> "style"
f #=> {"default"=>{"shoes"=>"boots"}, "blue"=>{"jeans"=>"jeggings"}}
g #=> {"size"=>"none"}
c,v = f.find { |kk,_| kk != "default" }
  #=> ["blue", {"jeans"=>"jeggings"}]
c #=> "blue"
v #=> {"jeans"=>"jeggings"}
c == color
  # "blue" == "blue" => true
v.is_a?(Hash)
  #=> true
g[k] = v
  #=> {"jeans"=>"jeggings"}
g #=> {"size"=>"none", "style"=>{"jeans"=>"jeggings"}}

and g is returned.

margo
3#
margo Reply to 2017-11-11 21:16:48Z

Below is what I ended up with after some refactoring. It works and tests all pass. Could do with more refactoring.

class Filterer
  def self.filter(facets, color)
    acc = {}
    facets.each do |k, facets|
      facets.each do |_, facet|
        acc[k] = color_facets(color, facets)  
      end
    end

    acc
  end

  def self.color_facets(color, facets)
    return {} if no_facets?(color, facets)

    facets[color] ? facets[color] : facets['default']
  end

  def self.no_facets?(color, facets)
    facets[color] && facets[color] == 'no facet'
  end
end
You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.370759 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO