Ruby: merge two hash as one and with value connected - ruby

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

Related

Manipulate hash in Ruby

I have a hash that looks like
{
"lt"=>"456",
"c"=>"123",
"system"=>{"pl"=>"valid-player-name", "plv"=>"player_version_1"},
"usage"=>{"trace"=>"1", "cq"=>"versionid", "stream"=>"od",
"uid"=>"9", "pst"=>[["0", "1", "10"]], "dur"=>"0", "vt"=>"2"}
}
How can I go about turning it into a hash that looks like
{
"lt"=>"456",
"c"=>"123",
"pl"=>"valid-player-name",
"plv"=>"player_version_1",
"trace"=>"1",
"cq"=>"versionid",
"stream"=>"od",
"uid"=>"9",
"pst"=>[["0", "1", "10"]], "dur"=>"0", "vt"=>"2"
}
I basically want to get rid of the keys system and usage and keep what's nested inside them
"Low-tech" version :)
h = { ... }
h.merge!(h.delete('system'))
h.merge!(h.delete('usage'))
Assuming no rails:
hash.reject { |key, _| %w(system usage).include? key }.merge(hash['system']).merge(hash['usage'])
With active support:
hash.except('system', 'usage').merge(hash['system']).merge(hash['usage'])
A more generic version.
Merge any key that contains a hash:
h = { ... }
hnew = h.inject(h.dup) { |h2, (k, v)|
h2.merge!(h2.delete(k)) if v.is_a?(Hash)
h2
}
Assuming that your data has the same structure each time, I might opt for something simple and easy to understand like this:
def manipulate_hash(h)
{
"lt" => h["lt"],
"c" => h["c"],
"pl" => h["system"]["pl"],
"plv" => h["system"]["plv"],
"trace" => h["usage"]["trace"],
"cq" => h["usage"]["cq"],
"stream" => h["usage"]["stream"],
"uid" => h["uid"],
"pst" => h["pst"],
"dur" => h["dur"],
"vt" => h["vt"]
}
end
I chose to make the hash using one big hash literal expression that spans multiple lines. If you don't like that, you could build it up on multiple lines like this:
def manipulate_hash
r = {}
r["lt"] = h["lt"]
r["c"] = h["c"]
...
r
end
You might consider using fetch instead of the [] angle brackets. That way, you'll get an exception if the expected key is missing from the hash. For example, replace h["lt"] with h.fetch("lt").
If you plan to have an arbitrarily large list of keys to merge, this is an easily scaleable method:
["system", "usage"].each_with_object(myhash) do |key|
myhash.merge!(myhash.delete(key))
end

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.

visiting hash with keys from array

I have a big hash with lots of nested key value pairs.
Eg.
h = {"foo" => {"bar" => {"hello" => {"world" => "result" } } } }
Now I want to access result and I have keys for that in array in proper sequence.
keys_arr = ["foo", "bar", "hello", "world"]
The motive is clear, I want to do following:
h["foo"]["bar"]["hello"]["world"]
# => "result"
But I don't know how to do this. I am currently doing:
key = '["' + keys_arr.join('"]["') + '"]'
eval("h"+key)
# => "result"
Which looks like a hack. Also it greatly reduces my ability to work with hash in real environment.
Please suggest alternate and better ways.
Using Enumerable#inject (or Enumerable#reduce):
h = {"foo" => {"bar" => {"hello" => {"world" => "result" } } } }
keys_arr = ["foo", "bar", "hello", "world"]
keys_arr.inject(h) { |x, k| x[k] }
# => "result"
UPDATE
If you want to do something like: h["foo"]["bar"]["hello"]["world"] = "ruby"
innermost = keys_arr[0...-1].inject(h) { |x, k| x[k] } # the innermost hash
innermost[keys_arr[-1]] = "ruby"
keys_arr.inject(h, :[])
will do
Another way:
h = {"foo" => {"bar" => {"hello" => {"world" => 10 } } } }
keys = ["foo", "bar", "hello", "world"]
result = h
keys.each do |key|
result = result[key]
end
puts result #=>10
If the key may not exist, see here:
Dealing with many [...] in Ruby

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

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.

Ruby / Remove everything after a matched key / array of hashes

Let's say I have the following array of hashes:
h = [{"name" => "bob"}, {"car" => "toyota"}, {"age" => "25"}]
And I have the following key to match:
k = 'car'
How do I match the 'k' to 'h' and have delete every element after the match so that it returns:
h = [{"name" => "bob"}, {"car" => "toyota"}]
Just convert hash to array, do your task and then convert back
h = {"name" => "bob", "car" => "toyota", "age" => "25"}
array = h.to_a.flatten
index = array.index('car') + 1
h = Hash[*array[0..index]]
=> {"name"=>"bob", "car"=>"toyota"}
By the way, the hash is ordered only since Ruby 1.9
ar = [{"name" => "bob"}, {"car" => "toyota"}, {"age" => "25"}]
p ar[0 .. ar.index{|h| h.key?('car')}] #=>[{"name"=>"bob"}, {"car"=>"toyota"}]
I like megas' version, as its short and to the point. Another approach, which would be more explicit, would be iterating over the keys array of each hash. The keys of a hash are maintained in an ordered array (http://ruby-doc.org/core-1.9.3/Hash.html). They are ordered by when they were first entered. As a result, you can try the following:
newArray = Array.new
h.each do |hash| # Iterate through your array of hashes
newArray << hash
if hash.has_key?("car") # check if this hash is the "car" hash.
break # exits the block
end
end
This all depends, of course, on whether the array was created in the proper order. If it was, then you're golden.
A hash is unordered set by definition, so what you request is somewhat undefined. However you can do something like a hack:
h = {"name" => "bob", "car" => "toyota", "age" => "25"}
matched = false
key_given = "car"
h.each do |k,v|
if matched
h.delete(k)
end
if k == key_given
matched = true
next
end
end
I'm pretty late to the party here. I was looking for a solution to this same problem, but I didn't love these answers. So, here's my approach:
class Array
def take_until(&blk)
i = find_index &blk
take(i + 1)
end
end
h = [{"name" => "bob"}, {"car" => "toyota"}, {"age" => "25"}]
k = 'car'
h.take_until { |x| x.has_key?(k) }
=> [{"name"=>"bob"}, {"car"=>"toyota"}]

Resources