开发者

Convert cartesian product to nested hash in ruby

I have a structure with a cartesian product that looks like this (and could go out to arbitrary depth)...

variables = ["var1","var2",...]
myhash = {
   {"var1"=>"a", "var2"=>"a", ...}=>1,
   {"var1"=>"a", "var2"=>"b", ...}=>2,
   {"var1"=>"b", "var2"=>"a", ...}=>3,
   {"var1"=>"b", "var2"=>"b", ...}=>4,
}

... it has a fixed structure but I'd like simple indexing so I开发者_运维知识库'm trying to write a method to convert it to this :

nested = {
   "a"=> { 
     "a"=> 1,
     "b"=> 2
    },
   "b"=> { 
     "a"=> 3,
     "b"=> 4
         }
    }

Any clever ideas (that allow for arbitrary depth)?


Maybe like this (not the cleanest way):

def cartesian_to_map(myhash)
  {}.tap do |hash|
    myhash.each do |h|
      (hash[h[0]["var1"]] ||= {}).merge!({h[0]["var2"] => h[1]})
    end
  end
end

Result:

puts cartesian_to_map(myhash).inspect
{"a"=>{"a"=>1, "b"=>2}, "b"=>{"a"=>3, "b"=>4}}


Here is my example.

It uses a method index(hash, fields) that takes the hash, and the fields you want to index by.

It's dirty, and uses a local variable to pass up the current level in the index.

I bet you can make it much nicer.

def index(hash, fields)
  # store the last index of the fields
  last_field = fields.length - 1

  # our indexed version
  indexed = {}

  hash.each do |key, value|
    # our current point in the indexed hash
    point = indexed
    fields.each_with_index do |field, i|
      key_field = key[field]
      if i == last_field
        point[key_field] = value
      else
        # ensure the next point is a hash
        point[key_field] ||= {}
        # move our point up
        point = point[key_field]
      end
    end
  end
  # return our indexed hash
  indexed
end

You can then just call

index(myhash, ["var1", "var2"])

And it should look like what you want

index({
  {"var1"=>"a", "var2"=>"a"} => 1,
  {"var1"=>"a", "var2"=>"b"} => 2,
  {"var1"=>"b", "var2"=>"a"} => 3,
  {"var1"=>"b", "var2"=>"b"} => 4,
}, ["var1", "var2"])

==

{
  "a"=> { 
    "a"=> 1,
    "b"=> 2
  },
  "b"=> { 
    "a"=> 3,
    "b"=> 4
  }
}

It seems to work. (see it as a gist https://gist.github.com/1126580)


Here's an ugly-but-effective solution:

nested = Hash[ myhash.group_by{ |h,n| h["var1"] } ].tap{ |nested|
  nested.each do |v1,a|
    nested[v1] = a.group_by{ |h,n| h["var2"] }
    nested[v1].each{ |v2,a| nested[v1][v2] = a.flatten.last }
  end
}

p nested
#=> {"a"=>{"a"=>1, "b"=>2}, "b"=>{"a"=>3, "b"=>4}}

You might consider an alternative representation that is easier to map to and (IMO) just as easy to index:

paired = Hash[ myhash.map{ |h,n| [ [h["var1"],h["var2"]], n ] } ]

p paired
#=> {["a", "a"]=>1, ["a", "b"]=>2, ["b", "a"]=>3, ["b", "b"]=>4}

p paired[["a","b"]]
#=> 2
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜