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"}}
Related
I have this structure in Ruby:
[
{“type"=>"Action", "parameterized"=>false, "groups"=>["feed"], "value"=>"feed”},
{"type"=>"State", "parameterized"=>true, "groups"=>["group"], "value"=>"group:%1s”},
{"type"=>"Action", "parameterized"=>false, "groups"=>["challenge", "build"], "value"=>"challenge:build”},
{"type"=>"Action", "parameterized"=>false, "groups"=>["challenge", "decorate"], "value"=>"challenge:decorate”},
{"type"=>"Action", "parameterized"=>false, "groups"=>["report"], "value"=>"report”},
{"type"=>"Action", "parameterized"=>false, "groups"=>["report", "complete"], "value"=>"report:complete”},
]
The elements in the array can come in any order, and I want it to be parsed into a tree structure so that if there are matching groups (each groups array do come in order where the first is the root and the next is a child of the first and the next again is the child of the one before and so on) are merged into the same branch in the tree. If there is a "parameterized" parameter, this also result in a extra branch in the tree. I want the structure above to end up line the following:
{
"Action" => {
"feed" => {
"value" => "feed"
},
"challenge" => {
"build" => {
"value" => "challenge:build"
}
"decorate" => {
"value" => "challenge:decorate"
}
},
"report" => {
"value" => "report",
"complete" => {
"value" => "report:complete"
}
}
},
"State" => {
"group" => {
"parameterized" => {
"value" => "group:%1s"
}
}
}
}
Any idea. how to do this in Ruby?
Best regards
Søren
First, let’s define the proc that will be finding buried groups inside the same type
deep_find = ->(hash, name) {
hash.detect { |k, v| k == name || v.is_a?(Hash) && deep_find.(v, name) }
}
Now we are ready to walk through the input hash either appending a tail to the found key or inserting the new key.
Objects in Ruby are mutable, and that will help us a lot; traversing the beast we’ll detect the needed hash and modify it inplace. This changes will be reflected in the whole.
input.each_with_object(Hash.new { |h, k| h[k] = h.dup.clear }) do |h, acc|
# let’s dig into the hash and find the _branch_ of interest
# it will return the hash _or_ `nil`
buried = deep_find.(acc[h['type']], '')
# if the target was found, we pivk it up for further modifications
# otherwise we start from the top level
target = buried ? buried.last : acc[h['type']]
# going down the tree of groups; each call to `inner[g]`
# will produce the nested hash, thanks to `default_proc`
# of the accumulator
target = h['groups'].reduce(target) { |inner, g| inner[g] }
# determine whether we should wrap the value with `parametrized`
# and eventually set it
(h['parameterized'] ? target['parameterized'] : target)['value'] = h['value']
end
#⇒ {"type1"=>{"group1"=>{"group2"=>{"value"=>"value1",
# "group7"=>{"group8"=>{"parameterized"=>{"value"=>"value3"}}}}}},
# "type2"=>{"group3"=>{"group4"=>{"group5"=>{"value"=>"value2"}}}}}
NB the trick with accumulator’s default proc (Hash.new { |h, k| h[k] = h.dup.clear }) allows not to bother with new values initialization, no matter how deep they are.
Suppose I have a hash,
hash = { "name" = > nil, "product" => nil , "price" => nil }
and an array
a = [ "Bob" , "Fryer" , "20$"]
I would like the output be hash
{ "name" => "Bob" , "product" => "Fryer" , "price" => "20$"}
Tried with zip, merge and map, couldn't get the right one
Try
Hash[hash.keys.zip(a)]
=> {"name"=>"Bob", "product"=>"Fryer", "price"=>"20$"}
You can get keys and zip it with array:
hash.keys.zip(a).to_h
=> {"name"=>"Bob", "product"=>"Fryer", "price"=>"20$"}
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]
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.
2 hash:
h1 = { "s1" => "2009-7-27", "s2" => "2010-3-6", "s3" => "2009-7-27" }
h2 = { "s1" => "12:29:15", "s2" => "10:00:17", "s3" => "12:25:52" }
I want to merge the two hash as one like this:
h = { "s1" => "2009-7-27 12:29:15",
"s2" => "2010-3-6 10:00:17",
"s3" => "2009-7-27 2:25:52" }
what is the best way to do this? thanks!
h = h1.merge(h2){|key, first, second| first + " " + second }
It will work if your keys are the same. In your code, they aren't ("s1" vs "s1="). Are they supposed to be the same keys?
You mean:
Hash[h1.map{|k,v| [k, "#{v} #{h2[k]}"]}]
=> {"s3"=>"2009-7-27 12:25:52", "s1"=>"2009-7-27 12:29:15", "s2"=>"2010-3-6 10:00:17"}
Note hashes are unordered, if you want an ordered hash you probably need to look at this