print out Hash key and value pairs recursively - ruby

I am trying to define a function that it can print out any hash values in a tree format. The function will do something like this:
From
{"parent1"=>
{"child1" => { "grandchild1" => 1,
"grandchild2" => 2},
"child2" => { "grandchild3" => 3,
"grandchild4" => 4}}
}
To
parent1:
child1:
grandchild1:1
grandchild2:2
child2:
grandchild3:3
grandchild4:4
And this is my code so far:
def readprop(foo)
level = ''
if foo.is_a?(Hash)
foo.each_key {|key| if foo[key].nil? == false
puts level + key + ":"
level += " "
readprop(foo[key])
end
}
else
puts level + foo
level = level[0,level.length - 2]
end
end
and it will give me a bad format like this:
parent1:
child1:
grandchild1:
1
grandchild2:
2
child2:
grandchild3:
3
grandchild4:
4

You are almost there. One way to solve it is to make level a part of the recursive function parameters. x is the hash in the question.
Simple recursive version:
def print_hash(h,spaces=4,level=0)
h.each do |key,val|
format = "#{' '*spaces*level}#{key}: "
if val.is_a? Hash
puts format
print_hash(val,spaces,level+1)
else
puts format + val.to_s
end
end
end
print_hash(x)
#parent1:
# child1:
# grandchild1: 1
# grandchild2: 2
# child2:
# grandchild3: 3
# grandchild4: 4
In this case you could also convert it to YAML (as mentioned in a comment above)
require 'YAML'
puts x.to_yaml
#---
#parent1:
# child1:
# grandchild1: 1
# grandchild2: 2
# child2:
# grandchild3: 3
# grandchild4: 4

I would use recursion, but there is another way that might be of interest to some. Below I've used a "pretty printer", awesome-print, to do part of the formatting (the indentation in particular), saving the result to a string, and then applied a couple of gsub's to the string to massage the results into the desired format.
Suppose your hash were as follows:
h = { "parent1"=>
{ "child1" => { "grandchild11" => 1,
"grandchild12" => { "great grandchild121" => 3 } },
"child2" => { "grandchild21" => { "great grandchild211" =>
{ "great great grandchild2111" => 4 } },
"grandchild22" => 2 }
}
}
We could then do the following.
require 'awesome_print'
puts str = h.awesome_inspect(indent: -5, index: false, plain: true).
gsub(/^\s*(?:{|},?)\s*\n|[\"{}]/, '').
gsub(/\s*=>\s/, ':')
prints
parent1:
child1:
grandchild11:1,
grandchild12:
great grandchild121:3
child2:
grandchild21:
great grandchild211:
great great grandchild2111:4
grandchild22:2
The steps:
str = h.awesome_inspect(indent: -5, index: false, plain: true)
puts str prints
{
"parent1" => {
"child1" => {
"grandchild11" => 1,
"grandchild12" => {
"great grandchild121" => 3
}
},
"child2" => {
"grandchild21" => {
"great grandchild211" => {
"great great grandchild2111" => 4
}
},
"grandchild22" => 2
}
}
}
s1 = str.gsub(/^\s*(?:{|},?)\s*\n|[\"{}]/, '')
puts s1 prints
parent1 =>
child1 =>
grandchild11 => 1,
grandchild12 =>
great grandchild121 => 3
child2 =>
grandchild21 =>
great grandchild211 =>
great great grandchild2111 => 4
grandchild22 => 2
s2 = s1.gsub(/\s*=>\s/, ':')
puts s2 prints the result above.

Not exactly what you require but I will submit this answer as I think you may find it useful:
require 'yaml'
hash = {"parent1"=> {"child1" => { "grandchild1" => 1,"grandchild2" => 2},
"child2" => { "grandchild3" => 3,"grandchild4" => 4}}}
puts hash.to_yaml
prints:
---
parent1:
child1:
grandchild1: 1
grandchild2: 2
child2:
grandchild3: 3
grandchild4: 4

See Ruby Recursive Tree
Suppose we have
#$ mkdir -p foo/bar
#$ mkdir -p baz/boo/bee
#$ mkdir -p baz/goo
We can get
{
"baz"=>{
"boo"=>{
"bee"=>{}},
"goo"=>{}},
"foo"=>{
"bar"=>{}}}
We can traverse the tree as the following. So, here's a way to make a Hash based on directory tree on disk:
Dir.glob('**/*'). # get all files below current dir
select{|f|
File.directory?(f) # only directories we need
}.map{|path|
path.split '/' # split to parts
}.inject({}){|acc, path| # start with empty hash
path.inject(acc) do |acc2,dir| # for each path part, create a child of current node
acc2[dir] ||= {} # and pass it as new current node
end
acc
}
Thanks to Mladen Jablanović in the other answer for this concept.

Related

Transform array of nested Hashes into flat array of non-nested hashes

I want to transform the given array into result array:
given = [{
"foo_v1_4" => [{
"derivate_version" => 0,
"layers" => {
"tlayer" => {
"baz" => {
"three" => 0.65
},
"bazbar" => {
"three" => 0.65
}
}
}
}]
}]
# the value of key :one is first hash key (foo_v1_4) plus underscore (_) plus derivate_version (0)
result = [{
one: 'foo_v1_4_0',
tlayer: 'baz',
three: '0.6'
},
{
one: 'foo_v1_4_0',
tlayer: 'bazbar',
three: '0.6'
}
]
What I tried:
given.each do |el |
el.each do |derivat |
derivat.each do |d |
d.each do |layer |
layer.each do |l |
derivat = "#{d}_#{l['derivate_version']}"
puts derivat
end
end
end
end
end
I'm struggling at iterating through "layers" hash, the amount of elements in layers is equal to the amount of elements in result array.
It helps to format the objects so we can better see their structures:
given = [
{
"foo_v1_4" => [
{ "derivate_version" => 0,
"layers" => {
"tlayer" => {
"baz" => { "three" => 0.65 },
"bazbar" => { "three" => 0.65 }
}
}
}
]
}
]
result = [
{
one: 'foo_v1_4_0',
tlayer: 'baz',
three: '0.6'
},
{
one: 'foo_v1_4_0',
tlayer: 'bazbar',
three: '0.6'
}
]
We can begin by writing the structure of result:
result = [
{
one:
tlayer:
three:
},
{
one:
tlayer:
three:
}
]
We see that
given = [ { "foo_v1_4" => <array> } ]
The values of the keys :one in the hash result[0] is therefore the first key of the first element of given:
one_val = given[0].keys[0]
#=> "foo_v1_4"
result = [
{
one: one_val
tlayer:
three:
},
{
one: one_val
tlayer:
three:
}
]
All the remaining objects of interest are contained in the hash
h = given[0]["foo_v1_4"][0]["layers"]["layer"]
#=> {
# "baz"=>{ "three"=>0.65 },
# "bazbar"=>{ "three"=>0.65 }
# }
so it is convenient to define it. We see that:
h.keys[0]
#=> "baz"
h.keys[1]
#=> "bazaar"
h["bazbar"]["three"]
#=> 0.65
Note that it generally is not good practice to assume that hash keys are ordered in a particular way.
We may now complete the construction of result,
v = h["bazbar"]["three"].truncate(1)
#=> 0.6
result = [
{
one: one_val,
tlayer: h.keys[0],
three: v
},
{ one: one_val,
tlayer: h.keys[1],
three: v
}
]
#=> [
# { :one=>"foo_v1_4", :tlayer=>"baz", :three=>0.6 },
# { :one=>"foo_v1_4", :tlayer=>"bazbar", :three=>0.6 }
# ]
The creation of the temporary objects one_val, h, and v improves time- and space-efficiency, makes the calculations easier to test and improves the readability of the code.
Try the below:
result = []
given.each do |level1|
level1.each do |key, derivate_versions|
derivate_versions.each do |layers|
# iterate over the elements under tlayer
layers.dig('layers', 'tlayer').each do |tlayer_key, tlayer_value|
sub_result = {}
# key - foo_v1_4, layers['derivate_version'] - 0 => 'foo_v1_4_0'
sub_result[:one] = key + '_' + layers['derivate_version'].to_s
# talyer_key - baz, barbaz
sub_result[:tlayer] = tlayer_key
# talyer_value - { "three" => 0.65 }
sub_result[:three] = tlayer_value['three']
result << sub_result
end
end
end
end
The value of result will be:
2.6.3 :084 > p result
[{:one=>"foo_v1_4_0", :tlayer=>"baz", :three=>0.65}, {:one=>"foo_v1_4_0", :tlayer=>"bazbar", :three=>0.65}]

compare array of hashes and print expected & actual results

I have 2 array of hashes:
actual = [{"column_name"=>"NONINTERESTINCOME", "column_data_type"=>"NUMBER"},
{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"VARCHAR"},
{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"TIMESTAMP"},
{"column_name"=>"UPDATEDATE", "column_data_type"=>"TIMESTAMP"}]
expected = [{"column_name"=>"NONINTERESTINCOME", "column_data_type"=>"NUMBER"},
{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"NUMBER"},
{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"NUMBER"},
{"column_name"=>"UPDATEDATE", "column_data_type"=>"TIMESTAMP"}]
I need to compare these 2 hashes and find out the ones for which the column_data_type differs.
to compare we can directly use:
diff = actual - expected
This will print the output as:
{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"VARCHAR"}
{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"TIMESTAMP"}
My expected output is that in the result i want to print the actual and expected datatype, means the datatypes for the missing `column_name' from both the actual and expected array of hashes, something like:
{"column_name"=>"NONINTERESTEXPENSE", "expected_column_data_type"=>"NUMBER", "actual_column_data_type" => "VARCHAR"}
{"column_name"=>"TRANSACTIONDATE", "expected_column_data_type"=>"NUMBER","actual_column_data_type" => "TIMESTAMP" }
This will work irrespective of order of hashes in your array.
diff = []
expected.each do |elem|
column_name = elem['column_name']
column_type = elem['column_data_type']
match = actual.detect { |elem2| elem2['column_name'] == column_name }
if column_type != match['column_data_type']
diff << { 'column_name' => column_name,
'expected_column_data_type' => column_type,
'actual_column_data_type' => match['column_data_type'] }
end
end
p diff
[actual, expected].map { |a| a.map(&:dup).map(&:values) }
.map(&Hash.method(:[]))
.reduce do |actual, expected|
actual.merge(expected) do |k, o, n|
o == n ? nil : {name: k, actual: o, expected: n}
end
end.values.compact
#⇒ [
# [0] {
# :name => "NONINTERESTEXPENSE",
# :actual => "VARCHAR",
# :expected => "NUMBER"
# },
# [1] {
# :name => "TRANSACTIONDATE",
# :actual => "TIMESTAMP",
# :expected => "NUMBER"
# }
# ]
The method above easily expandable to merge N arrays (use reduce.with_index and merge with key "value_from_#{idx}".)
(expected - actual).
concat(actual - expected).
group_by { |column| column['column_name'] }.
map do |name, (expected, actual)|
{
'column_name' => name,
'expected_column_data_type' => expected['column_data_type'],
'actual_column_data_type' => actual['column_data_type'],
}
end
What about this?
def select(hashes_array, column_name)
hashes_array.select { |h| h["column_name"] == column_name }.first
end
diff = (expected - actual).map do |h|
{
"column_name" => h["column_name"],
"expected_column_data_type" => select(expected, h["column_name"])["column_data_type"],
"actual_column_data_type" => select(actual, h["column_name"])["column_data_type"],
}
end
PS: surely this code can be improved to look more elegant
Code
def convert(actual, expected)
hashify(actual-expected, "actual_data_type").
merge(hashify(expected-actual, "expected_data_type")) { |_,a,e| a.merge(e) }.values
end
def hashify(arr, key)
arr.each_with_object({}) { |g,h| h[g["column_name"]] =
{ "column_name"=>g["column_name"], key=>g["column_data_type"] } }
end
Example
actual = [
{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"TIMESTAMP"},
{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"VARCHAR"},
{"column_name"=>"NONINTERESTINCOME", "column_data_type"=>"NUMBER"},
{"column_name"=>"UPDATEDATE", "column_data_type"=>"TIMESTAMP"}
]
expected = [
{"column_name"=>"NONINTERESTINCOME", "column_data_type"=>"NUMBER"},
{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"NUMBER"},
{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"NUMBER"},
{"column_name"=>"UPDATEDATE", "column_data_type"=>"TIMESTAMP"}
]
convert(actual, expected)
#=> [{"column_name"=>"TRANSACTIONDATE",
# "actual_data_type"=>"TIMESTAMP", "expected_data_type"=>"NUMBER"},
# {"column_name"=>"NONINTERESTEXPENSE",
# "actual_data_type"=>"VARCHAR", "expected_data_type"=>"NUMBER"}]
Explanation
For the example above the steps are as follows.
First hashify actual and expected.
f = actual-expected
#=> [{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"TIMESTAMP"},
# {"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"VARCHAR"}]
g = hashify(f, "actual_data_type")
#=> {"TRANSACTIONDATE"=>{"column_name"=>"TRANSACTIONDATE",
# "actual_data_type"=>"TIMESTAMP"},
# "NONINTERESTEXPENSE"=>{ "column_name"=>"NONINTERESTEXPENSE",
# "actual_data_type"=>"VARCHAR"}}
h = expected-actual
#=> [{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"NUMBER"},
# {"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"NUMBER"}]
i = hashify(h, "expected_data_type")
#=> {"NONINTERESTEXPENSE"=>{"column_name"=>"NONINTERESTEXPENSE",
# "expected_data_type"=>"NUMBER"},
# "TRANSACTIONDATE"=>{"column_name"=>"TRANSACTIONDATE",
# "expected_data_type"=>"NUMBER"}}
Next merge g and i using the form of Hash#merge that employs a block to determine the values of keys that are present in both hashes being merged. See the doc for the definitions of the three block variables (the first of which, the common key, I've represented by an underscore to signify that it is not used in the block calculation).
j = g.merge(i) { |_,a,e| a.merge(e) }
#=> {"TRANSACTIONDATE"=>{"column_name"=>"TRANSACTIONDATE",
# "actual_data_type"=>"TIMESTAMP", "expected_data_type"=>"NUMBER"},
# "NONINTERESTEXPENSE"=>{"column_name"=>"NONINTERESTEXPENSE",
# "actual_data_type"=>"VARCHAR", "expected_data_type"=>"NUMBER"}}
Lastly, drop the keys.
k = j.values
#=> [{"column_name"=>"TRANSACTIONDATE", "actual_data_type"=>"TIMESTAMP",
# "expected_data_type"=>"NUMBER"},
# {"column_name"=>"NONINTERESTEXPENSE", "actual_data_type"=>"VARCHAR",
# "expected_data_type"=>"NUMBER"}]

Structuring ruby multidimensional hash by first hash

I have the hash below:
mm = {
0 => {
0 => 'p1',
1 => 'p2',
2 => 'p3'
},
1 => {
0 => 'idfp1',
1 => 'idfp2',
2 => 'idfp3'
},
2 => {
0 => 'idfp12',
1 => 'idfp22',
2 => 'idfp32'
}
}
And i'm trying to sort it by the hash with a key of 0. In the first hash (0), there are k-v pairs of number to identifier.
In every subsequent hash (1 and 2), 0 points to the 0 from the first hash, 1 points to the 1 from the first hash, etc.
In each hash after 0 (1 and 2), there are IDs (id for person 1) that belong to p1 (person 1).
I've tried to sort this by creating a new hash with only the first hash in the one above to no avail. This is my attempt. The keys are correct but it's pointing to nil, when it should be pointing to the hash with each person's id.
ids = {}
org = {}
mm[0].each do |id, name|
ids[id] = name
end
mm.drop(1).each do |one|
one.each do |key, id|
org[ids[key]] = id
end
end
How can I achieve this in Ruby?
Edit:
In case the explanation doesn't suffice, here is the desired result:
org = {
'p1' => {
0 => 'idfp1',
1 => 'idfp12'
},
'p2' => {
0 => 'idfp2',
1 => 'idfp22'
},
'p3' => {
0 => 'idfp3',
1 => 'idfp32'
}
}
Two ways:
#1
Code
mm[0].invert.each_with_object({}) { |(k,i),h|
h[k] = (1...mm.size).each_with_object ({}) { |j,g| g[j] = mm[j][i] } }
#=> {"p1"=>{1=>"idfp1", 2=>"idfp12"},
# "p2"=>{1=>"idfp2", 2=>"idfp22"},
# "p3"=>{1=>"idfp3", 2=>"idfp32"}}
Explanation
a = mm[0]
#=> {0=>"p1", 1=>"p2", 2=>"p3"}
b = a.invert
#=> {"p1"=>0, "p2"=>1, "p3"=>2}
b.each_with_object({}) { |(k,i),h|
h[k] = (1...mm.size).each_with_object ({}) { |j,g| g[j] = mm[j][i] } }
#=> {"p1"=>{1=>"idfp1", 2=>"idfp12"},
# "p2"=>{1=>"idfp2", 2=>"idfp22"},
# "p3"=>{1=>"idfp3", 2=>"idfp32"}}
#2
Code
mm.values
.map(&:values)
.transpose
.each_with_object({}) { |a,h| h[a.shift] = Hash[[*(0...a.size)].zip(a) ] }
#=> {"p1"=>{0=>"idfp1", 1=>"idfp12"},
# "p2"=>{0=>"idfp2", 1=>"idfp22"},
# "p3"=>{0=>"idfp3", 1=>"idfp32"}}
Explanation
a = mm.values
#=> [{0=>"p1", 1=>"p2", 2=>"p3" },
# {0=>"idfp1", 1=>"idfp2", 2=>"idfp3" },
# {0=>"idfp12", 1=>"idfp22", 2=>"idfp32"}]
b = a.map(&:values
#=> [[ "p1", "p2", "p3" ],
# [ "idfp1", "idfp2", "idfp3" ],
# [ "idfp12", "idfp22", "idfp32"]]
c = b.transpose
#=> [["p1", "idfp1", "idfp12"],
# ["p2", "idfp2", "idfp22"],
# ["p3", "idfp3", "idfp32"]]
c.each_with_object({}) { |a,h| h[a.shift] = Hash[[*(0...a.size)].zip(a) ] }
#=> {"p1"=>{0=>"idfp1", 1=>"idfp12"},
# "p2"=>{0=>"idfp2", 1=>"idfp22"},
# "p3"=>{0=>"idfp3", 1=>"idfp32"}}

Ruby: rules for implicit hashes

Why second output shows me only one element of Array? Is it still Array or Hash already?
def printArray(arr)
arr.each { | j |
k, v = j.first
printf("%s %s %s \n", k, v, j)
}
end
print "Array 1\n"
printArray( [
{kk: { 'k1' => 'v1' }},
{kk: { 'k2' => 'v2' }},
{kk: { 'k3' => 'v3' }},
])
print "Array 2\n"
printArray( [
kk: { 'k1' => 'v1' },
kk: { 'k2' => 'v2' },
kk: { 'k3' => 'v3' },
])
exit
# Output:
#
# Array 1
# kk {"k1"=>"v1"} {:kk=>{"k1"=>"v1"}}
# kk {"k2"=>"v2"} {:kk=>{"k2"=>"v2"}}
# kk {"k3"=>"v3"} {:kk=>{"k3"=>"v3"}}
# Array 2
# kk {"k3"=>"v3"} {:kk=>{"k3"=>"v3"}}
Ruby interpreted the second example as an array with a single hash as its element (the curly braces are implied). It is equivalent to this:
[{ kk: { 'k1' => 'v1' }, kk: { 'k2' => 'v2' }, kk: { 'k3' => 'v3' }}]
Only the last 'kk' is shown because hashes can't have duplicate keys; only the last one sticks.
If you want an array with multiple hashes as elements, you need to use the syntax like on your first example.
More examples on which ruby implies a hash start:
# Only argument on method calls
def only_arg(obj)
puts obj.class
end
only_arg(bar: "baz") # => Hash
# Which is equivalent to:
only_arg({bar: "baz"}) # => Hash
# Last argument on method calls
def last_arg(ignored, obj)
puts obj.class
end
last_arg("ignored", bar: "baz") # => Hash
# Which is equivalent to:
last_arg("ignored", { bar: "baz" }) # => Hash
# Last element on an array
def last_on_array(arr)
puts arr.last.class
end
last_on_array(["something", "something", bar: "baz"]) # => Hash
# Which is equivalent to:
last_on_array(["something", "something", { bar: "baz" }]) # => Hash

Testing hash contents using RSpec

I have a test like so:
it "should not indicate backwards jumps if the checker position is not a king" do
board = Board.new
game_board = board.create_test_board
board.add_checker(game_board, :red, 3, 3)
x_coord = 3
y_coord = 3
jump_locations = {}
jump_locations["upper_left"] = true
jump_locations["upper_right"] = false
jump_locations["lower_left"] = false
jump_locations["lower_right"] = true
adjusted_jump_locations = #bs.adjust_jump_locations_if_not_king(game_board, x_coord, y_coord, jump_locations)
adjusted_jump_locations["upper_left"].should == true
adjusted_jump_locations["upper_right"].should == false
adjusted_jump_locations["lower_left"].should == false
adjusted_jump_locations["lower_right"].should == false
end
which, I know, is verbose. Is there a more concise way to state my expectations? I've looked at the docs but I can't see where to compress my expectations. Thanks.
It works for hashes too:
expect(jump_locations).to include(
"upper_left" => true,
"upper_right" => false,
"lower_left" => false,
"lower_right" => true
)
Source:
include matcher # relishapp.com
Just wanna add to #David's answer. You could nest and use matchers in your include hash. For example:
# Pass
expect({
"num" => 5,
"a" => {
"b" => [3, 4, 5]
}
}).to include({
"num" => a_value_between(3, 10),
"a" => {
"b" => be_an(Array)
}
})
A caveat: a nested include hash must test all keys or the test will fail, e.g.:
# Fail
expect({
"a" => {
"b" => 1,
"c" => 2
}
}).to include({
"a" => {
"b" => 1
}
})
Syntax has changed for RSpec 3, but include matcher is still the one:
expect(jump_locations).to include(
"upper_left" => true,
"upper_right" => false,
"lower_left" => false,
"lower_right" => true
)
See built-in-matchers#include-matcher.
Another easy way to test if the whole content is a Hash is to checkout if the content is the Hash Object itself:
it 'is to be a Hash Object' do
workbook = {name: 'A', address: 'La'}
expect(workbook.is_a?(Hash)).to be_truthy
end
For the question above we can check as follow:
expect(adjusted_jump_locations).to match(hash_including('upper_left' => true))

Resources