views:

244

answers:

2

I need a chunk of Ruby code to combine an array of contents like such:

[{:dim_location=>[{:dim_city=>:dim_state}]},
 :dim_marital_status, 
 {:dim_location=>[:dim_zip, :dim_business]}]

into:

[{:dim_location => [:dim_business, {:dim_city=>:dim_state}, :dim_zip]},
 :dim_marital_status]

It needs to support an arbitrary level of depth, though the depth will rarely be beyond 8 levels deep.

+1  A: 

Revised after comment:

source = [{:dim_location=>[{:dim_city=>:dim_state}]}, :dim_marital_status, {:dim_location=>[:dim_zip, :dim_business]}]

expected = [{:dim_location => [:dim_business, {:dim_city=>:dim_state}, :dim_zip]}, :dim_marital_status]

source2 = [{:dim_location=>{:dim_city=>:dim_state}}, {:dim_location=>:dim_city}]  

def merge_dim_locations(array)
  return array unless array.is_a?(Array)
  values = array.dup
  dim_locations = values.select {|x| x.is_a?(Hash) && x.has_key?(:dim_location)}
  old_index = values.index(dim_locations[0]) unless dim_locations.empty?
  merged = dim_locations.inject({}) do |memo, obj| 
    values.delete(obj)
    x = merge_dim_locations(obj[:dim_location])
    if x.is_a?(Array) 
      memo[:dim_location] = (memo[:dim_location] || []) + x
    else
      memo[:dim_location] ||= []
      memo[:dim_location] << x
    end
    memo
  end
  unless merged.empty?
    values.insert(old_index, merged)
  end
  values
end


puts "source1:"
puts source.inspect
puts "result1:"
puts merge_dim_locations(source).inspect
puts "expected1:"
puts expected.inspect

puts "\nsource2:"
puts source2.inspect
puts "result2:"
puts merge_dim_locations(source2).inspect
dan
That ALMOST works perfectly. However when I feed it the following, it blows up: [{:dim_location=>{:dim_city=>:dim_state}}, {:dim_location=>:dim_city}]
rwl4
OK I tweaked it a little so it doesn't blow up. But what's the result supposed to be after processing this last piece of input?
dan
I am building dynamic queries in Rails that involve different joins that are dependent on which attributes I need. I have a simple hash mapped with attributes as the key, and the associations as the value.If you're curious why I would do such a thing it's because it's a pretty heavy duty amount of data that is stored in a snowflake schema and I created a named_scope that lets me query any dimension I want based on the attribute name.
rwl4
OK, so what should the script produce when the input is `[{:dim_location=>{:dim_city=>:dim_state}}, {:dim_location=>:dim_city}`?
dan
We'd want it to merge it like so: [{:dim_location=>{:dim_city=>:dim_state}}]. And there is a potential that there could be other tree heads as well, beyond dim_location. There could be: [{:dim_favorite => :dim_car }, {:dim_favorite => :dim_city}, {:dim_location => :dim_city}] which would need to return: [{:dim_favorite => [:dim_car, :dim_city], :dim_location => :dim_city}]
rwl4
A: 

I don't think there's enough detail in your question to give you a complete answer, but this might get you started:

class Hash
  def recursive_merge!(other)
    other.keys.each do |k|
      if self[k].is_a?(Array) && other[k].is_a?(Array)
        self[k] += other[k]
      elsif self[k].is_a?(Hash) && other[k].is_a?(Hash)
        self[k].recursive_merge!(other[k])
      else
        self[k] = other[k]
      end
    end
    self
  end
end
glenn mcdonald