简体   繁体   中英

ruby select inner hash from nested hash

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.

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM