ruby set key of hash for array of hashes - ruby

Given the following array of hashes, how can I make a new hash with friend_id as the key and dist the value?
results = [
{"user_id"=>"18", "friend_id"=>"17", "dist"=>"1"},
{"user_id"=>"18", "friend_id"=>"42", "dist"=>"1"},
{"user_id"=>"18", "friend_id"=>"43", "dist"=>"1"},
{"user_id"=>"18", "friend_id"=>"46", "dist"=>"2"}
]
desired_hash = {"17" => "1", "42" => "1", "43" => "1", "46" => "2"}
I've tried map but the values are then in an array. I also tried to flatten that result but it flattened the key instead of the value

results.each_with_object({}) { |g,h| h[g["friend_id"]] = g["dist"] }
or
results.each_with_object({}) { |g,h| h.update(g["friend_id"]=> g["dist"]) }

For this use case, I think it would be simpler and more readable to just use #each:
desired_hash = Hash.new
results.each {|h| desired_hash[h["friend_id"]] = h["dist"]}
Then, desired_hash is:
#=> {"17"=>"1", "42"=>"1", "43"=>"1", "46"=>"2"}

You can use Enumerable#inject method.
results.inject({}) {|sum, e| sum.merge({e["friend_id"] => e["dist"]})}
# => {"17"=>"1", "42"=>"1", "43"=>"1", "46"=>"2"}

results.map {|h| [h['friend_id'], h['dist']]} .to_h
Although I probably like #CarySwoveland 's answer better, on the lines of:
results.each_with_object({}) {|h, n| n[h['friend_id']] = h['dist']}

Simply:
desired_hash = Hash[results.map{ |h| [ h['friend_id'], h['dist']] }]
or as Victor suggests
desired_hash = Hash[results.map{ |x| x.values_at('friend_id', 'dist') }]

Related

Modifying an Array

I have an Array like this
example_array = ['dog', 'cat', 'snake']
And I am trying to append the timestamp to each element of the array and the output should look like
example_array = [{'dog': 'time_stamp'},{'cat':'time_stamp'},{'snake':'time_stamp'}]
I've tried this but the output is incorrect:
a = {}
example_array.each_with_index do |element, i|
a.merge!("#{element}": "#{Time.now}")
example_array.delete_at(i)
end
Can anyone suggest me a solution in ruby?
I have tried a lot of ways but couldn't obtain the output like above.
Aditha,
How about this?
array = ["cat", "hat", "bat", "mat"]
hash = []
hash.push(Hash[array.collect { |item| [item, Time.now] } ])
OUTPUT: => [{"cat"=>"2018-02-28 04:23:08 UTC", "hat"=>"2018-02-28 04:23:08 UTC", "bat"=>"2018-02-28 04:23:08 UTC", "mat"=>"2018-02-28 04:23:08 UTC"}]
Instead of item.upcase you would insert your timestamp info. It gives me hashes inside of array.
example_array.product([Time.now]).map { |k,v| { k.to_sym=>v }}
#=> [{:dog=>2018-02-27 20:42:56 -0800},
# {:cat=>2018-02-27 20:42:56 -0800},
# {:snake=>2018-02-27 20:42:56 -0800}
]Note this ensures that all values (timestamps) are equal.
only weird thing is that you have to use => instead of :
arr = ['dog', 'cat', 'snake']
arr2 = []
for index in 0 ... arr.size
arr2.push({arr[index] => Time.now})
end
puts arr2
['dog', 'cat', 'snake'].map{|e| [{e.to_sym => "time_stamp"}]}
# => [[{:dog=>"time_stamp"}], [{:cat=>"time_stamp"}], [{:snake=>"time_stamp"}]]

How to convert, in Ruby, a list of key value pairs to a Hash, such that values with duplicate keys are stored in an array?

Given a list key-value pairs, in the form of an array of arrays - e.g. [ ["key1","value1"], ["key2","value2"], ["key1", "value3"] ], how to convert these to a Hash that stores all the values, in the most elegant way?
For the above example, I would want to get { "key1" => [ "value1", "value3" ], "key2" => [ "value2" ] }.
[["key1","value1"], ["key2","value2"], ["key1", "value3"]]
.group_by(&:first).each{|_, v| v.map!(&:last)}
Another way is to use the form of Hash#update (aka merge!) that uses a block to determine the values of keys that are in both hashes being merged.
arr = [ ["key1","value1"], ["key2","value2"], ["key1", "value3"] ]
arr.each_with_object({}) { |(k,v),h| h.update(k=>[v]) { |_,o,n| o+n } }
#=> {"key1"=>["value1", "value3"], "key2"=>["value2"]}
Hash.new{ |h,k| h[k]=[] }.tap{ |h| array.each{ |k,v| h[k] << v } }
OR
c = Hash.new {|h,k| h[k] = [] }
array.each{|k, v| c[k] << v}
My best solution so far is this:
kvlist.inject(Hash.new([])) do |memo,a|
memo[a[0]] = (memo[a[0]] << a[1])
memo
end
Which I think is not very good.

How to map a Ruby hash?

Is there a better way to map a Ruby hash? I want to iterate over each pair and collect the values. Perhaps using tap?
hash = { a:1, b:2 }
output = hash.to_a.map do |one_pair|
k = one_pair.first
v = one_pair.last
"#{ k }=#{ v*2 }"
end
>> [
[0] "a=2",
[1] "b=4"
]
Ruby's hash includes the Enumerable module which includes the map function.
hash = {a:1, b:2}
hash.map { |k, v| "#{k}=#{v * 2}" }
Enumerable#map | RubyDocs
Err, yes, with map, invoked directly on the hash:
{ a:1, b:2 }.map { |k,v| "#{k}=#{v*2}" } # => [ 'a=2', 'b=4' ]
hash = { a:1, b:2 }
hash.map{|k,v| [k,v*2]* '='}
# => ["a=2", "b=4"]

Best way to merge key value pairs in a hash based on number of values for that key in Ruby

I have a hash of arrays in ruby as :
#people = { "a" => ["john", "mark", "tony"], "b"=> ["tom","tim"],
"c" =>["jane"], "others"=>["rob", "ryan"] }
I would like to merge all key value pairs where there are less than 3 items in the array for a particular keys values. They should be merged into the key called "others" to give roughly the result of
#people = { "a" => ["john", "mark", "tony"],
"others"=> ["rob", "ryan", "tom", "tim", "jane"] }
Using the following code is problematic as duplicate key values in a hash cannot exist:
#people = Hash[#people.map{|k,v| v.count<3 ? ["others",v] : [k,v]} ] %>
Whats the best way to elegantly solve this?
You almost have it, the problem is, as you notice, that you can't build the Hash's key/value pairs on the fly because of duplicates. One way around the problem is to start out with the skeleton of what you're trying to build:
#people = #people.each_with_object({ 'others' => [ ] }) do |(k,v), h|
if(v.length >= 3)
h[k] = v
else
h['others'] += v
end
end
Or, if you don't like each_with_object, you could:
h = { 'others' => [ ] }
#people.each do |k, v|
# as above
end
#people = h
Or you could use pretty much the same structure with inject (taking care, as usual, to return the right thing from the block).
There are certainly other ways to do this but these approaches are pretty clear and easy to understand; IMO clarity should be your first goal.
try:
>> #people = { "a" => ["john", "mark", "tony"], "b"=> ["tom","tim"],
"c" =>["jane"], "others"=>["rob", "ryan"] }
>> #new_people = {"others" => []}
>> #people.each_pair {|k,v| (v.size >= 3 && k!="others") ? #new_people.merge!(k=>v) : #new_people['others']+= v}
>> #new_people
=> {"others"=>["rob", "ryan", "jane", "tom", "tim"], "a"=>["john", "mark", "tony"]}
Hash[ #people.group_by { |k,v| v.size < 3 ? 'others' : k }.
map { |k,v| [k, v.flat_map(&:last)] } ]
=> {"a"=>["john", "mark", "tony"],
"others"=>["tom", "tim", "jane", "rob", "ryan"]}
What about this:
> less_than_three, others = #people.partition {|(key, values)| values.size >= 3 }
> Hash[less_than_three]
# => {"a"=>["john", "mark", "tony"]}
> Hash["others" => others.map {|o| o.last}.flatten]
# => {"others"=>["tom", "tim", "jane", "rob", "ryan"]}
#people[:others] = []
#people.each do |k, v|
#people[:others] |= #people.delete(k) if v.size < 3
end
#people.inject({}) do |m, (k, v)|
m[i = v.size >= 3 ? k : 'others'] = m[i].to_a + v
m
end

How do I convert a Ruby hash so that all of its keys are symbols?

I have a Ruby hash which looks like:
{ "id" => "123", "name" => "test" }
I would like to convert it to:
{ :id => "123", :name => "test" }
hash = {"apple" => "banana", "coconut" => "domino"}
Hash[hash.map{ |k, v| [k.to_sym, v] }]
#=> {:apple=>"banana", :coconut=>"domino"}
#mu is too short: Didn't see word "recursive", but if you insist (along with protection against non-existent to_sym, just want to remind that in Ruby 1.8 1.to_sym == nil, so playing with some key types can be misleading):
hash = {"a" => {"b" => "c"}, "d" => "e", Object.new => "g"}
s2s =
lambda do |h|
Hash === h ?
Hash[
h.map do |k, v|
[k.respond_to?(:to_sym) ? k.to_sym : k, s2s[v]]
end
] : h
end
s2s[hash] #=> {:d=>"e", #<Object:0x100396ee8>=>"g", :a=>{:b=>"c"}}
If you happen to be in Rails then you'll have symbolize_keys:
Return a new hash with all keys converted to symbols, as long as they respond to to_sym.
and symbolize_keys! which does the same but operates in-place. So, if you're in Rails, you could:
hash.symbolize_keys!
If you want to recursively symbolize inner hashes then I think you'd have to do it yourself but with something like this:
def symbolize_keys_deep!(h)
h.keys.each do |k|
ks = k.to_sym
h[ks] = h.delete k
symbolize_keys_deep! h[ks] if h[ks].kind_of? Hash
end
end
You might want to play with the kind_of? Hash to match your specific circumstances; using respond_to? :keys might make more sense. And if you want to allow for keys that don't understand to_sym, then:
def symbolize_keys_deep!(h)
h.keys.each do |k|
ks = k.respond_to?(:to_sym) ? k.to_sym : k
h[ks] = h.delete k # Preserve order even when k == ks
symbolize_keys_deep! h[ks] if h[ks].kind_of? Hash
end
end
Note that h[ks] = h.delete k doesn't change the content of the Hash when k == ks but it will preserve the order when you're using Ruby 1.9+. You could also use the [(key.to_sym rescue key) || key] approach that Rails uses in their symbolize_keys! but I think that's an abuse of the exception handling system.
The second symbolize_keys_deep! turns this:
{ 'a' => 'b', 'c' => { 'd' => { 'e' => 'f' }, 'g' => 'h' }, ['i'] => 'j' }
into this:
{ :a => 'b', :c => { :d => { :e => 'f' }, :g => 'h' }, ['i'] => 'j' }
You could monkey patch either version of symbolize_keys_deep! into Hash if you really wanted to but I generally stay away from monkey patching unless I have very good reasons to do it.
If you are using Rails >= 4 you can use:
hash.deep_symbolize_keys
hash.deep_symbolize_keys!
or
hash.deep_stringify_keys
hash.deep_stringify_keys!
see http://apidock.com/rails/v4.2.1/Hash/deep_symbolize_keys
Just in case you are parsing JSON, from the JSON docs you can add the option to symbolize the keys upon parsing:
hash = JSON.parse(json_data, symbolize_names: true)
Victor Moroz provided a lovely answer for the simple recursive case, but it won't process hashes that are nested within nested arrays:
hash = { "a" => [{ "b" => "c" }] }
s2s[hash] #=> {:a=>[{"b"=>"c"}]}
If you need to support hashes within arrays within hashes, you'll want something more like this:
def recursive_symbolize_keys(h)
case h
when Hash
Hash[
h.map do |k, v|
[ k.respond_to?(:to_sym) ? k.to_sym : k, recursive_symbolize_keys(v) ]
end
]
when Enumerable
h.map { |v| recursive_symbolize_keys(v) }
else
h
end
end
Try this:
hash = {"apple" => "banana", "coconut" => "domino"}
# => {"apple"=>"banana", "coconut"=>"domino"}
hash.tap do |h|
h.keys.each { |k| h[k.to_sym] = h.delete(k) }
end
# => {:apple=>"banana", :coconut=>"domino"}
This iterates over the keys, and for each one, it deletes the stringified key and assigns its value to the symbolized key.
If you're using Rails (or just Active Support):
{ "id" => "123", "name" => "test" }.symbolize_keys
Starting with Ruby 2.5 you can use the transform_key method.
So in your case it would be:
h = { "id" => "123", "name" => "test" }
h.transform_keys!(&:to_sym) #=> {:id=>"123", :name=>"test"}
Note: the same methods are also available on Ruby on Rails.
Here's a Ruby one-liner that is faster than the chosen answer:
hash = {"apple" => "banana", "coconut" => "domino"}
#=> {"apple"=>"banana", "coconut"=>"domino"}
hash.inject({}){|h,(k,v)| h[k.intern] = v; h}
#=> {:apple=>"banana", :coconut=>"domino"}
Benchmark results:
n = 100000
Benchmark.bm do |bm|
bm.report { n.times { hash.inject({}){|h,(k,v)| h[k.intern] = v; h} } }
bm.report { n.times { Hash[hash.map{ |k, v| [k.to_sym, v] }] } }
end
# => user system total real
# => 0.100000 0.000000 0.100000 ( 0.107940)
# => 0.120000 0.010000 0.130000 ( 0.137966)
I'm partial to:
irb
ruby-1.9.2-p290 :001 > hash = {"apple" => "banana", "coconut" => "domino"}
{
"apple" => "banana",
"coconut" => "domino"
}
ruby-1.9.2-p290 :002 > hash.inject({}){ |h, (n,v)| h[n.to_sym] = v; h }
{
:apple => "banana",
:coconut => "domino"
}
This works because we're iterating over the hash and building a new one on the fly. It isn't recursive, but you could figure that out from looking at some of the other answers.
hash.inject({}){ |h, (n,v)| h[n.to_sym] = v; h }
You can also extend core Hash ruby class placing a /lib/hash.rb file :
class Hash
def symbolize_keys_deep!
new_hash = {}
keys.each do |k|
ks = k.respond_to?(:to_sym) ? k.to_sym : k
if values_at(k).first.kind_of? Hash or values_at(k).first.kind_of? Array
new_hash[ks] = values_at(k).first.send(:symbolize_keys_deep!)
else
new_hash[ks] = values_at(k).first
end
end
new_hash
end
end
If you want to make sure keys of any hash wrapped into arrays inside your parent hash are symbolized, you need to extend also array class creating a "array.rb" file with that code :
class Array
def symbolize_keys_deep!
new_ar = []
self.each do |value|
new_value = value
if value.is_a? Hash or value.is_a? Array
new_value = value.symbolize_keys_deep!
end
new_ar << new_value
end
new_ar
end
end
This allows to call "symbolize_keys_deep!" on any hash variable like this :
myhash.symbolize_keys_deep!
def symbolize_keys(hash)
new={}
hash.map do |key,value|
if value.is_a?(Hash)
value = symbolize_keys(value)
end
new[key.to_sym]=value
end
return new
end
puts symbolize_keys("c"=>{"a"=>2,"k"=>{"e"=>9}})
#{:c=>{:a=>2, :k=>{:e=>9}}}
Here's my two cents,
my version of symbolize_keys_deep! uses the original symbolize_keys! provided by rails and just makes a simple recursive call to Symbolize sub hashes.
def symbolize_keys_deep!(h)
h.symbolize_keys!
h.each do |k, v|
symbolize_keys_deep!(v) if v.is_a? Hash
end
end
Facets' Hash#rekey is also a worth mentioning.
Sample:
require 'facets/hash/rekey'
{ "id" => "123", "name" => "test" }.deep_rekey
=> {:id=>"123", :name=>"test"}
There is also a recursive version:
require 'facets/hash/deep_rekey'
{ "id" => "123", "name" => {"first" => "John", "last" => "Doe" } }.deep_rekey
=> {:id=>"123", :name=>{:first=>"John", :last=>"Doe"}}
Here's a little recursive function to do a deep symbolization of the keys:
def symbolize_keys(hash)
Hash[hash.map{|k,v| v.is_a?(Hash) ? [k.to_sym, symbolize_keys(v)] : [k.to_sym, v] }]
end

Resources