Make key a hash - ruby-on-rails

I have a current hash:
{
"2018-03-12"=>[{:date=>"2018-03-12", :net_revenue=>0.044}],
"2018-03-11"=>[{:date=>"2018-03-11", :net_revenue=>0.033}],
"2018-03-10"=>[{:date=>"2018-03-10", :net_revenue=>298.860}]
}
How do I make it look like this?
{
"2018-03-12"=>{:date=>"2018-03-12", :net_revenue=>0.044},
"2018-03-11"=>{:date=>"2018-03-11", :net_revenue=>206.008},
"2018-03-10"=>{:date=>"2018-03-10", :net_revenue=>298.860}
}

As simple as
hash.transform_values(&:first)
(ruby 2.4 and later)

hash = {
"2018-03-12"=>[{:date=>"2018-03-12", :net_revenue=>0.044}],
"2018-03-11"=>[{:date=>"2018-03-11", :net_revenue=>0.033}],
"2018-03-10"=>[{:date=>"2018-03-10", :net_revenue=>298.860}]
}
new_hash = hash.each { |k, v| hash[k] = v[0] }
The new_hash will look like:
new_hash = {
"2018-03-12"=>{:date=>"2018-03-12", :net_revenue=>0.044},
"2018-03-11"=>{:date=>"2018-03-11", :net_revenue=>0.033},
"2018-03-10"=>{:date=>"2018-03-10", :net_revenue=>298.860}
}

Other than making sure the data comes in the way you want it,
hash.each do |key, val|
hash[key] = val.first
end

Related

transform_keys for an array of hashes

I have the following array of hashes and I want to use transform_keys to strip the beginning of each key using a regex:
array_of_hashes = [{"a_0_abc"=>"1",
"a_0_def"=>"1",
"a_0_hij"=>"1",},
{"a_1_abc”=>"2",
"a_1_def"=>"2",
"a_1_hij"=>"2"}]
and I want the following:
transformed_hash_keys = [{"abc"=>"1",
"def"=>"1",
"hij"=>"1",},
{"abc"=>"2",
"def"=>"2",
"hij"=>"2"}]
I have the following method but it results in array_of_hashes instead of transformed_hash_keys:
def strip
s = array_of_hashes.each { |hash| hash.transform_keys { |key| key.sub(/^a_(\d+)_/, '') } }
end
Can anyone tell me what I'm doing wrong in this method?
transform_keys doesn't operate in place and each returns the original iterator, not the result of the block.
You could do what you want with map instead of each.
def strip
s = array_of_hashes.map { |hash| hash.transform_keys { |key| key.sub(/^a_(\d+)_/, '') } }
end
Or, you could use transform_keys! to modify the contents of array_of_hashes
def strip
s = array_of_hashes.each { |hash| hash.transform_keys! { |key| key.sub(/^a_(\d+)_/, '') } }
end
Here's a pure Ruby solution.
arr = [{ "a_0_abc"=>"1", "a_0_def"=>"1", "a_0_hij"=>"1" },
{ "a_1_abc"=>"2", "a_1_def"=>"2", "a_1_hij"=>"2" }]
arr.map { |h| h.map { |k,v| [k[/[[:alpha:]]+\z/], v] }.to_h }
#=> [{"abc"=>"1", "def"=>"1", "hij"=>"1"}, {"abc"=>"2", "def"=>"2", "hij"=>"2"}]
or
arr.map { |h| h.each_with_object({}) { |(k,v),g| g[k[/[[:alpha:]]+\z/]] = v } }
# => [{"abc"=>"1", "def"=>"1", "hij"=>"1"}, {"abc"=>"2", "def"=>"2", "hij"=>"2"}]
This lets you do any kind of transformation on the keys just passing a block to it:
def strip_keys(object)
deep_transform_keys_in_object!(object) { |key| key.sub(/^a_(\d+)_/, '') }
end
def deep_transform_keys_in_object!(object, &block)
case object
when Hash
object.keys.each do |key|
value = object.delete(key)
object[yield(key)] = deep_transform_keys_in_object!(value, &block)
end
object
when Array
object.map! { |e| deep_transform_keys_in_object!(e, &block) }
else
object
end
end

How to sort Ruby Hash based on date?

I have a hash object with the following structure:
{"action1"=>
{"2014-08-20"=>0,
"2014-07-26"=>1,
"2014-07-31"=>1
},
"action2"=>
{"2014-08-01"=>2,
"2014-08-20"=>2,
"2014-07-25"=>2,
"2014-08-06"=>1,
"2014-08-21"=>1
}
"action3"=>
{"2014-07-30"=>2,
"2014-07-31"=>1,
"2014-07-22"=>1,
}
}
I want to sort the hash based on the date and return back a Hash(Not array). The final result should be:
{"action1"=>
{"2014-07-26"=>1,
"2014-07-31"=>1,
"2014-08-20"=>0
},
"action2"=>
{"2014-07-25"=>2,
"2014-08-01"=>2,
"2014-08-06"=>2,
"2014-08-20"=>1,
"2014-08-21"=>1
}
"action3"=>
{"2014-07-22"=>1,
"2014-07-30"=>2,
"2014-07-31"=>1
}
}
Iterate over the hash, and for each value, sort.
h = {"action1"=>
{"2014-08-20"=>0,
"2014-07-26"=>1,
"2014-07-31"=>1
},
"action2"=>
{"2014-08-01"=>2,
"2014-08-20"=>2,
"2014-07-25"=>2,
"2014-08-06"=>1,
"2014-08-21"=>1
},
"action3"=>
{"2014-07-30"=>2,
"2014-07-31"=>1,
"2014-07-22"=>1,
}
}
h.each do |k, v|
h[k] = Hash[v.sort]
end
Here you need to iterate your hash and fetch the value than you need to apply sort_by function on each value so you will get your result
hashName.each do |key, hash|
Hash[hashName.sort_by{|k,v| k}]
end
This is all you need:
h.each { |k,v| h[k] = v.sort.to_h }
#=> {"action1"=>{"2014-07-26"=>1, "2014-07-31"=>1, "2014-08-20"=>0},
# "action2"=>{"2014-07-25"=>2, "2014-08-01"=>2, "2014-08-06"=>1,
# "2014-08-20"=>2, "2014-08-21"=>1},
# "action3"=>{"2014-07-22"=>1, "2014-07-30"=>2, "2014-07-31"=>1}}
Hash#to_h appeared in Ruby 2.0. For earlier versions, use the class method Hash::[]: i.e., replace v.sort.to_h with Hash[v.sort].

reversing hash.sort_by in ruby

Simple enough but Im drawing a blank on it.
#sortedHash = #otherHash.sort_by { |k,v| v }
This stores the has based on key value in ascending order. in other words first value in hash is lowest. How do i reverse (descending order) it so that the highest value is at the top?
#sortedInternalLinksHash = #countHash.sort_by { |k,v| -v }
#sortedInternalLinksHash = #countHash.sort_by { |k,v| !v }
#sortedInternalLinksHash = #countHash.sort_by { |k,v| v }.reverse
Also you can use:
#sortedInternalLinksHash = #countHash.sort_by { |_,v| - v }

ruby (w/rails) group_by, then group_by again

Is there a better way to do the following?
prices = Price...all.group_by(&:foreign_key_id)
#prices = Hash.new
prices.each {|k, v| #prices[k] = Hash.new if !#prices[k]; #prices[k] = v.group_by {|g| g.created_at.to_time.to_i } }
I'd like to do something like
prices.each {|k,v| v = v.group_by(&:created_at) }
But this doesn't seem to work because v is an array and group_by produces a hash. Perhaps there's a way to do this with inject?
prices.inject(Hash.new{ |h, k| h[k] = {} }) { |h, (k, v)|
h[k] = v.group_by(&:created_at)
h
}
Added: Less function way:
prices.keys.each { |k|
prices[k] = prices[k].group_by(&:created_at)
}
This should work:
prices = Price...all.inject({}) do |hash, el|
(hash[el.foreign_key_id] ||={})[el.created_at] << el
hash
end
but I doubt that you will have two records with the same created_at value.
You may need set format for created_at.
Something like this:
prices = Price...all.inject({}) do |hash, el|
(hash[el.foreign_key_id] ||={})[el.created_at.to_s(:db)] << el
hash
end
may be
prices.each {|k,v| prices[k] = v.group_by(&:created_at) }
or
new_prices = {}
prices.each {|k,v| new_prices[k] = v.group_by(&:created_at) }
Hash doesn't support inject
I understand the second group_by, but the first one I think it will be a Model logic, so, depend on your type of relation you can use in your model the options: :uniq => true or :group => 'foreign_key_id' . You can read it here http://guides.rubyonrails.org/association_basics.html

How can I replace a hash key with another key?

I have a condition that gets a hash.
hash = {"_id"=>"4de7140772f8be03da000018", .....}
Yet, I want to rename the key of that hash as follows.
hash = {"id"=>"4de7140772f8be03da000018", ......}
P.S. I don't know what keys are in the hash; they are random. Some keys are prefixed with an underscore that I would like to remove.
hash[:new_key] = hash.delete :old_key
rails Hash has standard method for it:
hash.transform_keys{ |key| key.to_s.upcase }
http://api.rubyonrails.org/classes/Hash.html#method-i-transform_keys
UPD: ruby 2.5 method
If all the keys are strings and all of them have the underscore prefix, then you can patch up the hash in place with this:
h.keys.each { |k| h[k[1, k.length - 1]] = h[k]; h.delete(k) }
The k[1, k.length - 1] bit grabs all of k except the first character. If you want a copy, then:
new_h = Hash[h.map { |k, v| [k[1, k.length - 1], v] }]
Or
new_h = h.inject({ }) { |x, (k,v)| x[k[1, k.length - 1]] = v; x }
You could also use sub if you don't like the k[] notation for extracting a substring:
h.keys.each { |k| h[k.sub(/\A_/, '')] = h[k]; h.delete(k) }
Hash[h.map { |k, v| [k.sub(/\A_/, ''), v] }]
h.inject({ }) { |x, (k,v)| x[k.sub(/\A_/, '')] = v; x }
And, if only some of the keys have the underscore prefix:
h.keys.each do |k|
if(k[0,1] == '_')
h[k[1, k.length - 1]] = h[k]
h.delete(k)
end
end
Similar modifications can be done to all the other variants above but these two:
Hash[h.map { |k, v| [k.sub(/\A_/, ''), v] }]
h.inject({ }) { |x, (k,v)| x[k.sub(/\A_/, '')] = v; x }
should be okay with keys that don't have underscore prefixes without extra modifications.
you can do
hash.inject({}){|option, (k,v) | option["id"] = v if k == "_id"; option}
This should work for your case!
If we want to rename a specific key in hash then we can do it as follows:
Suppose my hash is my_hash = {'test' => 'ruby hash demo'}
Now I want to replace 'test' by 'message', then:
my_hash['message'] = my_hash.delete('test')
For Ruby 2.5 or newer with transform_keys and delete_prefix / delete_suffix methods:
hash1 = { '_id' => 'random1' }
hash2 = { 'old_first' => '123456', 'old_second' => '234567' }
hash3 = { 'first_com' => 'google.com', 'second_com' => 'amazon.com' }
hash1.transform_keys { |key| key.delete_prefix('_') }
# => {"id"=>"random1"}
hash2.transform_keys { |key| key.delete_prefix('old_') }
# => {"first"=>"123456", "second"=>"234567"}
hash3.transform_keys { |key| key.delete_suffix('_com') }
# => {"first"=>"google.com", "second"=>"amazon.com"}
h.inject({}) { |m, (k,v)| m[k.sub(/^_/,'')] = v; m }
hash.each {|k,v| hash.delete(k) && hash[k[1..-1]]=v if k[0,1] == '_'}
I went overkill and came up with the following. My motivation behind this was to append to hash keys to avoid scope conflicts when merging together/flattening hashes.
Examples
Extend Hash Class
Adds rekey method to Hash instances.
# Adds additional methods to Hash
class ::Hash
# Changes the keys on a hash
# Takes a block that passes the current key
# Whatever the block returns becomes the new key
# If a hash is returned for the key it will merge the current hash
# with the returned hash from the block. This allows for nested rekeying.
def rekey
self.each_with_object({}) do |(key, value), previous|
new_key = yield(key, value)
if new_key.is_a?(Hash)
previous.merge!(new_key)
else
previous[new_key] = value
end
end
end
end
Prepend Example
my_feelings_about_icecreams = {
vanilla: 'Delicious',
chocolate: 'Too Chocolatey',
strawberry: 'It Is Alright...'
}
my_feelings_about_icecreams.rekey { |key| "#{key}_icecream".to_sym }
# => {:vanilla_icecream=>"Delicious", :chocolate_icecream=>"Too Chocolatey", :strawberry_icecream=>"It Is Alright..."}
Trim Example
{ _id: 1, ___something_: 'what?!' }.rekey do |key|
trimmed = key.to_s.tr('_', '')
trimmed.to_sym
end
# => {:id=>1, :something=>"what?!"}
Flattening and Appending a "Scope"
If you pass a hash back to rekey it will merge the hash which allows you to flatten collections. This allows us to add scope to our keys when flattening a hash to avoid overwriting a key upon merging.
people = {
bob: {
name: 'Bob',
toys: [
{ what: 'car', color: 'red' },
{ what: 'ball', color: 'blue' }
]
},
tom: {
name: 'Tom',
toys: [
{ what: 'house', color: 'blue; da ba dee da ba die' },
{ what: 'nerf gun', color: 'metallic' }
]
}
}
people.rekey do |person, person_info|
person_info.rekey do |key|
"#{person}_#{key}".to_sym
end
end
# =>
# {
# :bob_name=>"Bob",
# :bob_toys=>[
# {:what=>"car", :color=>"red"},
# {:what=>"ball", :color=>"blue"}
# ],
# :tom_name=>"Tom",
# :tom_toys=>[
# {:what=>"house", :color=>"blue; da ba dee da ba die"},
# {:what=>"nerf gun", :color=>"metallic"}
# ]
# }
Previous answers are good enough, but they might update original data.
In case if you don't want the original data to be affected, you can try my code.
newhash=hash.reject{|k| k=='_id'}.merge({id:hash['_id']})
First it will ignore the key '_id' then merge with the updated one.
Answering exactly what was asked:
hash = {"_id"=>"4de7140772f8be03da000018"}
hash.transform_keys { |key| key[1..] }
# => {"id"=>"4de7140772f8be03da000018"}
The method transform_keys exists in the Hash class since Ruby version 2.5.
https://blog.bigbinary.com/2018/01/09/ruby-2-5-adds-hash-transform_keys-method.html
If you had a hash inside a hash, something like
hash = {
"object" => {
"_id"=>"4de7140772f8be03da000018"
}
}
and if you wanted to change "_id" to something like"token"
you can use deep_transform_keys here and do it like so
hash.deep_transform_keys do |key|
key = "token" if key == "_id"
key
end
which results in
{
"object" => {
"token"=>"4de7140772f8be03da000018"
}
}
Even if you had a symbol key hash instead to start with, something like
hash = {
object: {
id: "4de7140772f8be03da000018"
}
}
you can combine all of these concepts to convert them into a string key hash
hash.deep_transform_keys do |key|
key = "token" if key == :id
key.to_s
end
If you only want to change only one key, there is a straightforward way to do it in Ruby 2.8+ using the transform_keys method. In this example, if you want to change _id to id, then you can:
hash.transform_keys({_id: :id})
Reference: https://bugs.ruby-lang.org/issues/16274

Resources