Ruby; How can I update the nested hashes in this one? - ruby

Sorry, it's a little bit too long. I need to change the status of :hero and :heroine to "dead"
def update_status
epic_tragedy = {
:montague => {
:patriarch => {name: "Lord Montague", age: "53"},
:matriarch => {name: "Lady Montague", age: "54"},
:hero => {name: "Romeo", age: "15", status: "alive"},
:hero_friends => [
{name: "Benvolio", age: "17", attitude: "worried"},
{name: "Mercutio", age: "18", attitude: "hot-headed"}
]
},
:capulet => {
:patriarch => {name: "Lord Capulet", age: "50"},
:matriarch => {name: "Lady Capulet", age: "51"},
:heroine => {name: "Juliet", age: "15", status: "alive"},
:heroine_friends => [
{name: "Steven", age: "30", attitude: "confused"},
{name: "Nurse", age: "44", attitude: "worried"}
]
}
}
I did add the code below inside the method but it shows error.
epic_tragedy[:montague][:hero][:status] = "dead"
epic_tragedy[:capulet][:hero][:status] = "dead"
Is there any additional step before I put those lines?

epic_tragedy is a local variable within the method definition. Such local variables cannot be accessed from outside the method.
However, the hash is returned when the method is executed, so try
h = update_status
#=> {:montague=>{
# :patriarch=>{:name=>"Lord Montague", :age=>"53"},
# :matriarch=>{:name=>"Lady Montague", :age=>"54"},
# :hero=>{:name=>"Romeo", :age=>"15", :status=>"alive"},
# :hero_friends=>[
# {:name=>"Benvolio", :age=>"17", :attitude=>"worried"},
# {:name=>"Mercutio", :age=>"18", :attitude=>"hot-headed"}
# ]
# },
# :capulet=>{
# :patriarch=>{:name=>"Lord Capulet", :age=>"50"},
# :matriarch=>{:name=>"Lady Capulet", :age=>"51"},
# :heroine=>{:name=>"Juliet", :age=>"15", :status=>"alive"},
# :heroine_friends=>[
# {:name=>"Steven", :age=>"30", :attitude=>"confused"},
# {:name=>"Nurse", :age=>"44", :attitude=>"worried"}
# ]
# }
# }
h[:montague][:hero][:status] = "dead"
h[:capulet][:heroine][:status] = "dead"
h #=> {:montague=>{
# :patriarch=>{:name=>"Lord Montague", :age=>"53"},
# :matriarch=>{:name=>"Lady Montague", :age=>"54"},
# :hero=>{:name=>"Romeo", :age=>"15", :status=>"dead"},
# :hero_friends=>[
# {:name=>"Benvolio", :age=>"17", :attitude=>"worried"},
# {:name=>"Mercutio", :age=>"18", :attitude=>"hot-headed"}
# ]
# },
# :capulet=>{
# :patriarch=>{:name=>"Lord Capulet", :age=>"50"},
# :matriarch=>{:name=>"Lady Capulet", :age=>"51"},
# :heroine=>{:name=>"Juliet", :age=>"15", :status=>"dead"},
# :heroine_friends=>[
# {:name=>"Steven", :age=>"30", :attitude=>"confused"},
# {:name=>"Nurse", :age=>"44", :attitude=>"worried"}
# ]
# }
# }

Related

Iterate through an array of hashes and combine similar elements

Hi I have this array for example
array = [
{ingredients: [:t1, :t2], exp: 100, result: :t5},
{ingredients: [:t3, :t4], exp: 200, result: :t10},
{ingredients: [:t1, :t2], exp: 50, result: :t6}
]
I want an array to look like this:
array = [
{ingredients: [:t1, :t2], exp: 100, results: [:t5, :t6]},
{ingredients: [:t3, :t4], exp: 200, results: [:t10]},
]
So it should check every element in the array and combine all the results of elements which contain the same ingredients array.
I don't really know where to start with this, so any help is appreciated.
array = [
{ingredients: [:t1, :t2], exp: 100, result: :t5},
{ingredients: [:t3, :t4], exp: 200, result: :t10},
{ingredients: [:t1, :t2], exp: 50, result: :t6}
]
array.reduce([]) { |memo, e| # will build new array
el = memo.find { |_e| _e[:ingredients] == e[:ingredients] }
if el # already have same ingredients
el[:results] << e[:result] # modify
memo
else
e[:results] = [*e[:result]] # append
e.delete :result
memo << e
end
}
#=> [
# [0] {
# :exp => 100,
# :ingredients => [
# [0] :t1,
# [1] :t2
# ],
# :results => [
# [0] :t5,
# [1] :t6
# ]
# },
# [1] {
# :exp => 200,
# :ingredients => [
# [0] :t3,
# [1] :t4
# ],
# :results => [
# [0] :t10
# ]
# }
#]
Hope it helps.

Ruby - Compare/merge 2 array of hashes based on 1 key

Lets say I have 2 array of hashes as follows,
local_todos = [{name: "abc", title: "abcdwer", api_id: "1234567", updated_at: "2013-22-12"},
{name: "abcd", title: "abcdwe", api_id: "098098", updated_at: "2013-22-11"},
{name: "abcde", title: "abcdqw", api_id: "345345", updated_at: "2013-22-18"},
{name: "abcdef", title: "abcder", api_id: "234456", updated_at: "2013-22-15"}]
google_tasks = [{name: "abc", title: "xxxxx", id: "1234567", updated: "2013-22-19"},
{name: "abcd", title: "zzzzz", id: "098098", updated: "2013-22-15"},
{name: "abcde", title: "abcdqw", id: "345345", updated: "2013-22-18"},
{name: "abcdef", title: "abcder", id: "234456", updated: "2013-22-15"}]
Now I want to, merge/compare/filter these 2 hashes purely based on api_id(local_todos) and id(google_tasks), so that only the id/api_id(both are the same value) that has a difference in the updated_at(local_todos) and updated(google_tasks) value is printed as the output.
Desired output will be like this,
["1234567", "098098"]
Because if you check those 2 ids has a different updated/updated_at values.
Any help?
Just simple selection:
local_todos.map do | v1 |
google_tasks.any? {|v2| v1[ :api_id ] == v2[ :id ] && v1[ :updated_at ] != v2[ :updated ] } && v1[ :api_id ] || nil
end.compact
# => ["1234567", "098098"]
To compare the code you are able to use the following code:
funcs =
[ proc { local_todos.map {|v1| google_tasks.any? {|v2| v1[ :api_id ] == v2[ :id ] && v1[ :updated_at ] != v2[ :updated ] } && v1[ :api_id ] || nil }.compact },
proc {
local_todos.inject([]) do |result,l_td|
if found = google_tasks.detect {|g_td| g_td[:id] == l_td[:api_id] and g_td[:updated] != l_td[:updated_at]}
result << found[:id]
end
result
end
},
]
def ctime func
time = 0
1000.times { time += Benchmark.measure { 1000.times { func.call } }.to_a[5].to_f }
rtime = time /= 1000000
end
funcs.each {| func | p ctime( func ) }
# 3.9753190517425536e-05
# 4.056975722312927e-05
In my bench results the first code is slight faster.
local_todos.inject([]) do |result,l_td|
if found = google_tasks.detect {|g_td| g_td[:id] == l_td[:api_id] and g_td[:updated] != l_td[:updated_at]}
result << found[:id]
end
result
end
local_todos.collect do |l_todo|
google_tasks.collect do |g_task|
l_todo[:api_id] if (l_todo[:api_id] == g_task[:id] && l_todo[:updated_at] != g_task[:updated])
end.compact
end.flatten

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

Check if any of array hash objects has needed value

I have an array like this:
arr = [{id: 1, name: 'John' }, {id: 2, name: 'Sam' }, {id: 3, name: 'Bob' }]
I need to check if any of arr objects have name Sam. What is the most elegant way? I can only think of cycling with each.
I need to check if any of arr objects have name Sam
Enumerable#any? is a good way to go.
arr = [ {id: 1, name: 'John' }, {id: 2, name: 'Sam' }, {id: 3, name: 'Bob' }]
arr.any? {|h| h[:name] == "Sam"}
# => true
Now if you also want to see which Array object has the value Sam in it,you can use Enumerable#find for the same:
arr.find {|h| h[:name] == "Sam"}
# => {:id=>2, :name=>"Sam"}
You can also choose select or count methods
Enumberable#select
> arr = [{id: 1, name: 'John' }, {id: 2, name: 'Sam' }, {id: 3, name: 'Bob' }]
> arr.select { | h | h[:name] == 'Sam' }
# => [{:id=>2, :name=>"Sam"}]
Enumberable#count
> arr.count { | h | h[:name] == 'Sam' }
# => 1
You can use Enumberable#find_all to return all object that match the constrain
arr = [{:id=>1,:first_name=>'sam'},{:id=>2,:first_name=>'sam'},{:id=>3,:first_name=>'samanderson'},{:id=>4,:first_name=>'samuel'}]
arr.find_all{|obj| obj.first_name == 'sam'}
# => [{:id=>1,:first_name=>'sam'},{:id=>2,:first_name=>'sam'}]

Storing CSV data in Ruby hash

Say I have a CSV file with 4 fields,
ID,name,pay,age
and about 32,000 records.
What's the best way to stick this into a hash in Ruby?
In other words, an example record would look like:
{:rec1 => {:id=>"00001", :name => "Bob", :pay => 150, :age => 95 } }
Thanks for the help!
You can use the Excelsior rubygem for this:
csv = ...
result = Hash.new
counter = 1
Excelsior::Reader.rows(csv) do |row|
row_hash = result[("rec#{counter}".intern)] = Hash.new
row.each do |col_name, col_val|
row_hash[col_name.intern] = col_val
end
counter += 1
end
# do something with result...
Typically we'd want to use an :id field for the Hash key, since it'd be the same as a primary key in a database table:
{"00001" => {:name => "Bob", :pay => 150, :age => 95 } }
This will create a hash looking like that:
require 'ap'
# Pretend this is CSV data...
csv = [
%w[ id name pay age ],
%w[ 1 bob 150 95 ],
%w[ 2 fred 151 90 ],
%w[ 3 sam 140 85 ],
%w[ 31999 jane 150 95 ]
]
# pull headers from the first record
headers = csv.shift
# drop the first header, which is the ID. We'll use it as the key so we won't need a name for it.
headers.shift
# loop over the remaining records, adding them to a hash
data = csv.inject({}) { |h, row| h[row.shift.rjust(5, '0')] = Hash[headers.zip(row)]; h }
ap data
# >> {
# >> "00001" => {
# >> "name" => "bob",
# >> "pay" => "150",
# >> "age" => "95"
# >> },
# >> "00002" => {
# >> "name" => "fred",
# >> "pay" => "151",
# >> "age" => "90"
# >> },
# >> "00003" => {
# >> "name" => "sam",
# >> "pay" => "140",
# >> "age" => "85"
# >> },
# >> "31999" => {
# >> "name" => "jane",
# >> "pay" => "150",
# >> "age" => "95"
# >> }
# >> }
Check out the Ruby Gem smarter_csv, which parses CSV-files and returns array(s) of hashes for the rows in the CSV-file. It can also do chunking, to more efficiently deal with large CSV-files, so you can pass the chunks to parallel Resque workers or mass-create records with Mongoid or MongoMapper.
It comes with plenty of useful options - check out the documentation on GitHub
require 'smarter_csv'
filename = '/tmp/input.csv'
array = SmarterCSV.process(filename)
=>
[ {:id=> 1, :name => "Bob", :pay => 150, :age => 95 } ,
...
]
See also:
https://github.com/tilo/smarter_csv
http://www.unixgods.org/~tilo/Ruby/process_csv_as_hashes.html
Hash[*CSV.read(filename, :headers => true).flat_map.with_index{|r,i| ["rec#{i+1}", r.to_hash]}]

Resources