Create hash-of-hashes using structure of the default hash - ruby

I have the following code:
default = {:id => 0, :detail =>{:name=>"Default", :id => ""}}
employees = {}
nr = (0..3).to_a
nr.each do |n|
employee = default
employee[:id] = n
employee[:detail][:name] = "Default #{n}"
employee[:detail][:id] = "KEY-#{n}"
employees[n] = employee
end
puts employees
I expect the values for the key :id in :detail hash to be KEY-0, KEY-1, KEY-2.

You will need marshall your default in order to copy
default = {id: 0, detail: {name: "Default", id:""}}
employees = {}
4.times do |n|
employees[n] = Marshal.load(Marshal.dump(default))
employees[n][:id] = n
employees[n][:detail][:name] = "Default #{n}"
employees[n][:detail][:id] = "KEY-#{n}"
end
puts employees
The output is
{0=>{:id=>0, :detail=>{:name=>"Default 0", :id=>"KEY-0"}}, 1=>{:id=>1, :detail=>{:name=>"Default 1", :id=>"KEY-1"}}, 2=>{:id=>2, :detail=>{:name=>"Default 2", :id=>"KEY-2"}}, 3=>{:id=>3, :detail=>{:name=>"Default 3", :id=>"KEY-3"}}}
You can read this post Cloning an array with its content
ADDED
And here you have an reduce version and should be faster if you want.
employees = {}
4.times { |n| employees[n]={id: n, detail: {name: "Default #{n}", id:"KEY-#{n}"}} }
puts employees

You need only change:
default = { :id=>0, :detail=>{ :name=>"Default", :id=>"" } }
to
def default
{}.merge(:id=>0, :detail=>({}.merge(:name=>"Default", :id=>"")))
end
but, hey, while we're at it we may as well Ruby-ize the rest:
employees = (0..3).map do |n|
employee = default
employee[:id] = n
employee[:detail][:name] = "Default #{n}"
employee[:detail][:id] = "KEY-#{n}"
employee
end
#=> [{:id=>0, :detail=>{:name=>"Default 0", :id=>"KEY-0"}},
# {:id=>1, :detail=>{:name=>"Default 1", :id=>"KEY-1"}},
# {:id=>2, :detail=>{:name=>"Default 2", :id=>"KEY-2"}},
# {:id=>3, :detail=>{:name=>"Default 3", :id=>"KEY-3"}}]
Let's confirm we are making deep copies of default:
employees[0][:detail][:id] = "cat"
employees
#=> [{:id=>0, :detail=>{:name=>"Default 0", :id=>"cat"}},
# {:id=>1, :detail=>{:name=>"Default 1", :id=>"KEY-1"}},
# {:id=>2, :detail=>{:name=>"Default 2", :id=>"KEY-2"}},
# {:id=>3, :detail=>{:name=>"Default 3", :id=>"KEY-3"}}]
You'd more commonly see this written:
employees = (0..3).map do |n|
default.merge(:id=>n, :detail=>{:name=>"Default #{n}", :id=>"KEY-#{n}"})
end
#=> [{:id=>0, :detail=>{:name=>"Default 0", :id=>"cat"}},
# {:id=>1, :detail=>{:name=>"Default 1", :id=>"KEY-1"}},
# {:id=>2, :detail=>{:name=>"Default 2", :id=>"KEY-2"}},
# {:id=>3, :detail=>{:name=>"Default 3", :id=>"KEY-3"}}]
As suggested by other answers, you could to this:
class Object
def deep_copy
Marshal.load(Marshal.dump(self))
end
end
Then you could write:
default = { :id=>0, :detail=>{ :name=>"Default", :id=>"" } }
employees = (0..3).map do |n|
default.deep_copy.merge(:id=>n, :detail=>{:name=>"Default #{n}",
:id=>"KEY-#{n}"})
end
#=> [{:id=>0, :detail=>{:name=>"Default 0", :id=>"KEY-0"}},
# {:id=>1, :detail=>{:name=>"Default 1", :id=>"KEY-1"}},
# {:id=>2, :detail=>{:name=>"Default 2", :id=>"KEY-2"}},
# {:id=>3, :detail=>{:name=>"Default 3", :id=>"KEY-3"}}]
This has the advantage that, if you change default, no other changes are needed.

You are making a shallow copy in each iteration, i.e. each time each copy is overridden with the values calculated in the last iteration. You can try the following for you hash-within-hash default pattern to make a deep copy:
employee = Marshal.load(Marshal.dump(default))
Demonstration

Related

Find all values that match a parameter in a nested ruby hash

If I have a multi-nested hash like so
{
"Monday"=>{
"North"=>{
"Group 1"=>[
{:name=>"Event A", :type=>"Private"},
{:name=>"Event B", :type=>"Public"},
]
},
"South"=>{
"Group 1"=>[
{:name=>"Event c", :type=>"Private"},
{:name=>"Event D", :type=>"Public"},
{:name=>"Event E", :type=>"Private"},
]
}
},
"Tuesday"=>{
"North"=>{
"Group 1"=>[
{:name=>"Event F", :type=>"Private"},
{:name=>"Event G", :type=>"Public"},
]
},
"South"=>{
"Group 1"=>[
{:name=>"Event H", :type=>"Private"},
]
}
}
}
I would like to be able to search within the hash for all Events that have a type that is equal to Private
How would I go about doing this without knowing exactly what the values of the keys will be in the hash?
If using the gem is an option, there is iteraptor, that is explicitly about iterating deeply nested structures.
Assuming your original hash is named hash, here we go:
hash.iteraptor.
each(full_parent: true, yield_all: true).
with_object({}) do |(parent, (k, v)), acc|
(acc[parent[0...-1]] ||= []) << k if
parent.last.is_a?(Integer) && v.nil? && k.is_a?(Hash) && k[:type] == "Private"
end
Resulting in:
#⇒ {["Monday", "North", "Group 1"] =>
# [{:name=>"Event A", :type=>"Private"}],
# ["Monday", "South", "Group 1"] =>
# [{:name=>"Event c", :type=>"Private"},
# {:name=>"Event E", :type=>"Private"}],
# ["Tuesday", "North", "Group 1"] =>
# [{:name=>"Event F", :type=>"Private"}],
# ["Tuesday", "South", "Group 1"] =>
# [{:name=>"Event H", :type=>"Private"}]}
In solving this recursively I have made three assumptions:
There can be any number of nested arrays and hashes;
:type is the only known key;
if a hash contains the key :type it contains exactly one other key.
def get_em(obj)
arr = []
case obj
when Hash
obj.values.each do |v|
case v
when "Private"
arr += obj.values-[v]
when Hash, Array
arr += get_em(v)
end
end
when Array
obj.each { |e| arr += get_em(e) if Hash === e || Array === e }
end
arr
end
If h is the hash given in the example,
get_em(h)
#=> ["Event A", "Event C", "Event E", "Event F", "Event H"]
Note Hash === e is equivalent to e.is_a?(Hash).
Try this recursion:
def hash_match(the_hash)
found=false
the_hash.each do |key, value|
if value.is_a?(Hash)
if hash_match(value)
if value.has_key :name
puts value[:name]
end
end
elsif value.is_a?(Array)
value.each do |element|
if element.is_a?(Hash)
if hash_match(element)
if element.has_key? :name
puts element[:name]
end
end
end
end
else
if key==:type && value=="Private"
found=true
end
end
end
return found
end
Then just call hash_match(your_hash)

how do I map one csv to another with ruby

I have two csv's with different headers.
lets say csv 1 has headers one, two, three, four and I want to create a csv with headers five, six, seven, eight.
I'm having a hard time writing the code to open the first CSV and then creating the second CSV.
Here is the current code that I have.
require 'csv'
wmj_headers = [
"Project Number",
"Task ID",
"Task Name",
"Status Comment",
"Act Complete",
"Plan Complete",
"Description"]
jir_headers_hash = {
"Summary" => "Task Name",
"Issue key" => "Status Comment",
"Resolved" => "Act Complete",
"Due date" => "Plan Complete",
"Description" => "Description"
}
puts "Enter path to a directory of .csv files"
dir_path = gets.chomp
csv_file_names = Dir["#{dir_path}*.csv"]
csv_file_names.each do |f_path|
base_name = File.basename(f_path, '.csv')
wmj_name = "#{base_name}_wmj.csv"
arr = []
mycount = 0
CSV.open(wmj_name, "wb") do |row|
row << wmj_headers
CSV.foreach(f_path, :headers => true) do |r|
r.headers.each do |value|
if jir_headers_hash[value].nil? == false
arr << r[value]
end
end
end
row << arr
end
end
People tend to overcomplicate things. You don’t need any CSV processing at all to substitute headers.
$ cat /tmp/src.csv
one,two,three
1,2,3
4,5,6
Let’s substitute the headers and stream everything else untouched.
subst = {"one" => "ONE", "two" => "TWO", "three" => "THREE"}
src, dest = %w[/tmp/src.csv /tmp/dest.csv].map { |f| File.new f, "a+" }
headers = src.readline() # read just headers
dest.write(headers.gsub(/\b(#{Regexp.union(subst.keys)})\b/, )) # write headers
IO.copy_stream(src, dest, -1, headers.length) # stream the rest
[src, dest].each(&:close)
Check it:
$ cat /tmp/dest.csv
ONE,TWO,THREE
1,2,3
4,5,6
If you want to substitute CSV column names, here it is:
require 'csv'
# [["one", "two", "three"], ["1", "2", "3"], ["4", "5", "6"]]
csv = CSV.read('data.csv')
# new keys
ks = ['k1', 'k2', 'k3']
# [["k1", "k2", "k3"], ["1", "2", "3"], ["4", "5", "6"]]
k = csv.transpose.each_with_index.map do |x,i|
x[0] = ks[i]
x
end.transpose
# write new file
CSV.open("myfile.csv", "w") do |csv|
k.each do |row|
csv << row
end
end

Group List of hashes and index the values

I has an array of hashes
Some hashes are duplicate
I want to keep the duplicate, but add counter to the title
For example "TITLE #1" And "TITLE #2"
This is my Array
list = []
#temp = {}
#temp["name"] = "Germany"
#temp["id"] = 1
list << #temp
#temp["name"] = "USA"
#temp["id"] = 2
list << #temp
#temp["name"] = "USA"
#temp["id"] = 3
list << #temp
#temp["name"] = "France"
#temp["id"] = 4
list << #temp
#temp["name"] = "France"
#temp["id"] = 5
list << #temp
#temp["name"] = "France"
#temp["id"] = 6
list << #temp
I Want the result Same as the source but near "USA" add the counter "USA #1" and "USA #2"
And France change to "France #1", "France #2" "France #3"
No change on germany element because there are not multiple items
You need #temp = 0 at the beginning of each block of code.
After executing your code with the modification
list = [{"name"=>"Germany", "id"=>1},
{"name"=>"USA", "id"=>2},
{"name"=>"USA", "id"=>3},
{"name"=>"France", "id"=>4},
{"name"=>"France", "id"=>5},
{"name"=>"France", "id"=>6}]
We can then obtain your desired result as follows.
list.group_by { |h| h["name"] }.values.flat_map do |a|
a.map.with_index(1) do |h,i|
base = h["name"]
h.merge("name"=>base +" #{i}")
end
end
#=> [{"name"=>"Germany 1", "id"=>1},
# {"name"=>"USA 1", "id"=>2},
# {"name"=>"USA 2", "id"=>3},
# {"name"=>"France 1", "id"=>4},
# {"name"=>"France 2", "id"=>5},
# {"name"=>"France 3", "id"=>6}]
Note
arr = list.group_by { |h| h["name"] }.values
#=> [[{"name"=>"Germany", "id"=>1}],
# [{"name"=>"USA", "id"=>2}, {"name"=>"USA", "id"=>3}],
# [{"name"=>"France", "id"=>4}, {"name"=>"France", "id"=>5},
# {"name"=>"France", "id"=>6}]]
Had I used Enumerable#map rather than Enumerable#flat_map, the result would have been
[[{"name"=>"Germany 1", "id"=>1}],
[{"name"=>"USA 1", "id"=>2}, {"name"=>"USA 2", "id"=>3}],
[{"name"=>"France 1", "id"=>4}, {"name"=>"France 2", "id"=>5},
{"name"=>"France 3", "id"=>6}]]
Using flat_map is equivalent to inserting a splat in front of each of this array's elements.
[*[{"name"=>"Germany 1", "id"=>1}],
*[{"name"=>"USA 1", "id"=>2}, {"name"=>"USA 2", "id"=>3}],
*[{"name"=>"France 1", "id"=>4}, {"name"=>"France 2", "id"=>5},
{"name"=>"France 3", "id"=>6}]]

Convert Nested hashes in Ruby into a table format

I have a nested hash in ruby:
{"Table1"=>{"Acct type"=>{"Expected"=>"ACC", "Actual"=>"ACC"}, "Seq No"=>{"Expected"=>"100.0", "Actual"=>#<BigDecimal:56d0b28,'0.1E3',9(18)>}, "Class"=>{"Expected"=>"AC", "Actual"=>"AC"}}, "Table 2"=>{"Date"=>{"Expected"=>"20140606", "Actual"=>"20130606"}}}
I need to display the above nested hash in a table format -
Table Field Expected Value Actual Value
Table 1 Acct type ACC ACC
Table 1 Seq No 100.0 100.0
Table 1 Class AC AC
Table 2 Date 20140606 20130606
Any suggestions/pointers would be really helpful. I have tried using 'tableprint' and 'text-table' gems but couldn't get the desired result. The above data was fetched from DB2 tables using ActiveRecord.
Here is one solution:
x = {
"Table1"=>{
"Acct type"=>{"Expected"=>"ACC", "Actual"=>"ACC"},
"Seq No"=>{"Expected"=>"100.0", "Actual"=> 100.00},
"Class"=>{"Expected"=>"AC", "Actual"=>"AC"}
},
"Table 2"=>{
"Date"=> {"Expected"=>"20140606",
"Actual"=>"20130606"}}}
def table_row(row_name, row_values)
[row_name, row_values["Expected"], row_values["Actual"]]
end
def table_rows(table_hash)
table_hash.map do |field_name, field_values|
table_row(field_name, field_values)
end
end
def widest_string_lengths(visual_table)
table_columns_count = visual_table.first.size - 1
0.upto(table_columns_count).map do |index|
columns = visual_table.map { |row| row[index].to_s }
columns.max_by(&:length).length + 2
end
end
def aligned_visual_table(visual_table)
widest_string_items = widest_string_lengths(visual_table)
visual_table.map do |row|
row.each_with_index.map do |cell, index|
cell.to_s.ljust(widest_string_items[index] + 2)
end
end
end
table_headers = ['Table', 'Field', 'Value', 'Actual Value']
# This just turns the hash to array of arrays
visual_table = x.map do |table_name, table_hash|
table_rows(table_hash).map do |table_rows|
table_rows.insert(0, table_name)
end
end.flatten(1).insert(0, table_headers)
puts "*********"
puts aligned_visual_table(visual_table).map(&:join).join("\n")
You example suggests there are unspecified parameters affecting the appearance of the table. For example, the column labels "Table" and "Field" are left-adjusted, whereas "Expected Value" and "Actual Value" are not. Also, "Expected Value" overlaps "Acct type". Below I suggest how you might construct the table, but I've made compromises: left-adjusting all column labels and not overlapping vertically.
Code
def print_table(h, column_labels_and_spacing, table_label)
column_labels = column_labels_and_spacing.map(&:first)
column_spacing = column_labels_and_spacing[0..-2].map(&:last) << 0
rows = h.map { |k,f|
[table_label[k]].product(f.map { |field, g|
[[field, g.values.map(&:to_s)].flatten] }) }
.flat_map { |e| e.map(&:flatten) }.unshift(column_labels)
column_widths = rows.transpose.map { |c| c.max_by(&:size).size }
.zip(column_spacing).map { |a,b| a+b }
rows.each do |row|
row.zip(column_widths).each { |s, width| print s.ljust(width) }
puts
end
end
Example
Input
h = {"Table1"=>
{"Acct type"=>{"Expected"=>"ACC", "Actual"=>"ACC"},
"Seq No" =>{"Expected"=>"100.0", "Actual"=>100},
"Class" =>{"Expected"=>"AC", "Actual"=>"AC"}},
"Table 2"=>
{"Date" =>{"Expected"=>"20140606", "Actual"=>"20130606"}}}
column_labels_and_spacing = [["Table", 1], ["Field", 2],
["Expected", 3], ["Actual"]]
table_label = { "Table1"=>"Table 1", "Table 2"=>"Table 2" }
To simplify the explanation I converted the BigDecimal value to a Fixnum.
Invoke method
print_table(h, column_labels_and_spacing, table_label)
#-> Table Field Expected Actual
# Table 1 Acct type ACC ACC
# Table 1 Seq No 100.0 100
# Table 1 Class AC AC
# Table 2 Date 20140606 20130606
Explanation
For the above example:
column_labels = column_labels_and_spacing.map(&:first)
#=> ["Table", "Field", "Expected", "Actual"]
column_spacing = column_labels_and_spacing[0..-2].map(&:last) << 0
#=> [1, 2, 3, 0]
a = h.map { |k,f|
[table_label[k]].product(f.map { |field, g|
[[field, g.values.map(&:to_s)].flatten] }) }
# => [[["Table 1", [["Acct type", "ACC", "ACC"]]],
# ["Table 1", [["Seq No", "100.0", "100"]]],
# ["Table 1", [["Class", "AC", "AC"]]]],
# [["Table 2", [["Date", "20140606", "20130606"]]]]]
To see how a is calculated, consider the first key-value pair of h passed to the block1, the key being assigned to the block variable k and the value assigned to f:
k = "Table1"
f = { "Acct type"=>{ "Expected"=>"ACC", "Actual"=>"ACC" } }
The block then performs the following calculations:
b = f.map { |field, g| [[field, g.values.map(&:to_s)].flatten] }
# => [["Acct type", "ACC", "ACC"]]
c = ["Table 1"].product([[["Acct type", "ACC", "ACC"]]])
#=> [["Table 1", [["Acct type", "ACC", "ACC"]]]]
d = c.flatten
#=> ["Table 1", "Acct type", "ACC", "ACC"]
Now let's carry on, having computed a. Compute the rows of the body of the table:
e = a.flat_map { |e| e.map(&:flatten) }
#=> [["Table 1", "Acct type", "ACC", "ACC"],
# ["Table 1", "Seq No", "100.0", "100"],
# ["Table 1", "Class", "AC", "AC"],
# ["Table 2", "Date", "20140606", "20130606"]]
and then add the column labels:
rows = e.unshift(column_labels)
#=> [["Table", "Field", "Expected", "Actual"],
# ["Table 1", "Acct type", "ACC", "ACC"],
# ["Table 1", "Seq No", "100.0", "100"],
# ["Table 1", "Class", "AC", "AC"],
# ["Table 2", "Date", "20140606", "20130606"]]
Next, compute column widths:
f = rows.transpose
#=> [["Table", "Table 1", "Table 1", "Table 1", "Table 2"],
# ["Field", "Acct type", "Seq No", "Class", "Date"],
# ["Expected", "ACC", "100.0", "AC", "20140606"],
# ["Actual", "ACC", "100", "AC", "20130606"]]
g = f.map { |c| c.max_by(&:size).size }
#=> [7, 9, 8, 8]
i = g.zip(column_spacing)
# => [[7, 1], [9, 2], [8, 3], [8, 0]]
column_widths = i.map { |a,b| a+b }
#=> [8, 11, 11, 8]
rows.each do |row|
row.zip(column_widths).each { |s, width| print s.ljust(width) }
puts
end
Consider the printing of the second row of the table (the first row after the column labels):
row = ["Table 1", "Acct type", "ACC", "ACC"]
row.zip(column_widths)
# => [["Table 1", 8], ["Acct type", 11], ["ACC", 11], ["ACC", 8]]
The first field is printed:
s = "Table 1"
width = 8
print s.ljust(width); puts ?:
#Table 1 :
where I printed the colon just to show the field width. Other fields are printed similarly.
1 Ruby 1.9+ is required to know which key-value pair is passed to the block first.

Sequentially parse array to hash in Ruby

I have an array that looks like this:
array = [
"timestamp 1",
"data 1",
"data 2",
"data 3",
"timestamp 2",
"data "1",
"timestamp 3",
".."
]
etc
I want to loop through my array, and turn it into a hash data structure that looks like:
hash = {
"timestamp 1" => [ "data 1", " data 2", "data 3" ],
"timestamp 2" => [ "data 1" ],
}
I can't figure out a good "rubyish" way of doing it. I'm looping through the array, and I just quite can't seem to figure out how to keep track of where I am at, and assign to the hash as needed.
# Let's comb through the array, and map the time value to the subsequent lines beneath
array.each do |e|
if timestamp?(e)
hash["#{e}"] == nil
else
# last time stamp here => e
end
EDIT: Here is the timestamp? method
def timestamp?(string)
begin
return true if string =~ /[a-zA-z][a-z][a-z]\s[a-zA-z][a-z][a-z]\s\d\d\s\d\d:\d\d:\d\d\s\d\d\d\d/
false
rescue => msg
puts "Error in timestamp? => #{msg}"
exit
end
end
array = [
"timestamp 1",
"data 1",
"data 2",
"data 3",
"timestamp 2",
"data 1",
"timestamp 3",
"data 2"
]
hsh = {}
ary = []
array.each do |line|
if line.start_with?("timestamp")
ary = Array.new
hsh[line] = ary
else
ary << line
end
end
puts hsh.inspect
I would do as below:
array = [
"timestamp 1",
"data 1",
"data 2",
"data 3",
"timestamp 2",
"data 1",
]
Hash[array.slice_before{|i| i.include? 'timestamp'}.map{|a| [a.first,a[1..-1]]}]
# => {"timestamp 1"=>["data 1", "data 2", "data 3"], "timestamp 2"=>["data 1"]}
Hash[array.slice_before{|e| e.start_with?("timestamp ")}.map{|k, *v| [k, v]}]
Output
{
"timestamp 1" => [
"data 1",
"data 2",
"data 3"
],
"timestamp 2" => ["data 1"],
"timestamp 3" => [".."]
}
You can keep track of the last hash key using an outside variable. It will be persisted across all iterations:
h = {}
last_group = nil
array.each do |e|
if timestamp?(e)
array[e] = []
last_group = e
else
h[last_group] << e
end
end
last_timestamp = nil
array.reduce(Hash.new(){|hsh,k| hsh[k]=[]}) do |hsh, m|
if m =~ /timestamp/
last_timestamp = m
else
hsh[last_timestamp] << m
end
hsh
end
hash = (Hash.new { |this, key| this[key] = [] } ).tap do |hash|
current_timestamp = nil
array.each do |element|
current_timestamp = element if timestamp? element
hash[current_timestamp] << element unless timestamp? element
end
end
Using an outside variable to keep track of the current timestamp, but wrapping it in a closure to avoid polluting the namespace.
I know this has already been answered, but there are so many ways to do this.
I prefer these two ways, they might not be fast but i find them readable:
my_hash = Hash.new
array.slice_before(/timestamp/).each do |array|
key, *values = array
my_hash[key] = values
end
or
one_liner = Hash[array.slice_before(/timestamp/).map{|x|[x.shift, x]}]

Resources