Extracting Data from array of hashes Ruby - ruby

Given that I have the following array of hashes
#response = { "0"=>{"forename_1"=>"John", "surname_1"=>"Smith"},
"1"=>{"forename_1"=>"Chris", "surname_1"=>"Jenkins"},
"2"=>{"forename_1"=>"Billy", "surname_1"=>"Bob"},
"Status" => 100
}
I am looking to create an array of the forename_1 and surname_1 values combined, so desired output would be
["John Smith", "Chris Jenkins", "Billy Bob"]
So I can get this far, but need further assistance
# Delete the Status as not needed
#response.delete_if { |k| ["Status"].include? k }
#response.each do |key, value|
puts key
#This will print out 0 1 2
puts value
# This will print {"forename_1"=>"John", "surname_1"=>"Smith"}, "{"forename_1"=>"Chris", "surname_1"=>"Jenkins"}, "{"forename_1"=>"Billy", "surname_1"=>"Bob"}
puts value.keys
# prints ["forename_1", "surname_1"], ["forename_1", "surname_1"], ["forename_1", "surname_1"]
puts value.values
# prints ["John", "Smith"], ["Chris", "Jenkins"], ["Billy", "Bob"]
value.map { |v| v["forename_1"] }
# However i get no implicit conversion of String into Integer error
end
What am i doing wrong here?
Thanks

Another way :
#response.values.grep(Hash).map { |t| t.values.join(' ')}

What you have to do is to get the values of the #response hash, filter out what is not an instance of Hash, and then join together the forename and the surname, I would do something like this:
#response.values.grep(Hash).map { |h| "#{h['forename_1']} #{h['surname_1']}" }
# => ["John Smith", "Chris Jenkins", "Billy Bob"]

#response.values.map{ |res|
[res["forename_1"] , res["surname_1"]].join(' ') if res.is_a?(Hash)
}.compact

Related

How to generate the expected output by using split method used in my code?

Question:
Create a method for Array that returns a hash having 'key' as length of the element and value as an array of all the elements of that length. Make use of Array#each.
Returned Hash should be sorted by key.
I have tried to do it through Hash sorting over length. I have almost resolved it using another method but I want to use split and hash to achieve expected output.
Can anyone suggest any amendments in my code below?
Input argument:
array-hash.rb "['abc','def',1234,234,'abcd','x','mnop',5,'zZzZ']"
Expected output:
{1=>["x", "5"], 3=>["abc", "def", "234"], 4=>["1234", "abcd", "mnop", "zZzZ"]}
class String
def key_length(v2)
hash = {}
v2.each do |item|
item_length = item.to_s.length
hash[item_length] ||= []
hash[item_length].push(item)
end
Hash[hash.sort]
end
end
reader = ''
if ARGV.empty?
puts 'Please provide an input'
else
v1 = ARGV[0]
v2 = v1.tr("'[]''",'').split
p reader.key_length(v2)
end
Actual output:
{35=>["abc,def,1234,234,abcd,x,mnop,5,zZzZ"]}
Given the array (converted from string, note integers as string between ""):
ary = str[1..-2].delete('\'').split(',')
ary #=> ["abc", "def", "1234", "234", "abcd", "x", "mnop", "5", "zZzZ"]
The most "idiomatic" way should be using group_by:
ary.group_by(&:size)
If you want to use each, then you could use Enumerable#each_with_object, where the object is an Hash#new with an empty array as default:
ary.each_with_object(Hash.new{ |h,k| h[k] = []}) { |e, h| h[e.size] << e }
Which is the same as
res = Hash.new{ |h,k| h[k] = []}
ary.each { |e| res[e.size] << e }
Not sure why you need to monkeypatch* array here, is this a school exercise or something?
I think your bug is you need to pass in the comma delimiter arg to split.
I would solve the underlying problem as a reduce/inject/fold thing, myself.
s = "['abc','def',1234,234,'abcd','x','mnop',5,'zZzZ']"
splits = s.tr("'[]''",'').split(',') # need to pass in the comma for the split
Hash[splits.inject({}) { |memo,s| memo[s.length] ||= []; memo[s.length] << s; memo }.sort] # doesn't use Array.each but?
{1=>["x", "5"], 3=>["def", "234"], 4=>["1234", "abcd", "mnop"],
5=>["['abc"], 6=>["zZzZ']"]}

categorize by hash value

I have an array of hashes with values like:
by_person = [{ :person => "Jane Smith", :filenames => ["Report.pdf", "File2.pdf"]}, {:person => "John Doe", :filenames => ["Report.pdf] }]
I would like to end up with another array of hashes (by_file) that has each unique value from the filenames key as a key in the by_file array:
by_file = [{ :filename => "Report.pdf", :people => ["Jane Smith", "John Doe"] }, { :filename => "File2.pdf", :people => [Jane Smith] }]
I have tried:
by_file = []
by_person.each do |person|
person[:filenames].each do |file|
unless by_file.include?(file)
# list people that are included in file
by_person_each_file = by_person.select{|person| person[:filenames].include?(file)}
by_person_each_file.each do |person|
by_file << {
:file => file,
:people => person[:person]
}
end
end
end
end
as well as:
by_file.map(&:to_a).reduce({}) {|h,(k,v)| (h[k] ||= []) << v; h}
Any feedback is appreciated, thanks!
Doesn't seem too tricky, but the way you're compiling it isn't very efficient:
by_person = [{ :person => "Jane Smith", :filenames => ["Report.pdf", "File2.pdf"]}, {:person => "John Doe", :filenames => ["Report.pdf"] }]
by_file = by_person.each_with_object({ }) do |entry, index|
entry[:filenames].each do |filename|
set = index[filename] ||= [ ]
set << entry[:person]
end
end.collect do |filename, people|
{
filename: filename,
people: people
}
end
puts by_file.inspect
# => [{:filename=>"Report.pdf", :people=>["Jane Smith", "John Doe"]}, {:filename=>"File2.pdf", :people=>["Jane Smith"]}]
This makes use of a hash to group the people by filename, essentially inverting your structure, and then converts that into the final format in a second pass. This is more efficient than working with the final format during compilation as that's not indexed and requires an expensive linear search to find the correct container to insert into.
An alternate method is to create a default hash constructor that makes the structure you're looking for:
by_file_hash = Hash.new do |h,k|
h[k] = {
filename: k,
people: [ ]
}
end
by_person.each do |entry|
entry[:filenames].each do |filename|
by_file_hash[filename][:people] << entry[:person]
end
end
by_file = by_file_hash.values
puts by_file.inspect
# => [{:filename=>"Report.pdf", :people=>["Jane Smith", "John Doe"]}, {:filename=>"File2.pdf", :people=>["Jane Smith"]}]
This may or may not be easier to understand.
This is one way to do it.
Code
def convert(by_person)
by_person.each_with_object({}) do |hf,hp|
hf[:filenames].each do |fname|
hp.update({ fname=>[hf[:person]] }) { |_,oh,nh| oh+nh }
end
end.map { |fname,people| { :filename => fname, :people=>people } }
end
Example
by_person = [{:person=>"Jane Smith", :filenames=>["Report.pdf", "File2.pdf"]},
{:person=>"John Doe", :filenames=>["Report.pdf"]}]
convert(by_person)
#=> [{:filename=>"Report.pdf", :people=>["Jane Smith", "John Doe"]},
# {:filename=>"File2.pdf", :people=>["Jane Smith"]}]
Explanation
For by_person in the example:
enum1 = by_person.each_with_object({})
#=>[{:person=>"Jane Smith", :filenames=>["Report.pdf", "File2.pdf"]},
{:person=>"John Doe", :filenames=>["Report.pdf"]}]:each_with_object({})>
Let's see what values the enumerator enum will pass into the block:
enum1.to_a
#=> [[{:person=>"Jane Smith", :filenames=>["Report.pdf", "File2.pdf"]}, {}],
# [{:person=>"John Doe", :filenames=>["Report.pdf"]}, {}]]
As will be shown below, the empty hash in the first element of the enumerator will no longer be empty with the second element is passed into the block.
The first element is assigned to the block variables as follows (I've indented to indicate the block level):
hf = {:person=>"Jane Smith", :filenames=>["Report.pdf", "File2.pdf"]}
hp = {}
enum2 = hf[:filenames].each
#=> #<Enumerator: ["Report.pdf", "File2.pdf"]:each>
enum2.to_a
#=> ["Report.pdf", "File2.pdf"]
"Report.pdf" is passed to the inner block, assigned to the block variable:
fname = "Report.pdf"
and
hp.update({ "Report.pdf"=>["Jane Smith"] }) { |_,oh,nh| oh+nh }
#=> {"Report.pdf"=>["Jane Smith"]}
is executed, returning the updated value of hp.
Here the block for Hash#update (aka Hash#merge!) is not consulted. It is only needed when the hash hp and the merging hash (here { fname=>["Jane Smith"] }) have one or more common keys. For each common key, the key and the corresponding values from the two hashes are passed to the block. This is elaborated below.
Next, enum2 passes "File2.pdf" into the block and assigns it to the block variable:
fname = "File2.pdf"
and executes
hp.update({ "File2.pdf"=>["Jane Smith"] }) { |_,oh,nh| oh+nh }
#=> {"Report.pdf"=>["Jane Smith"], "File2.pdf"=>["Jane Smith"]}
which returns the updated value of hp. Again, update's block was not consulted. We're now finished with Jane, so enum1 next passes its second and last value into the block and assigns the block variables as follows:
hf = {:person=>"John Doe", :filenames=>["Report.pdf"]}
hp = {"Report.pdf"=>["Jane Smith"], "File2.pdf"=>["Jane Smith"]}
Note that hp has now been updated. We then have:
enum2 = hf[:filenames].each
#=> #<Enumerator: ["Report.pdf"]:each>
enum2.to_a
#=> ["Report.pdf"]
enum2 assigns
fname = "Report.pdf"
and executes:
hp.update({ "Report.pdf"=>["John Doe"] }) { |_,oh,nv| oh+nv }
#=> {"Report.pdf"=>["Jane Smith", "John Doe"], "File2.pdf"=>["Jane Smith"]}
In making this update, hp and the hash being merged both have the key "Report.pdf". The following values are therefore passed to the block variables |k,ov,nv|:
k = "Report.pdf"
oh = ["Jane Smith"]
nh = ["John Doe"]
We don't need the key, so I've replaced it with an underscore. The block returns
["Jane Smith"]+["John Doe"] #=> ["Jane Smith", "John Doe"]
which becomes the new value for the key "Report.pdf".
Before turning to the final step, I'd like to suggest that you consider stopping here. That is, rather than constructing an array of hashes, one for each file, just leave it as a hash with the files as keys and arrays of persons the values:
{ "Report.pdf"=>["Jane Smith", "John Doe"], "File2.pdf"=>["Jane Smith"] }
The final step is straightforward:
hp.map { |fname,people| { :filename => fname, :people=>people } }
#=> [{ :filename=>"Report.pdf", :people=>["Jane Smith", "John Doe"] },
# { :filename=>"File2.pdf", :people=>["Jane Smith"] }]

RABL OR PLAIN JSON formatting - how do I group results by common key?

{"controller_id"=>699, "high_temp"=>11, "low_temp"=>8, "date"=>"2014-01-13"}
{"controller_id"=>699, "high_temp"=>11, "low_temp"=>7, "date"=>"2014-01-14"}
{"controller_id"=>699, "high_temp"=>10, "low_temp"=>6, "date"=>"2014-01-15"}
{"controller_id"=>794, "high_temp"=>39, "low_temp"=>34, "date"=>"2014-01-17"}
{"controller_id"=>794, "high_temp"=>77, "low_temp"=>62, "date"=>"2014-01-18"}
{"controller_id"=>794, "high_temp"=>16, "low_temp"=>85, "date"=>"2014-01-19"}
{"controller_id"=>794, "high_temp"=>37, "low_temp"=>47, "date"=>"2014-01-20"}
Above is a sample of the output.
These results are coming back from ActiveRecord as Objects. Example:
#<ControllerWeatherDay controller_id: 699, high_temp: 8, low_temp: 0, date: "2014-01-01">
As you can see, we report weather for controllers by date, what I'm trying to do in RABL that I have been struggling with is:
{"controllers" : [
{
id:699,
weather: [
{"high_temp"=>11, "low_temp"=>8, "date"=>"2014-01-13"},
{"high_temp"=>11, "low_temp"=>7, "date"=>"2014-01-14"},
{"high_temp"=>10, "low_temp"=>6, "date"=>"2014-01-15"}
]
},
{
id:794,
weather: [
{"high_temp"=>39, "low_temp"=>34, "date"=>"2014-01-17"},
{"high_temp"=>77, "low_temp"=>62, "date"=>"2014-01-18"},
{"high_temp"=>16, "low_temp"=>85, "date"=>"2014-01-19"},
{"high_temp"=>37, "low_temp"=>47, "date"=>"2014-01-20"}
]
}
]
}
I am TOTALLY ok with not using RABL. It's a pain in the ass. Regular JSON object construction is ok with me. Any help is appreciated.
RABL :
object #weather
if params[:controller_id]
attributes :controller_id, :high_temp, :low_temp, :date
else
attributes :high_temp, :low_temp, :date
end
I've worked through various node :high_temp do |h| stuff end iterations, but I'm honestly not thrilled with RABL. Happy to dump it.
The solution w/out RABL (I omit the deletion of controller_id from the resulting arrays for the sake of code readness):
data = [
{"controller_id"=>699, "high_temp"=>11, "low_temp"=>8, "date"=>"2014-01-13"},
{"controller_id"=>699, "high_temp"=>11, "low_temp"=>7, "date"=>"2014-01-14"},
{"controller_id"=>699, "high_temp"=>10, "low_temp"=>6, "date"=>"2014-01-15"},
{"controller_id"=>794, "high_temp"=>39, "low_temp"=>34, "date"=>"2014-01-17"},
{"controller_id"=>794, "high_temp"=>77, "low_temp"=>62, "date"=>"2014-01-18"},
{"controller_id"=>794, "high_temp"=>16, "low_temp"=>85, "date"=>"2014-01-19"},
{"controller_id"=>794, "high_temp"=>37, "low_temp"=>47, "date"=>"2014-01-20"}
]
result = data.inject({}) { |memo, c| # memoize the hash
(memo["#{c['controller_id']}"] ||= []) << c; memo
}.map { |id, weather| # map to desired output format
{ :id => id, :weather => weather }
}
Hope it helps.
try out the following code (I presume an array variable called input_data which contains, yeah, the input data :-)):
# Make a hash where controller_ids are keys
mid_result = input_data.inject({}) do |res, el|
if el["controller_id"]
res[el["controller_id"]] ||= []
res[el["controller_id"]].push( el.slice("high_temp", "low_temp", "date"))
end
res
end
# Final formatting
{
"controllers" => mid_result.map do |key, value|
{"id" => key, "weather" => value}
end
}
Then you can call .to_json on the final result if you need
You could use group_by:
arr.group_by { |x| x['controller_id'] }.map { |id, weather| {id: id, weather: weather } }
# => [{:id=>699, :weather=>[
# {"controller_id"=>699, "high_temp"=>11, "low_temp"=>8, "date"=>"2014-01-13"},
# {"controller_id"=>699, "high_temp"=>11, "low_temp"=>7, "date"=>"2014-01-14"},
# {"controller_id"=>699, "high_temp"=>10, "low_temp"=>6, "date"=>"2014-01-15"}]}
# [{:id=>794, :weather=>[
# {"controller_id"=>794, "high_temp"=>39, "low_temp"=>34, "date"=>"2014-01-17"},
# {"controller_id"=>794, "high_temp"=>77, "low_temp"=>62, "date"=>"2014-01-18"},
# {"controller_id"=>794, "high_temp"=>16, "low_temp"=>85, "date"=>"2014-01-19"},
# {"controller_id"=>794, "high_temp"=>37, "low_temp"=>47, "date"=>"2014-01-20"}]}
# ]

compare values in a hash and return the matching key

I'm trying to look through a hash and compare it's values to an existing string and then when the match is found I want to output its key. I trying to write this in a code block and output the result to the console.
officer.name = "Dave"
#hash = { "Tom" => "97", "Dave" => "98", "John" => "99" }
#hash.each { |key, value| do
if #{key} == officer.name
puts "id: #{value}"
else
puts "no match"
end
}
Right now my console outputs:
id: 97
no match
id: 98
no match
id: 99
no match
I'm trying to get it to output just the value of #{value} based on it's matching #{key}, which in this case would be Dave. So for the above example I want my console to spit just the number 98 or "no match".
This is a hash! You can do what you attempt way more efficiently:
officer.name = "Dave"
#hash = { "Tom" => "97", "Dave" => "98", "John" => "99" }
unless #hash.key?(officer.name)
puts "no match"
else
puts "id: #{#hash[officer.name]}"
end
Is it because you forgot the " ?
if "#{key}" == officer.name
but you could just do
if key == officer.name
officer.name = "Dave"
#hash = { "Tom" => "97", "Dave" => "98", "John" => "99" }
#hash.each do |key, value|
if key == officer.name
puts key
else
puts "no match"
end
end
This should work
#hash.has_key?(officer.name) ? #hash[officer.name] : "no match"
When doing hash lookups by key, avoid #[]. Favor #fetch instead:
officer.name = "Dave"
#hash = { "Tom" => "97", "Dave" => "98", "John" => "99" }
puts #hash.fetch(officer.name, 'no match')
#fetch allows you to specify a default value other than nil, which will prevent unexpected nils from hash lookups from throwing the all-too-common NoMethodError.
this works too. slight edit from original post.
it prints out answer for each pair though and doesn't identify which pair the answer refers to.
officer = "Dave"
#hash = { "Tom" => "97", "Dave" => "98", "John" => "99" }
#hash.each do |key, value|
if key == officer
puts key
else
puts "no match"
end
end

How do you replace a string with a hash collection value in Ruby?

I have a hash collection:
my_hash = {"1" => "apple", "2" => "bee", "3" => "cat"}
What syntax would I use to replace the first occurrence of the key with hash collection value in a string?
eg my input string:
str = I want a 3
The resulting string would be:
str = I want a cat
My one liner:
hash.each { |k, v| str[k] &&= v }
or using String#sub! method:
hash.each { |k, v| str.sub!(k, v) }
"I want a %{b}" % {c: "apple", b: "bee", a: "cat"}
=> "I want a bee"
Assuming Ruby 1.9 or later:
str.gsub /\d/, my_hash
I didn't understand your problem, but you can try this:
my_hash = {"1" => "apple", "2" => "bee", "3" => "cat"}
str = "I want a 3"
str.gsub(/[[:word:]]+/).each do |word|
my_hash[word] || word
end
#=> "I want a cat"
:D
Just to add point free style abuse to fl00r's answer:
my_hash = {"1" => "apple", "2" => "bee", "3" => "cat"}
my_hash.default_proc = Proc.new {|hash, key| key}
str = "I want a 3"
str.gsub(/[[:word:]]+/).each(&my_hash.method(:[]))
my_hash = {"1" => "apple", "2" => "bee", "3" => "cat"}
str = "I want a 3"
If there isn't any general pattern for the strings you want to substitute, you can use:
str.sub /#{my_hash.keys.map { |s| Regexp.escape s }.join '|'}/, my_hash
But if there is one, the code becomes much simpler, e.g.:
str.sub /[0-9]+/, my_hash
If you want to substitute all the occurrences, not only the first one, use gsub.
You can use String.sub in ruby 1.9:
string.sub(key, hash[key])
The following code replace the first occurrence of the key with hash collection value in the given string str
str.gsub(/\w+/) { |m| my_hash.fetch(m,m)}
=> "I want a cat"

Resources