Parslet subtree doesn't fire - ruby

Resume (I shrinked down the following long story to the simple problem)
tree = {:properties => [{:a => 'b'}, {:c => 'd'}]}
big_tree = {:properties => [{:a => 'b'}, {:c => 'd'}], :moves => [{:a => 'b'}, {:c => 'd'}]}
trans = Parslet::Transform.new do
rule(:properties => subtree(:nested)) do
out = {}
nested.each {|pair| out = out.merge pair}
{:properties => out}
end
end
pp tree
pp trans.apply(tree)
pp big_tree
pp trans.apply(big_tree)
# OUTPUT
{:properties=>[{:a=>"b"}, {:c=>"d"}]}
{:properties=>{:a=>"b", :c=>"d"}} # Worked with small tree
{:properties=>[{:a=>"b"}, {:c=>"d"}], :moves=>[{:a=>"b"}, {:c=>"d"}]}
{:properties=>[{:a=>"b"}, {:c=>"d"}], :moves=>[{:a=>"b"}, {:c=>"d"}]} # Didn't work with bigger tree
=========================FULL STORY (Not so relevant after the header)
I am making an SGF files parser with Parslet.
Now I am at the stage to make a Transformer.
From the Parser I already get the structs like that:
[{:properties=>
[{:name=>"GM"#2, :values=>[{:value=>"1"#5}]},
{:name=>"FF"#7, :values=>[{:value=>"4"#10}]},
{:name=>"SZ"#12, :values=>[{:value=>"19"#15}]},
{:name=>"AP"#18, :values=>[{:value=>"SmartGo Kifu:2.2"#21}]},
{:name=>"GN"#40, :values=>[{:value=>"2013-05-11g"#43}]},
{:name=>"PW"#57, :values=>[{:value=>"Dahan"#60}]},
{:name=>"PB"#68, :values=>[{:value=>"SmartGo"#71}]},
{:name=>"DT"#81, :values=>[{:value=>"2013-05-11"#84}]},
{:name=>"KM"#97, :values=>[{:value=>"6.5"#100}]},
{:name=>"RE"#106, :values=>[{:value=>"W+R"#109}]},
{:name=>"RU"#115, :values=>[{:value=>"AGA (Area)"#118}]},
{:name=>"ID"#129, :values=>[{:value=>"ch0"#132}]}],
:moves=>
[{:player=>"B"#137, :place=>"oq"#139},
{:player=>"W"#143, :place=>"dd"#145},
{:player=>"B"#149, :place=>"oo"#151},
...etc...
The ruleset I am using to Transform:
# Rewrite player: COLOR, place: X to COLOR: X
rule( player: simple(:p), place: simple(:pl)) do
if p == 'W'
{ white: pl }
elsif p == 'B'
{ black: pl }
end
end
# Un-nest single-value hash
rule( value: simple(:v)) { v }
# Rewrite name: KEY, values: SINGLE_VALUE to KEY: SINGLE_VALUE
rule( name: simple(:n), values: [ simple(:v) ]) { {n.to_sym => v} }
# A Problem!!!
rule( properties: subtree(:props) ) do
out = {}
props.each {|pair| pair.each {|k, v| out[k] = v}}
{ properties: out }
end
With such rules I get the following struct:
[{:properties=>
[{:GM=>"1"#5},
{:FF=>"4"#10},
{:SZ=>"19"#15},
{:AP=>"SmartGo Kifu:2.2"#21},
{:GN=>"2013-05-11g"#43},
{:PW=>"Dahan"#60},
{:PB=>"SmartGo"#71},
{:DT=>"2013-05-11"#84},
{:KM=>"6.5"#100},
{:RE=>"W+R"#109},
{:RU=>"AGA (Area)"#118},
{:ID=>"ch0"#132}],
:moves=>
[{:black=>"oq"#139},
{:white=>"dd"#145},
{:black=>"oo"#151},
...etc...
Everything is perfect.
The only Problem of mine is that :properties Array of Hashes.
In the end I want to have
[{:properties=>
{:GM=>"1"#5,
:FF=>"4"#10,
:SZ=>"19"#15,
:AP=>"SmartGo Kifu:2.2"#21,
:GN=>"2013-05-11g"#43,
:PW=>"Dahan"#60,
:PB=>"SmartGo"#71,
:DT=>"2013-05-11"#84,
:KM=>"6.5"#100,
:RE=>"W+R"#109,
:RU=>"AGA (Area)"#118,
:ID=>"ch0"#132},
:moves=>
[{:black=>"oq"#139},
{:white=>"dd"#145},
{:black=>"oo"#151},
...etc...
You see? Merge all arrayed hashes inside :properties, because after the previous transformations they now have unique keys. Also flatten the struct a bit.
Hey! I can do it manually. I mean to run a separate method like
merged_stuff = {}
tree.first[:properties].each {|pair| pair.each {|k, v| merged_stuff[k] = v}}
tree.first[:properties] = merged_stuff
But Why I Cannot Do That With The Neat Transform Rules, To Have All Transformation Logic In One Place?
The point is that rule( properties: subtree(:props) ) does not get fired at all. Even if I just return nil from the block, it doesn't change anything. So, seems, that this subtree doesn't catch the things, or I don't.

The problem is that :properties and :moves are keys in the same hash, and subtree apparently doesn't want to match part of a hash. If you remove :moves, the rule will be executed. It is kinda explained in the documentation:
A word on patterns
Given the PORO hash
{
:dog => 'terrier',
:cat => 'suit' }
one might assume that the following rule matches :dog and replaces it by 'foo':
rule(:dog => 'terrier') { 'foo' }
This is frankly impossible. How would 'foo' live besides :cat => 'suit'
inside the hash? It cannot. This is why hashes are either matched completely,
cats n’ all, or not at all.
though I must admit it's not a really clear example.
So the problem rule should look like this:
rule( properties: subtree(:props), moves: subtree(:m) ) do
out = {}
props.each {|pair| pair.each {|k, v| out[k] = v}}
{ properties: out , moves: m}
end

Transform rules match a whole node and replace it, so you need to match the whole hash, not just one key.
rule( properties: subtree(:props), moves: subtree(:moves) )
If you converted the {:name=>"GM", :values=>[{:value=>"1"}]} type things into objects (using OpenStruct say) then you don't need to use subtree, you can use sequence.

Related

how to merge hash of hash with same keys in ruby [duplicate]

I would like to merge a nested hash.
a = {:book=>
[{:title=>"Hamlet",
:author=>"William Shakespeare"
}]}
b = {:book=>
[{:title=>"Pride and Prejudice",
:author=>"Jane Austen"
}]}
I would like the merge to be:
{:book=>
[{:title=>"Hamlet",
:author=>"William Shakespeare"},
{:title=>"Pride and Prejudice",
:author=>"Jane Austen"}]}
What is the nest way to accomplish this?
For rails 3.0.0+ or higher version there is the deep_merge function for ActiveSupport that does exactly what you ask for.
I found a more generic deep-merge algorithm here, and used it like so:
class ::Hash
def deep_merge(second)
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
self.merge(second, &merger)
end
end
a.deep_merge(b)
To add on to Jon M and koendc's answers, the below code will handle merges of hashes, and :nil as above, but it will also union any arrays that are present in both hashes (with the same key):
class ::Hash
def deep_merge(second)
merger = proc { |_, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : Array === v1 && Array === v2 ? v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }
merge(second.to_h, &merger)
end
end
a.deep_merge(b)
For variety's sake - and this will only work if you want to merge all the keys in your hash in the same way - you could do this:
a.merge(b) { |k, x, y| x + y }
When you pass a block to Hash#merge, k is the key being merged, where the key exists in both a and b, x is the value of a[k] and y is the value of b[k]. The result of the block becomes the value in the merged hash for key k.
I think in your specific case though, nkm's answer is better.
A little late to answer your question, but I wrote a fairly rich deep merge utility awhile back that is now maintained by Daniel Deleo on Github: https://github.com/danielsdeleo/deep_merge
It will merge your arrays exactly as you want. From the first example in the docs:
So if you have two hashes like this:
source = {:x => [1,2,3], :y => 2}
dest = {:x => [4,5,'6'], :y => [7,8,9]}
dest.deep_merge!(source)
Results: {:x => [1,2,3,4,5,'6'], :y => 2}
It won't merge :y (because int and array aren't considered mergeable) - using the bang (!) syntax causes the source to overwrite.. Using the non-bang method will leave dest's internal values alone when an unmergeable entity is found. It will add the arrays contained in :x together because it knows how to merge arrays. It handles arbitrarily deep merging of hashes containing whatever data structures.
Lots more docs on Daniel's github repo now..
All answers look to me overcomplicated. Here's what I came up with eventually:
# #param tgt [Hash] target hash that we will be **altering**
# #param src [Hash] read from this source hash
# #return the modified target hash
# #note this one does not merge Arrays
def self.deep_merge!(tgt_hash, src_hash)
tgt_hash.merge!(src_hash) { |key, oldval, newval|
if oldval.kind_of?(Hash) && newval.kind_of?(Hash)
deep_merge!(oldval, newval)
else
newval
end
}
end
P.S. use as public, WTFPL or whatever license
Here is even better solution for recursive merging that uses refinements and has bang method alongside with block support. This code does work on pure Ruby.
module HashRecursive
refine Hash do
def merge(other_hash, recursive=false, &block)
if recursive
block_actual = Proc.new {|key, oldval, newval|
newval = block.call(key, oldval, newval) if block_given?
[oldval, newval].all? {|v| v.is_a?(Hash)} ? oldval.merge(newval, &block_actual) : newval
}
self.merge(other_hash, &block_actual)
else
super(other_hash, &block)
end
end
def merge!(other_hash, recursive=false, &block)
if recursive
self.replace(self.merge(other_hash, recursive, &block))
else
super(other_hash, &block)
end
end
end
end
using HashRecursive
After using HashRecursive was executed you can use default Hash::merge and Hash::merge! as if they haven't been modified. You can use blocks with these methods as before.
The new thing is that you can pass boolean recursive (second argument) to these modified methods and they will merge hashes recursively.
Example for simple usage is written at this answer. Here is an advanced example.
The example in this question is bad because it got nothing to do with recursive merging. Following line would meet question's example:
a.merge!(b) {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
Let me give you a better example to show the power of the code above. Imagine two rooms, each have one bookshelf in it. There are 3 rows on each bookshelf and each bookshelf currently have 2 books. Code:
room1 = {
:shelf => {
:row1 => [
{
:title => "Hamlet",
:author => "William Shakespeare"
}
],
:row2 => [
{
:title => "Pride and Prejudice",
:author => "Jane Austen"
}
]
}
}
room2 = {
:shelf => {
:row2 => [
{
:title => "The Great Gatsby",
:author => "F. Scott Fitzgerald"
}
],
:row3 => [
{
:title => "Catastrophe Theory",
:author => "V. I. Arnol'd"
}
]
}
}
We are going to move books from the shelf in the second room to the same rows on the shelf in the first room. First we will do this without setting recursive flag, i.e. same as using unmodified Hash::merge!:
room1.merge!(room2) {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
puts room1
The output will tell us that the shelf in the first room would look like this:
room1 = {
:shelf => {
:row2 => [
{
:title => "The Great Gatsby",
:author => "F. Scott Fitzgerald"
}
],
:row3 => [
{
:title => "Catastrophe Theory",
:author => "V. I. Arnol'd"
}
]
}
}
As you can see, not having recursive forced us to throw out our precious books.
Now we will do the same thing but with setting recursive flag to true. You can pass as second argument either recursive=true or just true:
room1.merge!(room2, true) {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
puts room1
Now the output will tell us that we actually moved our books:
room1 = {
:shelf => {
:row1 => [
{
:title => "Hamlet",
:author => "William Shakespeare"
}
],
:row2 => [
{
:title => "Pride and Prejudice",
:author => "Jane Austen"
},
{
:title => "The Great Gatsby",
:author => "F. Scott Fitzgerald"
}
],
:row3 => [
{
:title => "Catastrophe Theory",
:author => "V. I. Arnol'd"
}
]
}
}
That last execution could be rewritten as following:
room1 = room1.merge(room2, recursive=true) do |k, v1, v2|
if v1.is_a?(Array) && v2.is_a?(Array)
v1+v2
else
v2
end
end
puts room1
or
block = Proc.new {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
room1.merge!(room2, recursive=true, &block)
puts room1
That's it. Also take a look at my recursive version of Hash::each(Hash::each_pair) here.
I think Jon M's answer is the best, but it fails when you merge in a hash with a nil/undefined value.
This update solves the issue:
class ::Hash
def deep_merge(second)
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }
self.merge(second, &merger)
end
end
a.deep_merge(b)
a[:book] = a[:book] + b[:book]
Or
a[:book] << b[:book].first

what is difference between store vs merge in ruby hashes?

i create a hash:
a = {}
=> {}
then:
a.store(:b, {})
=> {}
and:
a.merge!(c: {})
=> {:b=>{}, :c=>{}}
what are differences actually?
store is an assignment method.
a = {}
# => {}
a.store(:b, {})
a
# => {:b=>{}}
# Here you are assigning a key :b with empty hash {}
Another example to make it clearer:
a = {}
# => {}
a.store("key", "value")
a
# => {"key"=>"value"}
merge on the other hand manipulates your existing hash by merging with a different hash.
Example:
a = {}
# => {}
a.merge({"key" => "value"})
# => {"key"=>"value"}
a
# => {} # original value still unchanged
a.merge!({"key" => "value"})
# => {"key"=>"value"}
a
# => {"key"=>"value"} # original value updated
However unless you use merge! a's value will not get changed i.e. merge will occur only for return.
what are differences actually?
I think the main difference is merge! will let you decide which value to keep when duplicate key is provided, since it expects a block as well.
On the other hand, when you use store, the previous value will be replaced by the latest value when duplicate key is provided.
store
h1 = { "a" => 100, "b" => 200 }
h1.store("b", 254)
#=> {"a"=>100, "b"=>254}
merge!
h1 = { "a" => 100, "b" => 200 }
h2 = { "b" => 254, "c" => 300 }
h1.merge!(h2) { |key, v1, v2| v1 }
#=> {"a"=>100, "b"=>200, "c"=>300}
store takes just one key/value tuple as input and returns the stored value.
h1 = { foo: 'bar' }
h1.store(:baz, 1) #=> 1
h1 #=> { foo: 'bar', baz: 1 }
Whereas merge! accepts a hash as input and returns the updated hash:
h2 = { foo: 'bar' }
h2.merge!(baz: 1, buz: 2) #=> { foo: 'bar', baz: 1, buz: 2 }
h2 #=> { foo: 'bar', baz: 1, buz: 2 }
merge! takes one argument, which is hash to merge into original. store takes two arguments, which is key and value to store. Therefore, with merge!, you can add multiple keys to original hash, like this:
a = {}
a.merge!(a: 'a', b: 'b')
a
# => {:a => "a", :b => "b"}
For a hash h, Hash#store has the same effect as Hash#[]=: they both either add one key-value pair k=>v to h (if h does not have a key k) or modify the value of key k (if the hash already contains the key). Also, they both return v.
Hash#merge! (aka update) has two forms. The first does the same thing as store, except it does it for each key-value pair in another hash. The second form uses a block to determine the values of keys that are present in both hashes being merged. Please refer to the docs for details on that form of the method. Both forms of merge! return the "merged" hash.
Hash#merge is not a relevant comparison as it does not mutate the hash.

Search one hash for its keys, grab the values, put them in an array and merge the first hash with the second

I have a hash with items like
{'people'=>'50'},
{'chairs'=>'23'},
{'footballs'=>'5'},
{'crayons'=>'1'},
and I have another hash with
{'people'=>'http://www.thing.com/this-post'},
{'footballs'=>'http://www.thing.com/that-post'},
{'people'=>'http://www.thing.com/nice-post'},
{'footballs'=>'http://www.thing.com/other-post'},
{'people'=>'http://www.thing.com/thingy-post'},
{'footballs'=>'http://www.thing.com/the-post'},
{'people'=>'http://www.thing.com/the-post'},
{'crayons'=>'http://www.thing.com/the-blah'},
{'chairs'=>'http://www.thing.com/the-page'},
and I want something like the following that takes the first hash and then looks through the second one, grabs all the links for each word and puts them into a array appended onto the end of the hash somehow.
{'people', '50' => {'http://www.thing.com/this-post', 'http://www.thing.com/nice-post', 'http://www.thing.com/thingy-post'}},
{'footballs', '5' => {'http://www.thing.com/the-post', 'http://www.thing.com/the-post'}},
{'crayons', '1' => {'http://www.thing.com/the-blah'}},
{'chairs', '23' => {'chairs'=>'http://www.thing.com/the-page'}},
I am very new to Ruby, and I have tried quite a few combinations, and I need some help.
Excuse the example, I hope that it makes sense.
What you have is a mix of hashes, arrays, and something in the middle. I'm going to assume the following inputs:
categories = {'people'=>'50',
'chairs'=>'23',
'footballs'=>'5',
'crayons'=>'1'}
and:
posts = [['people', 'http://www.thing.com/this-post'],
['footballs','http://www.thing.com/that-post'],
['people','http://www.thing.com/nice-post'],
['footballs','http://www.thing.com/other-post'],
['people','http://www.thing.com/thingy-post'],
['footballs','http://www.thing.com/the-post'],
['people','http://www.thing.com/the-post'],
['crayons','http://www.thing.com/the-blah'],
['chairs','http://www.thing.com/the-page']]
and the following output:
[['people', '50', ['http://www.thing.com/this-post', 'http://www.thing.com/nice-post', 'http://www.thing.com/thingy-post']],
[['footballs', '5', ['http://www.thing.com/the-post', 'http://www.thing.com/the-post']],
['crayons', '1', ['http://www.thing.com/the-blah']],
['chairs', '23' => {'chairs'=>'http://www.thing.com/the-page']]]
In which case what you would need is:
categories.map do |name, count|
[name, count, posts.select do |category, _|
category == name
end.map { |_, post| post }]
end
You need to understand the different syntax for Array and Hash in Ruby:
Hash:
{ 'key1' => 'value1',
'key2' => 'value2' }
Array:
[ 'item1', 'item2', 'item3', 'item4' ]
A Hash in ruby (like in every other language) can't have more than once instance of any single key, meaning that a Hash {'key1' => 1, 'key1' => 2} is invalid and will result in an unexpected value (duplicate keys are overridden - you'll have {'key1' => 2 }).
Since there is some confusion about the format of the data, I will suggest how you might effectively structure both the input and the output. I will first present some code you could use, then give an example of how it's used, then explain what is happening.
Code
def merge_em(hash, array)
hash_keys = hash.keys
new_hash = hash_keys.each_with_object({}) { |k,h|
h[k] = { qty: hash[k], http: [] } }
array.each do |h|
h.keys.each do |k|
(new_hash.update({k=>h[k]}) { |k,g,http|
{qty: g[:qty], http: (g[:http] << http)}}) if hash_keys.include?(k)
end
end
new_hash
end
Example
Here is a hash that I have modified to include a key/value pair that does not appear in the array below:
hash = {'people' =>'50', 'chairs' =>'23', 'footballs'=>'5',
'crayons'=> '1', 'cat_lives'=> '9'}
Below is your array of hashes that is to be merged into hash. You'll see I've added a key/value pair to your hash with key "chairs". As I hope to make clear, the code is no different (i.e., not simplified) if we know in advance that each hash has only one key value pair. (Aside: if, for example, we want want the key from a hash h that is known to have only one key, we still have to pull out all the keys into an array and then take the only element of the array: h.keys.first).
I have also added a hash to the array that has no key that is among hash's keys.
array =
[{'people' =>'http://www.thing.com/this-post'},
{'footballs'=>'http://www.thing.com/that-post'},
{'people' =>'http://www.thing.com/nice-post'},
{'footballs'=>'http://www.thing.com/other-post'},
{'people' =>'http://www.thing.com/thingy-post'},
{'footballs'=>'http://www.thing.com/the-post'},
{'people' =>'http://www.thing.com/the-post'},
{'crayons' =>'http://www.thing.com/the-blah'},
{'chairs' =>'http://www.thing.com/the-page',
'crayons' =>'http://www.thing.com/blah'},
{'balloons' =>'http://www.thing.com/the-page'}
]
We now merge the information from array into hash, and at the same time change the structure of hash to something more suitable:
result = merge_em(hash, array)
#=> {"people" =>{:qty=>"50",
# :http=>["http://www.thing.com/this-post",
# "http://www.thing.com/nice-post",
# "http://www.thing.com/thingy-post",
# "http://www.thing.com/the-post"]},
# "chairs" =>{:qty=>"23",
# :http=>["http://www.thing.com/the-page"]},
# "footballs"=>{:qty=>"5",
# :http=>["http://www.thing.com/that-post",
# "http://www.thing.com/other-post",
# "http://www.thing.com/the-post"]},
# "crayons" =>{:qty=>"1",
# :http=>["http://www.thing.com/the-blah",
# "http://www.thing.com/blah"]},
# "cat_lives"=>{:qty=>"9",
# :http=>[]}}
I've assumed you want to look up the content of result with hash's keys. It is therefore convenient to make the values associated with those keys hashes themselves, with keys :qty and http. The former is for the values in hash (the naming may be wrong); the latter is an array containing the strings drawn from array.
This way, if we want the value for the key "crayons", we could write:
result["crayons"]
#=> {:qty=>"1",
# :http=>["http://www.thing.com/the-blah", "http://www.thing.com/blah"]}
or
irb(main):133:0> result["crayons"][:qty]
#=> "1"
irb(main):134:0> result["crayons"][:http]
#=> ["http://www.thing.com/the-blah", "http://www.thing.com/blah"]
Explanation
Let's go through this line-by-line. First, we need to reference hash.keys more than once, so let's make it a variable:
hash_keys = hash.keys
#=> ["people", "chairs", "footballs", "crayons", "cat_lives"]
We may as well convert this hash to the output format now. We could do it during the merge operation below, but I think is clearer to do it as a separate step:
new_hash = hash_keys.each_with_object({}) { |k,h|
h[k] = { qty: hash[k], http: [] } }
#=> {"people" =>{:qty=>"50", :http=>[]},
# "chairs" =>{:qty=>"23", :http=>[]},
# "footballs"=>{:qty=>"5", :http=>[]},
# "crayons" =>{:qty=>"1", :http=>[]},
# "cat_lives"=>{:qty=>"9", :http=>[]}}
Now we merge each (hash) element of array into new_hash:
array.each do |h|
h.keys.each do |k|
(new_hash.update({k=>h[k]}) { |k,g,http|
{ qty: g[:qty], http: (g[:http] << http) } }) if hash_keys.include?(k)
end
end
The first hash h from array that is passed into the block by each is:
{'people'=>'http://www.thing.com/this-post'}
which is assigned to the block variable h. We next construct an array of h's keys:
h.keys #=> ["people"]
The first of these keys, "people" (pretend there were more, as there would be in the penultimate element of array) is passed by its each into the inner block, whose block variable, k, is assigned the value "people". We then use Hash#update (aka merge!) to merge the hash:
{k=>h[k]} #=> {"people"=>'http://www.thing.com/this-post'}
into new_hash, but only because:
hash_keys.include?(k)
#=> ["people", "chairs", "footballs", "crayons", "cat_lives"].include?("people")
#=> true
evaluates to true. Note that this will evaluate to false for the key "balloons", so the hash in array with that key will not be merged. update's block:
{ |k,g,http| { qty: g[:qty], http: (g[:http] << http) } }
is crucial. This is update's way of determining the value of a key that is in both new_hash and in the hash being merged, {k=>h[k]}. The three block variables are assigned the following values by update:
k : the key ("people")
g : the current value of `new_hash[k]`
#=> `new_hash["people"] => {:qty=>"50", :http=>[]}`
http: the value of the key/value being merged
#=> 'http://www.thing.com/this-post'
We want the merged hash value for key "people" to be:
{ qty: g[:qty], http: (g[:http] << http) }
#=> { qty: 50, http: ([] << 'http://www.thing.com/this-post') }
#=> { qty: 50, http: ['http://www.thing.com/this-post'] }
so now:
new_hash
#=> {"people" =>{:qty=>"50", :http=>['http://www.thing.com/this-post']},
# "chairs" =>{:qty=>"23", :http=>[]},
# "footballs"=>{:qty=>"5", :http=>[]},
# "crayons" =>{:qty=>"1", :http=>[]},
# "cat_lives"=>{:qty=>"9", :http=>[]}}
We do the same for each of the other elements of array.
Lastly, we need to return the merged new_hash, so we make last line of the method:
new_hash
You could also do this
cat = [{'people'=>'50'},
{'chairs'=>'23'},
{'footballs'=>'5'},
{'crayons'=>'1'}]
pages = [{'people'=>'http://www.thing.com/this-post'},
{'footballs'=>'http://www.thing.com/that-post'},
{'people'=>'http://www.thing.com/nice-post'},
{'footballs'=>'http://www.thing.com/other-post'},
{'people'=>'http://www.thing.com/thingy-post'},
{'footballs'=>'http://www.thing.com/the-post'},
{'people'=>'http://www.thing.com/the-post'},
{'crayons'=>'http://www.thing.com/the-blah'},
{'chairs'=>'http://www.thing.com/the-page'}]
cat.map do |c|
c.merge(Hash['pages',pages.collect{|h| h[c.keys.pop]}.compact])
end
#=> [{"people"=>"50", "pages"=>["http://www.thing.com/this-post", "http://www.thing.com/nice-post", "http://www.thing.com/thingy-post", "http://www.thing.com/the-post"]},
{"chairs"=>"23", "pages"=>["http://www.thing.com/the-page"]},
{"footballs"=>"5", "pages"=>["http://www.thing.com/that-post", "http://www.thing.com/other-post", "http://www.thing.com/the-post"]},
{"crayons"=>"1", "pages"=>["http://www.thing.com/the-blah"]}]
Which is closer to your request but far less usable than some of the other posts.

Ruby - Iterating over a Nested Hash and counting values

Despite there being many questions on nested hashes I've not found any solutions to my problem.
I'm pulling in strings and matching each character to a hash like so:
numberOfChars = {}
string.each_char do |c|
RULES.each do |key, value|
if c === key
numberOfChars[value] += 1
end
end
end
This worked fine and would output something like "a appears 3 times" until I realised that my hash needed to be nested, akin to this:
RULES = {
:limb {
:colour {
'a' => 'foo',
'b' => 'bar'
},
'c' => 'baz'
}
}
So how would I go about getting the 'leaf' key and it's value?
While it's iterating over the hash it also needs to count how many times each key appears, e.g. does 'a' appear more than 'b'? If so add a to new hash. But I'm pretty lost as to how that would work in practice without knowing how it'll be iterating over a nested hash to begin with.
It just seems to me an overly convoluted way of doing this but if anyone's got any pointers they'd be hugely appreciated!
Also, if it's not painfully clear already I'm new to Ruby so I'm probably making some fundamental mistakes.
Are you looking for something like this?
RULES = {
:limb => {
:colour => {
'a' => 'foo',
'b' => 'bar'
},
'c' => 'baz',
:color => {
'b' => 'baz'
}
}
}
def count_letters(hash, results = {})
hash.each do |key, value|
if value.kind_of?(Hash)
count_letters(value, results)
else
results[key] = (results[key] || 0) + 1
end
end
results
end
p count_letters(RULES)

Bidirectional Hash table in Ruby

I need a bidirectional Hash table in Ruby. For example:
h = {:abc => 123, :xyz => 789, :qaz => 789, :wsx => [888, 999]}
h.fetch(:xyz) # => 789
h.rfetch(123) # => abc
h.rfetch(789) # => [:xyz, :qaz]
h.rfetch(888) # => :wsx
Method rfetch means reversed fetch and is only my proposal.
Note three things:
If multiple keys map at the same value then rfetch returns all of them, packed in array.
If value is an array then rfetch looks for its param among elements of the array.
Bidirectional Hash means that both fetch and rfetch should execute in constant time.
Does such structure exists in Ruby (including external libraries)?
I thought about implementing it using two one-directional Hashes synchronized when one of them is modified (and packing it into class to avoid synchronization problems) but maybe I could use an already existing solution?
You could build something yourself pretty easily, just use a simple object that wraps two hashes (one for the forward direction, one for the reverse). For example:
class BiHash
def initialize
#forward = Hash.new { |h, k| h[k] = [ ] }
#reverse = Hash.new { |h, k| h[k] = [ ] }
end
def insert(k, v)
#forward[k].push(v)
#reverse[v].push(k)
v
end
def fetch(k)
fetch_from(#forward, k)
end
def rfetch(v)
fetch_from(#reverse, v)
end
protected
def fetch_from(h, k)
return nil if(!h.has_key?(k))
v = h[k]
v.length == 1 ? v.first : v.dup
end
end
Look ups will behave just like normal hash lookups (because they are normal hash lookups). Add some operators and maybe decent to_s and inspect implementations and you're good.
Such a thing works like this:
b = BiHash.new
b.insert(:a, 'a')
b.insert(:a, 'b')
b.insert(:a, 'c')
b.insert(:b, 'a')
b.insert(:c, 'x')
puts b.fetch(:a).inspect # ["a", "b", "c"]
puts b.fetch(:b).inspect # "a"
puts b.rfetch('a').inspect # [:a, :b]
puts b.rfetch('x').inspect # :c
puts b.fetch(:not_there).inspect # nil
puts b.rfetch('not there').inspect # nil
There's nothing wrong with building your tools when you need them.
There is no such structure built-in in Ruby.
Note that Hash#rassoc does something similar, but it returns only the first match and is linear-time:
h = {:abc => 123, :xyz => 789, :qaz => 789, :wsx => [888, 999]}
h.rassoc(123) # => [:abc, 123]
Also, it isn't possible to fullfill your requirements in Ruby in a perfectly safe manner, as you won't be able to detect changes in values that are arrays. E.g.:
h = MyBidirectionalArray.new(:foo => 42, :bar => [:hello, :world])
h.rfetch(:world) # => :bar
h[:bar].shift
h[:bar] # => [:world]
h.rfetch(:world) # => should be nil, but how to detect this??
Computing a hash everytime to detect a change will make your lookup linear-time. You could duplicate the array-values and freeze them, though (like Ruby does for Hash keys that are strings!)
What you seem to need is a Graph class, which could have a different API than a Hash, no? You can check out rgl or similar, but I don't know how they're implemented.
Good luck.
There is a Hash#invert method (http://www.ruby-doc.org/core-2.1.0/Hash.html#method-i-invert) to achieve this. It won't map multiple values to an array though.
Try this:
class Hash
def rfetch val
select { |k,v| v.is_a?(Array) ? v.include?(val) : v == val }.map { |x| x[0] }
end
end
If you're not doing lots of updates to this hash, you might be able to use inverthash.

Resources