How do I extract the hash from an array of one hash? - ruby

I'm writing an API parser at the moment, and I'm working on formatting the data nicely.
So far, I have the following code:
data.each {|season| episodes[season["no"].to_i] = season["episode"].group_by{|i| i["seasonnum"].to_i}}
However, the only issue with this is that the output comes out like this:
8 => {
1 => [
[0] {
"epnum" => "150",
"seasonnum" => "01",
"prodnum" => "3X7802",
"airdate" => "2012-10-03",
"link" => "http://www.tvrage.com/Supernatural/episodes/1065195189",
"title" => "We Need to Talk About Kevin"
}
],
2 => [
[0] {
"epnum" => "151",
"seasonnum" => "02",
"prodnum" => "3X7803",
"airdate" => "2012-10-10",
"link" => "http://www.tvrage.com/Supernatural/episodes/1065217045",
"title" => "What's Up, Tiger Mommy?"
}
]
}
So there's a redundant array in each value of the secondary hash. How would I remove this array and just have the inside hash? So, for example I want:
8 => {
1 => {
"epnum" => "150",
"seasonnum" => "01",
"prodnum" => "3X7802",
"airdate" => "2012-10-03",
"link" => "http://www.tvrage.com/Supernatural/episodes/1065195189",
"title" => "We Need to Talk About Kevin"
}
,
etc.
EDIT: Here's the full file:
require 'httparty'
require 'awesome_print'
require 'debugger'
require 'active_support'
episodes = Hash.new{ [] }
response = HTTParty.get('http://services.tvrage.com/feeds/episode_list.php?sid=5410')
data = response.parsed_response['Show']['Episodelist']["Season"]
data.each { |season|
episodes[season["no"].to_i] = season["episode"].group_by{ |i|
i["seasonnum"].to_i
}
}
ap episodes
Input data: http://services.tvrage.com/feeds/episode_list.php?sid=5410

Wild guess:
data.each { |season|
episodes[season["no"].to_i] = season["episode"].group_by{ |i|
i["seasonnum"].to_i
}.first
}

It looks like you're using group_by (array of entries with same key) when you really want index_by (one entry per key).
data.each {|season| episodes[season["no"].to_i] = season["episode"].index_by {|i| i["seasonnum"].to_i}}
NOTE: If you can have MORE than one episode with the same seasonnum, you SHOULD use group by and have an array of values here. If you're just building a hash of episodes with a convenient lookup (one to one mapping), then index_by is what you want.

Related

how can i perform a query on a hash, and get result another hash?

I am trying to do a query against a ruby hash, which is much alike this:
{"client1" => {"tag" => "13", "host" => "client1.example.com", ...}, "client2" => {"tag" => "11", ...} }
and I would like to map it to only the client names with their tags, like this:
{"client1" => "13", "client2" => "11"}
I have been struggeling with .each and .select and .find but haven't figured it out yet. I am pretty sure it is not that hard, does anybody know? Thanks
You could do the same as below
data = {
"client1" => {"tag" => "13", "host" => "client1.example.com"},
"client2" => {"tag" => "11"}
}
desired_data = Hash.new
data.each do |k,v|
desired_data[k] = v["tag"]
end
desired_data will contain your result.
As suggested by #sawa you could also use
data.each_with_object({}){|(k, v), h| h[k] = v["tag"]}
Use map:
test_hash = {"client1" => {"tag" => "13", "host" => "client1.example.com"}, "client2" => {"tag" => "11"} }
test_hash.map{|k,v| [k, v['tag']]}.to_h
#=> {"client1"=>"13", "client2"=>"11"}
One way is to merge the hash with itself, using the form of Hash#merge that employs a block to determine the values of keys that are present in both hashes being merged, which in this case is all keys.
h = {"client1" => {"tag" => "13", "host" => "client1.example.com"},
"client2" => {"tag" => "11"} }
h.merge(h) { |*,v| v["tag"] }
#=> {"client1"=>"13", "client2"=>"11"}
As explained in the doc, the block has three variables, often written |key, old_value, new_value|. Here old_value and new_value are the same. The asterisk in |*, new_value| is a placeholder for all but the last block variable.

How to generate direct access keys to nested hash which contains hash and arrays as values?

I want to compare two XML files where one is input and the other is output. I am converting both into a hash.
My idea is to get all the keys from the input XML converted to hash, and search each key in both the input and output hashes for their respective key/value pairs.
I have a hash:
{
"requisition_header" => {
"requested_by" => {"login" => "coupasupport"},
"department" => {"name" => "Marketing"},
"ship_to_address" => {"name" => "Address_1431693296"},
"justification" => nil,
"attachments" => [],
"requisition_lines" => [
{
"description" => "Cleaning Services for Building A",
"line_num" => 1,
"need_by_date" => 2010-09-23 07:00:00 UTC,
"source_part_num" => nil,
"supp_aux_part_num" => nil,
"unit_price" => #<BigDecimal:a60520c,'0.3E4',9(18)>,
"supplier" => {"name" => "amazon.com"},
"account" => {
"code" => "SF-Marketing-Indirect",
"account_type" => {"name" => "Ace Corporate"}
},
"currency" => {"code" => "USD"},
"payment_term" => {"code" => "Net 30"},
"shipping_term" => {"code" => "Standard"},
"commodity" => {"name" => "Marketing-Services"}
}
]
}
}
It is nested and all the values are not directly accessible.
I want a way to generate direct access to each value in the hash.
For example:
requisition_header.requested_by.login
will access "coupasupport".
requisition_header.department.name
will access "Marketing".
requisition_header.requisition_lines[0].description
will access "Cleaning Services for Building A".
requisition_header.requisition_lines[0].line_num
will access "1".
requisition_header.requisition_lines[0].need_by_date
will access "2010-09-23 07:00:00 UTC".
Each key built can be used to search for the value directly inside the hash.
That could be done with the following method, that translates the nested hash into nested OpenStructs:
require 'ostruct'
def deep_structify(hash)
result = {}
hash.each do |key, value|
result[key] = value.is_a?(Hash) ? deep_structify(value) : value
end if hash
OpenStruct.new(result)
end
hash = {"requisition_header"=>{"requested_by"=>{"login"=>"coupasupport"}, "department"=>{"name"=>"Marketing"}, "ship_to_address"=>{"name"=>"Address_1431693296"}, "justification"=>nil, "attachments"=>[], "requisition_lines"=>[{"description"=>"Cleaning Services for Building A", "line_num"=>1, "need_by_date"=>2010-09-23 07:00:00 UTC, "source_part_num"=>nil, "supp_aux_part_num"=>nil, "unit_price"=>#<BigDecimal:a60520c,'0.3E4',9(18)>, "supplier"=>{"name"=>"amazon.com"}, "account"=>{"code"=>"SF-Marketing-Indirect", "account_type"=>{"name"=>"Ace Corporate"}}, "currency"=>{"code"=>"USD"}, "payment_term"=>{"code"=>"Net 30"}, "shipping_term"=>{"code"=>"Standard"}, "commodity"=>{"name"=>"Marketing-Services"}}]}}
struct = deep_structify(hash)
struct.requisition_header.department.name
#=> "Marketing"
You can do it by overriding OpenStruct#new as well,
require 'ostruct'
class DeepStruct < OpenStruct
def initialize(hash=nil)
#table = {}
#hash_table = {}
if hash
hash.each do |k,v|
#table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v)
#hash_table[k.to_sym] = v
new_ostruct_member(k)
end
end
end
def to_h
#hash_table
end
end
Now you can do:
require 'deep_struct'
hash = {"requisition_header"=>{"requested_by"=>{"login"=>"coupasupport"}, "department"=>{"name"=>"Marketing"}, "ship_to_address"=>{"name"=>"Address_1431693296"}, "justification"=>nil, "attachments"=>[], "requisition_lines"=>[{"description"=>"Cleaning Services for Building A", "line_num"=>1, "need_by_date"=>2010-09-23 07:00:00 UTC, "source_part_num"=>nil, "supp_aux_part_num"=>nil, "unit_price"=>#<BigDecimal:a60520c,'0.3E4',9(18)>, "supplier"=>{"name"=>"amazon.com"}, "account"=>{"code"=>"SF-Marketing-Indirect", "account_type"=>{"name"=>"Ace Corporate"}}, "currency"=>{"code"=>"USD"}, "payment_term"=>{"code"=>"Net 30"}, "shipping_term"=>{"code"=>"Standard"}, "commodity"=>{"name"=>"Marketing-Services"}}]}}
mystruct = DeepStruct.new hash
mystruct.requisition_header.requested_by.login # => coupasupport
mystruct.requisition_header.to_h # => {"requested_by"=>{"login"=>"coupasupport"}
You could use BasicObject#method_missing:
Code
class Hash
def method_missing(key,*args)
(args.empty? && key?(key)) ? self[key] : super
end
end
Example
hash = { animals: {
pets: { dog: "Diva", cat: "Boots", python: "Stretch" },
farm: { pig: "Porky", chicken: "Little", sheep: "Baa" }
},
finishes: {
tinted: { stain: "Millers", paint: "Oxford" },
clear: { lacquer: "Target", varnish: "Topcoat" }
}
}
hash.finishes.tinted.stain
#=> "Millers
hash.animals.pets.cat
#=> "Boots"
hash.animals.pets
#=> {:dog=>"Diva", :cat=>"Boots", :python=>"Stretch"}
hash.animals
#=> {:pets=>{:dog=>"Diva", :cat=>"Boots", :python=>"Stretch"},
# :farm=>{:pig=>"Porky", :chicken=>"Little", :sheep=>"Baa"}}
Reader challenge
There is a potential "gotcha" with this approach. I leave it to the reader to identify it. My example contains a clue. (Mind you, there may be other problems I haven't thought of.)

rails + activerecord: how create a hash from table with particular field's value as key

Given a table ZipCodeInfos with fields zipcode, state, city (all strings), where zipcode is unique:
zipcode,city,state
"10000", "Fooville", "AA"
"10001", "Smallville", "AA"
"10002", "Whoville", "BB"
What is the fastest way to generate a hash object of the entire table where the zipcode is a key like this:
{ "10000" => {:city => "Fooville", :state => "AA" },
"10001" => {:city => "Smallville", :state => "AA" },
"10002" => {:city => "Whoville", :state => "BB" } }
I know for a given record I can use .attributes to generate a hash with key,value pairs of field-names, field-values, for example Zipcode.first.attributes gives me
{"id" => 1, "zipcode" => "10000", "city" => "Fooville", "state => "AA" }
But, short of brute force iterating over each record (via .map), I cannot quite figure out how to create the desired hash with the zipcode as the key for each node of the hash.
This is the best I could come up with, and I suspect there is some nifty Ruby goodness that is faster?
zip_info_hash = {}
ZipCodeInfo.all.map{|x| zip_info_hash[x.zip] =
{'state' => x.state, 'city' => x.city }}
You could also try:
ZipCodeInfos.all.group_by &:zipcode
will get you a hash of zip code to array of ZipCodeInfos activerecords.
You can use inject method.
Here is what I generally use.
def visitors_name_email
visitors.inject({}) do |result, visitor|
result.merge(visitor.name => visitor.email)
end
end
I can't think of a way to avoid map here. I'd make only some minor changes to your code:
zip_info=Hash[*ZipCodeInfo.all
.map{|x| [x.zip, {:city => x.city, :state => x.state}]}
.flatten]

Reverse a hash in Ruby

How would I reverse the elements in the hash, keeping the same values and keys, but reversing their order in the hash.
Like so:
{ "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
And convert that to:
{ "1" => "spider", "lala" => "54", "10" => "cool", "4" => "happiness" }
Or, perhaps I could run a each loop backwards, starting from the last element in the hash, rather than the first?
You could convert the Hash to an Array, reverse that, and then convert it back to a Hash:
reversed_h = Hash[h.to_a.reverse]
Hash#to_a gives you an array of arrays, the inner arrays are simple [key,value] pairs, then you reverse that array using Array#reverse, and Hash[] converts the [key,value] pairs back into a Hash.
Ruby 2.1 adds an Array#to_h method so you can now say:
reversed_h = h.to_a.reverse.to_h
In Ruby 2.1+ you can combine reverse_each and to_h:
{foo: 1, bar: 2}.reverse_each.to_h
#=> {:bar=>2, :foo=>1}
In pure ruby, you can do it by hash.map(&:reverse).to_h or hash.reverse_each.to_h
In rails, you can do it by hash.invert
hash = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
reversed_hash = Hash[hash.to_a.reverse]
h = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
p Hash[h.reverse_each.map{|e| e}]
#=> {"1"=>"spider", "lala"=>"54", "10"=>"cool", "4"=>"happiness"}
But this leaves a bad taste (just like the other answers, which work fine just like this one). If you have to do this, it could be an indication that a Hash was not the best choice.
Alternatively, you can use reduce and merge to add the item to the front of a new hash:
hash = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
hash.reduce({}){ |memo, object| Hash[*object].merge(memo) }
but, that's crazy :D
reversed_h = Hash[h.to_a.collect(&:reverse)]
In Ruby 1.8.7, the order of elements in a hash is documented to be not under our control, so none of the above methods work. In Ruby 1.9.3, things work and are documented in the way that the other answers rely upon.
$ irb1.8
h = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
Hash[h.to_a().reverse()]
=> {"lala"=>"54", "1"=>"spider", "10"=>"cool", "4"=>"happiness"}
quit
$ irb1.9.1
h = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "spider" }
Hash[h.to_a().reverse()]
=>{"1"=>"spider", "lala"=>"54", "10"=>"cool", "4"=>"happiness"}
The Ruby 1.8.7 way was ingrained so firmly for me that I misunderstood the question for quite some time. I thought it requested a way to Hash#invert: ie to transform the hash such that the range maps to the domain. That method discards duplicates. Luís Ramalho proffers a method that doesn't, but it's a bit clunky. This is a little shorter:
$ irb
def invertWithDuplicates(original)
inverse = Hash.new() { |hash, key| hash[key] = []; }
original.each_pair() { |key, value| inverse[value].push(key); }
return inverse
end
h = { "4" => "happiness", "10" => "cool", "lala" => "54", "1" => "cool" }
invertWithDuplicates(h)
=> {"happiness"=>["4"], "cool"=>["1", "10"], "54"=>["lala"]}
Sorry to drift away from the OP's intended topic, though I submit that this does fit the post's title "Reverse a hash in Ruby".
if need:
hash = {:a => :x, :b => :y, :c => :y, :d => :z}
to:
{:x => [:a], :y => [:b, c], :z => [:d] }
can:
h={};hash.to_a.each{|e|h[e[1]]||=[];h[e[1]]<<e[0]};h

Shifting a specific matching key-value pair from Ruby hash

I have a Ruby hash:
#tags = { "project_status" => { "title" => "Project status" },
"milestones" => { "title" => "Milestones"},
"lessons" => { "title" => "Lessons"},
"tasks" => { "title" => "Tasks"} }
I'd like to shift specific key-value pairs out of this hash.
e.g. if I am interested in "milestones" tags, then shift on the hash will give me:
=> ["milestones", {"title"=>"Milestones"}]
Which is exactly what I want.
Except that I can't work out how to select a specific key-value pair.
I could write something to iterate through the hash until I find the matching key and then call shift, but I'm assuming there's a cleaner "Ruby way" to do this :)
delete is probably what you're looking for. It removes corresponding key from the hash (whereas shift removes item from an array)
tags = { "project_status" => { "title" => "Project status" },
"milestones" => { "title" => "Milestones"},
"lessons" => { "title" => "Lessons"},
"tasks" => { "title" => "Tasks"} }
def shift hash, key
[key, hash.delete(key)] # removes key/value pair
# [key, hash[key]] # leaves key/value pair
end
shift tags, 'milestones' # => ["milestones", {"title"=>"Milestones"}]
tags # => {"project_status"=>{"title"=>"Project status"}, "lessons"=>{"title"=>"Lessons"}, "tasks"=>{"title"=>"Tasks"}}

Resources