constructing a new hash from the given values - ruby-on-rails

I seem lost trying to achieve the following, I tried all day please help
I HAVE
h = {
"kv1001"=> {
"impressions"=>{"b"=>0.245, "a"=>0.754},
"visitors" =>{"b"=>0.288, "a"=>0.711},
"ctr" =>{"b"=>0.003, "a"=>0.003},
"inScreen"=>{"b"=>3.95, "a"=>5.031}
},
"kv1002"=> {
"impressions"=>{"c"=>0.930, "d"=>0.035, "a"=>0.004, "b"=>0.019,"e"=>0.010},
"visitors"=>{"c"=>0.905, "d"=>0.048, "a"=>0.005, "b"=>0.026, "e"=>0.013},
"ctr"=>{"c"=>0.003, "d"=>0.006, "a"=>0.004, "b"=>0.003, "e"=>0.005},
"inScreen"=>{"c"=>4.731, "d"=>4.691, "a"=>5.533, "b"=>6.025, "e"=>5.546}
}
}
MY GOAL
{
"segment"=>"kv1001=a",
"impressions"=>"0.754",
"visitors"=>"0.711",
"inScreen"=>"5.031",
"ctr"=>"0.003"
}, {
"segment"=>"kv1001=b",
"impressions"=>"0.245",
"visitors"=>"0.288",
"inScreen"=>"3.95",
"ctr"=>"0.003"
}, {
"segment"=>"kv1002=a",
"impressions"=>"0.004"
#... etc
}
My goal is to create a hash with 'kv1001=a' i.e the letters inside the hash and assign the keys like impressions, visitors etc. The example MY GOAL has the format
So format type "kv1001=a" must be constructed from the hash itself, a is the letter inside the hash.
I have solved this now
`data_final = []
h.each do |group,val|
a = Array.new(26){{}}
val.values.each_with_index do |v, i|
keys = val.keys
segment_count = v.keys.length
(0..segment_count-1).each do |n|
a0 = {"segment" => "#{group}=#{v.to_a[n][0]}", keys[i] => v.to_a[n][1]}
a[n].merge! a0
if a[n].count > 4
data_final << a[n]
end
end
end
end`

Here's a simpler version
h.flat_map do |segment, attrs|
letters = attrs.values.flat_map(&:keys).uniq
# create a segment entry for each unique letter
letters.map do |letter|
seg = {"segment" => "#{segment}=#{letter}"}
seg.merge Hash[attrs.keys.map {|key| [key,attrs[key][letter]]}]
end
end
Output:
[{"segment"=>"kv1001=b",
"impressions"=>0.245,
"visitors"=>0.288,
"ctr"=>0.003,
"inScreen"=>3.95},
{"segment"=>"kv1001=a",
"impressions"=>0.754,
"visitors"=>0.711,
"ctr"=>0.003,
"inScreen"=>5.031},
{"segment"=>"kv1002=c",
"impressions"=>0.93,
"visitors"=>0.905,
"ctr"=>0.003,
"inScreen"=>4.731},
{"segment"=>"kv1002=d",
"impressions"=>0.035,
"visitors"=>0.048,
"ctr"=>0.006,
"inScreen"=>4.691},
{"segment"=>"kv1002=a",
"impressions"=>0.004,
"visitors"=>0.005,
"ctr"=>0.004,
"inScreen"=>5.533},
{"segment"=>"kv1002=b",
"impressions"=>0.019,
"visitors"=>0.026,
"ctr"=>0.003,
"inScreen"=>6.025},
{"segment"=>"kv1002=e",
"impressions"=>0.01,
"visitors"=>0.013,
"ctr"=>0.005,
"inScreen"=>5.546}]

Related

Searching using a single integer in a hash whose keys are ranges in Ruby

I have this hash in Ruby:
hash = {
0..25 => { low_battery_count: 13 },
26..75 => { average_battery_count: 4 },
76..100 => { good_battery_count: 4 }
}
Now, what is want is a method (preferably a built-in one) in which if I pass a value (integer e.g. 20, 30, etc), it should return me the value against the range in which this integer lies.
I have already solved it using each method but now, I want to optimize it,
even more, to do it without each or map methods to reduce its complexity.
For Example:
method(3) #=> {:low_battery_count=>13}
method(35) #=> {:average_battery_count=>4}
method(90) #=> {:good_battery_count=>4}
You can use find for this hash
BATTERIES = {
0..25 => { low_battery_count: 13 },
26..75 => { average_battery_count: 4 },
76..100 => { good_battery_count: 4 }
}
def get_batteries_count(value)
BATTERIES.find { |range, _| range.include?(value) }&.last
end
get_batteries_count(3) #=> {:low_battery_count=>13}
get_batteries_count(35) #=> {:average_battery_count=>4}
get_batteries_count(90) #=> {:good_battery_count=>4}
get_batteries_count(2000) #=> nil
You need to get exact key in order to fetch a value from the hash. So either way a passed number should be tested against the available keys (ranges).
You might consider introducing an optimisation based on assigning pre-determined ranges to the constants and using a case equality check:
SMALL = 0..25
MEDUIM = 26..75
LARGE = 76..100
def method(n)
case n
when SMALL then data[SMALL]
when MEDUIM then data[MEDUIM]
when LARGE then data[LARGE]
end
end
def data
#data ||= {
SMALL => { low_battery_count: 13 },
MEDUIM => { average_battery_count: 4 },
LARGE =>{ good_battery_count: 4 }
}
end
method(25)
=> {:low_battery_count=>13}
method(44)
=> {:average_battery_count=>4}
method(76)
=> {:good_battery_count=>4}
We can do this using some of built in methods but there is not any direct way to do this
class Hash
def value_at_key_range(range_member)
keys.select{ |range| range.include?(range_member) }.map{ |key| self[key]}
end
end
hash = {0..25=>{:low_battery_count=>13}, 26..75=>{:average_battery_count=>4}, 76..100=>{:good_battery_count=>4}}
p hash.value_at_key_range(10)
output
[{:low_battery_count=>13}]

Need help assigning values to a new array then calculating those values

def solve(c)
letter_values =('a'..'z').map.with_index(1) {|letter,value| [letter,value] }
remove_vowels =c.gsub(/[aeiou]/, ' ').split()
end
solve("zodiacs")
The letter_values array has the alphabet letters with numbers. I need to assign those numbers to the remove_vowels variable, which should only return consonants to carry out a calculation later on. If "z" is return, I need to look in the array and take the value of "z" which is 26 and assign it as a value to z from the regex.
So you want the index in the alphabet of each letter?
See this post
letter_values = ('a'..'z').map do |l| l.bytes.first - 96 end
def solve(c)
letter_values =('a'..'z').map.with_index(1) { |letter,value| [letter,value] }.to_h
remove_vowels = c.gsub(/[aeiou]/, '').split('')
hash = Hash.new
remove_vowels.map { | consonants | hash[consonants] = letter_values[consonants]}
hash
end
solve("zodiacs") => this will return {"z"=>26, "d"=>4, "c"=>3, "s"=>19}
Or you could use #each_with_object, the result will be the same; it cleans the code up quite a bit:
def solve(c)
letter_values =('a'..'z').map.with_index(1) { |letter,value| [letter,value] }.to_h
remove_vowels = c.gsub(/[aeiou]/, '').split('')
remove_vowels.each_with_object({}) { |constanant, hash| hash[constanant] = letter_values[constanant]}
end
solve("zodiacs") => this will return {"z"=>26, "d"=>4, "c"=>3, "s"=>19}

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

Ruby way to loop and check subsequent values against each other

I have an array that contains dates and values. An example of how it might look:
[
{'1/1/2010' => 'aa'},
{'1/1/2010' => 'bb'},
{'1/2/2010' => 'cc'},
{'1/2/2010' => 'dd'},
{'1/3/2010' => 'ee'}
]
Notice that some of the dates repeat. I'm trying to output this in a table format and I only want to show unique dates. So I loop through it with the following code to get my desired output.
prev_date = nil
#reading_schedule.reading_plans.each do |plan|
use_date = nil
if plan.assigned_date != prev_date
use_date = plan.assigned_date
end
prev_date = plan.assigned_date
plan.assigned_date = use_date
end
The resulting table will then look something like this
1/1/2010 aa
bb
1/2/2010 cc
dd
1/3/2010 ee
This work fine but I am new to ruby and was wondering if there was a better way to do this.
Enumerable.group_by is a good starting point:
require 'pp'
asdf = [
{'1/1/2010' => 'aa'},
{'1/1/2010' => 'bb'},
{'1/2/2010' => 'cc'},
{'1/2/2010' => 'dd'},
{'1/3/2010' => 'ee'}
]
pp asdf.group_by { |n| n.keys.first }.map{ |a,b| { a => b.map { |c| c.to_a.last.last } } }
# >> [{"1/1/2010"=>["aa", "bb"]}, {"1/2/2010"=>["cc", "dd"]}, {"1/3/2010"=>["ee"]}]
Which should be a data structure you can bend to your will.
I don't know as though it's better, but you could group the values by date using (e.g.) Enumerable#reduce (requires Ruby >= 1.8.7; before that, you have Enumerable#inject).
arr.reduce({}) { |memo, obj|
obj.each_pair { |key, value|
memo[key] = [] if ! memo.has_key?(key);
memo[key] << value
}
memo
}.sort
=> [["1/1/2010", ["aa", "bb"]], ["1/2/2010", ["cc", "dd"]], ["1/3/2010", ["ee"]]]
You could also use Array#each to similar effect.
This is totally a job for a hash.
Create a hash and use the date as the hashkey and an empty array as the hashvalue.
Then accumulate the values from the original array in the hashvalue array

There has got to be a cleaner way to do this

I have this code here and it works but there has to be a better way.....i need two arrays that look like this
[
{
"Vector Arena - Auckland Central, New Zealand" => {
"2010-10-10" => [
"Enter Sandman",
"Unforgiven",
"And justice for all"
]
}
},
{
"Brisbane Entertainment Centre - Brisbane Qld, Austr..." => {
"2010-10-11" => [
"Enter Sandman"
]
}
}
]
one for the past and one for the upcoming...the problem i have is i am repeating myself and though it works i want to clean it up ...here is my data
..
Try this:
h = Hash.new {|h1, k1| h1[k1] = Hash.new{|h2, k2| h2[k2] = []}}
result, today = [ h, h.dup], Date.today
Request.find_all_by_artist("Metallica",
:select => "DISTINCT venue, showdate, LOWER(song) AS song"
).each do |req|
idx = req.showdate < today ? 0 : 1
result[idx][req.venue][req.showdate] << req.song.titlecase
end
Note 1
In the first line I am initializing an hash of hashes. The outer hash creates the inner hash when a non existent key is accessed. An excerpt from Ruby Hash documentation:
If this hash is subsequently accessed by a key that doesn‘t correspond to a hash
entry, the block will be called with the hash object and the key, and should
return the default value. It is the block‘s responsibility to store the value in
the hash if required.
The inner hash creates and empty array when the non existent date is accessed.
E.g: Construct an hash containing of content as values and date as keys:
Without a default block:
h = {}
list.each do |data|
h[data.date] = [] unless h[data.date]
h[data.date] << data.content
end
With a default block
h = Hash.new{|h, k| h[k] = []}
list.each do |data|
h[data.date] << data.content
end
Second line simply creates an array with two items to hold the past and future data. Since both past and the present stores the data as Hash of Hash of Array, I simply duplicate the value.
Second line can also be written as
result = [ h, h.dup]
today = Date.today

Resources