Construct hash dynamically in Ruby - ruby

I have the following ruby code which creates a hash with specified format:
result.each do |result|
if domain == 'social'
hash[result['date']] = {
'positive' => result['positive'],
'negative' => result['negative'],
'declined' => result['declined']
}
end
if domain == 'motivation'
hash[result['date']] = {
'high' => result['high'],
'medium' => result['medium'],
'low' => result['low']
}
end
end
Is there any way to remove this duplications and do this in more clean way?

Maybe creating the hash for hash[result['date']] depending on the domain value?:
result.each do |result|
keys = case domain
when 'social' then %w[positive negative declined]
when 'motivation' then %w[high medium low]
end
hash[result['date']] = keys.each_with_object(Hash.new(0)) { |e, h| h[e] = result[e] }
end
Or:
result.each do |result|
keys = domain == 'social' ? %w[positive negative declined] : %w[high medium low]
hash[result['date']] = keys.each_with_object(Hash.new(0)) { |e, h| h[e] = result[e] }
end

You can use Hash#select:
social_keys = ['positive', 'negative', 'declined']
hash[result['date']] = result.select {|k, _| social_keys.include? k }

result.each do |result|
hash[result['date']] = result.slice(
*case domain
when "social" then %w[positive negative declined]
when "motivation" then %w[high medium low]
end
)
end

Related

Ruby If statement refactor

I'm coming back to Ruby after a long time away.
I've written the following code which is functional:
def generate_address_record(data)
address = FM[:fap_address].build do |a|
if data['Line 1'].blank?
a.unstructured_address.line1 = nil
else
a.unstructured_address.line1 = data['Line 1']
end
if data['Line 2'].blank?
a.unstructured_address.line2 = nil
else
a.unstructured_address.line2 = data['Line 2']
end
if data['Line 3'].blank?
a.unstructured_address.line3 = nil
else
a.unstructured_address.line3 = data['Line 3']
end
if data['Line 4'].blank?
a.unstructured_address.line4 = nil
else
a.unstructured_address.line4 = data['Line 4']
end
if data['Line 5'].blank?
a.unstructured_address.line5 = nil
else
a.unstructured_address.line5 = data['Line 5']
end
if data['Postcode'].blank?
a.unstructured_address.postcode = nil
else
a.unstructured_address.postcode = data['Postcode']
end
end
end
Is there a way this can be re-written in a 'nicer' way in one loop so that I don't need all of these individual if statements.
Any advice would be greatly appreciated.
If data contains only expected values, you can convert key to the method name.
def generate_address_record(data)
address = FM[:fap_address].build do |a|
data.each do |key, value|
name = key.gsub(/[[:space:]]/, '').downcase
a.unstructured_address.public_send("#{name}=", value.presence)
end
end
end
Be careful(or don't use this approach) when hash keys are coming from outside of the application or other uncontrolled environment.
Yup, you can use #presence (I'm assuming you're using Rails):
a.unstructured_address.line1 = data['Line 1'].presence
#presence behaviour:
''.presence
# => nil
nil.presence
# => nil
'a'.presence
# => "a"
false.presence
# => nil
I propose this combination of the various already posted solutions because I think it has a good balance between shortness and readability:
def generate_address_record(data)
address = FM[:fap_address].build do |a|
a.unstructured_address.line1 = data['Line 1'].presence
a.unstructured_address.line2 = data['Line 2'].presence
a.unstructured_address.line3 = data['Line 3'].presence
a.unstructured_address.line4 = data['Line 4'].presence
a.unstructured_address.line5 = data['Line 5'].presence
a.unstructured_address.postcode = data['Postcode'].presence
end
end
One pattern I find usefull is to make a hash and then iterate over it:
def generate_address_record(data)
address = FM[:fap_address].build do |a|
{
"Line 1" => :line1,
"Line 2" => :line2,
"Line 3" => :line3,
"Line 4" => :line4,
"Line 5" => :line5,
"Postcode" => :postcode
}.each do |key, accessor|
if data[key].blank?
a.unstructured_address.send(accessor) = nil
else
a.unstructured_address.send(:"#{accessor}=") = data[key]
end
end
end
end
You can also use this with presence as mrzasa shared

How can I parse a string into a hash?

I am trying to parse a string into a hash.
str = "Notifications[0].Open=1
Notifications[0].Body.Message[0]=3455
Notifications[0].Body.Message[1]=2524
Notifications[0].Body.Message[2]=2544
Notifications[0].Body.Message[3]=2452
Notifications[0].Body.Error[0]=2455
Notifications[0].Body.Currency=EUR
Notifications[0].Published=true"
The result should look similar to this:
pairs = {
'Open' = 1,
'Published' => true
'Body' => {
'Message' => [3455, 2524, 2544, 2452],
'Error' => [2455],
'Currency' => 'EUR',
}
}
Maybe someone can help on how I can make it. The only way I can think as for now is regexp.
something like this with regexp:
require 'pp'
str = "Notifications[0].Open=1
Notifications[0].Body.Message[0]=3455
Notifications[0].Body.Message[1]=2524
Notifications[0].Body.Message[2]=2544
Notifications[0].Body.Message[3]=2452
Notifications[0].Body.Error[0]=2455
Notifications[0].Body.Currency=EUR
Notifications[0].Published=true"
pairs = {}
pairs['Body'] = {}
values = []
str.scan(/Body\W+(.+)/).flatten.each do |line|
key = line[/\A\w+/]
value = line[/\w+\z/]
if line[/\A\w+\[\d+\]/] || key == 'Error'
values = [] unless pairs['Body'][key]
values << value
value = values
end
pairs['Body'][key] = value
end
str.scan(/\[0\]\.(?!Body.).*/).each do |line|
key = line[/(?!\A)\.(\w+)/, 1]
value = line[/\w+\z/]
if line[/\A\w+\[\d+\]/]
values = [] unless pairs[key]
values << value
value = values
end
pairs[key] = value
end
PP.pp pairs
-
{"Body"=>
{"Message"=>["3455", "2524", "2544", "2452"],
"Error"=>["2455"],
"Currency"=>"EUR"},
"Open"=>"1",
"Published"=>"true"}
Here it is. This code should work with any structure.
def parse(path, value, hash)
key, rest = path.split('.', 2)
if rest.nil?
hash[key] = value
else
hash[key] ||= {}
parse(rest, value, hash[key])
end
end
def conv_to_array(hash)
if hash.is_a?(Hash)
hash.each do |key, value|
hash[key] = if value.is_a?(Hash) && value.keys.all? { |k| k !~ /\D/ }
arr = []
value.each do |k, v|
arr[k.to_i] = conv_to_array(v)
end
arr
else
conv_to_array(value)
end
end
hash
else
if hash !~ /\D/
hash.to_i
elsif hash == 'true'
true
elsif hash == 'false'
false
else
hash
end
end
end
str = "Notifications[0].Open=1
Notifications[0].Body.Message[0]=3455
Notifications[0].Body.Message[1]=2524
Notifications[0].Body.Message[2]=2544
Notifications[0].Body.Message[3]=2452
Notifications[0].Body.Error[0]=2455
Notifications[0].Body.Currency=EUR
Notifications[0].Published=true"
str = str.tr('[', '.').tr(']', '')
hash = {}
str.split(' ').each do |chunk|
path, value = chunk.split('=')
parse(path.strip, value.strip, hash)
end
hash = conv_to_array(hash)
hash['Notifications'][0]
# => {"Open"=>1, "Body"=>{"Message"=>[3455, 2524, 2544, 2452], "Error"=>[2455], "Currency"=>"EUR"}, "Published"=>true}

Array with hash, how to merge same keys and add its value

I have an array with hashes in it. If they have the same key I just want to add its value.
#receivers << result
#receivers
=> [{:email=>"user_02#yorlook.com", :amount=>10.00}]
result
=> {:email=>"user_02#yorlook.com", :amount=>7.00}
I want the result of above to look like this
[{:email=>"user_02#yorlook.com", :amount=>17.00}]
Does anyone know how to do this?
Here is the the entire method
def receivers
#receivers = []
orders.each do |order|
product_email = order.product.user.paypal_email
outfit_email = order.outfit_user.paypal_email
if order.user_owns_outfit?
result = { email: product_email, amount: amount(order.total_price) }
else
result = { email: product_email, amount: amount(order.total_price, 0.9),
email: outfit_email, amount: amount(order.total_price, 0.1) }
end
#receivers << result
end
end
Using Enumerable#group_by
#receivers.group_by {|h| h[:email]}.map do |k, v|
{email: k, amount: v.inject(0){|s,h| s + h[:amount] } }
end
# => [{:email=>"user_02#yorlook.com", :amount=>17.0}]
Using Enumerable#each_with_object
#receivers.each_with_object(Hash.new(0)) {|h, nh| nh[h[:email]]+= h[:amount] }.map do |k, v|
{email: k, amount: v}
end
# Output: [{ "em#il.one" => 29.0 }, { "em#il.two" => 39.0 }]
def receivers
return #receivers if #receivers
# Produces: { "em#il.one" => 29.0, "em#il.two" => 39.0 }
partial_result = orders.reduce Hash.new(0.00) do |result, order|
product_email = order.product.user.paypal_email
outfit_email = order.outfit_user.paypal_email
if order.user_owns_outfit?
result[product_email] += amount(order.total_price)
else
result[product_email] += amount(order.total_price, .9)
result[outfit_email] += amount(order.total_price, .1)
end
result
end
#receivers = partial_result.reduce [] do |result, (email, amount)|
result << { email => amount }
end
end
I would just write the code this way:
def add(destination, source)
if destination.nil?
return nil
end
if source.class == Hash
source = [source]
end
for item in source
target = destination.find {|d| d[:email] == item[:email]}
if target.nil?
destination << item
else
target[:amount] += item[:amount]
end
end
destination
end
usage:
#receivers = []
add(#receivers, {:email=>"user_02#yorlook.com", :amount=>10.00})
=> [{:email=>"user_02#yorlook.com", :amount=>10.0}]
add(#receivers, #receivers)
=> [{:email=>"user_02#yorlook.com", :amount=>20.0}]
a = [
{:email=>"user_02#yorlook.com", :amount=>10.0},
{:email=>"user_02#yorlook.com", :amount=>7.0}
]
a.group_by { |v| v.delete :email } # group by emails
.map { |k, v| [k, v.inject(0) { |memo, a| memo + a[:amount] } ] } # sum amounts
.map { |e| %i|email amount|.zip e } # zip to keys
.map &:to_h # convert nested arrays to hashes
From what I understand, you could get away with just .inject:
a = [{:email=>"user_02#yorlook.com", :amount=>10.00}]
b = {:email=>"user_02#yorlook.com", :amount=>7.00}
c = {email: 'user_03#yorlook.com', amount: 10}
[a, b, c].flatten.inject({}) do |a, e|
a[e[:email]] ||= 0
a[e[:email]] += e[:amount]
a
end
=> {
"user_02#yorlook.com" => 17.0,
"user_03#yorlook.com" => 10
}

Difference between 2 different nested hash in Ruby 1.8.7

Consider the Following nested Hash:
data1 = {
"3"=>{"passenger_type"=>"ADT", "the_order"=>"3", "last"=>"JONES", "first"=>"ALENA", "middle"=>nil},
"2"=>{"passenger_type"=>"ADT", "the_order"=>"2", "last"=>"JONES", "first"=>"MAXIM", "middle"=>nil},
"1"=>{"passenger_type"=>"ADTT", "the_order"=>"1", "last"=>"JONES", "first"=>"TODD", "middle"=>nil}}
data2 = {
"3"=>{"first"=>"ALENA", "the_order"=>"3", "middle"=>"", "passenger_type"=>"ADTT", "last"=>"JONES"},
"2"=>{"first"=>"MAXIM", "the_order"=>"2", "middle"=>"", "passenger_type"=>"ADT", "last"=>"JONES"},
"1"=>{"first"=>"TODD", "the_order"=>"1", "middle"=>"", "passenger_type"=>"ADT", "last"=>"JONESS"}}
The Output Should be like this(difference between both hash listed values):
{"3" => {"passenger_type" => ["ADT", "ADTT"]},
"1" => {"passenger_type" => ["ADTT", "ADT"], "last" => ["JONES", "JONESS"]}
Anyone your suggestion is appreciated, thanks in advance.
You can use the form of Hash#merge that takes a block to produce the desired result in a compact manner:
data1.merge(data2) { |_,ho,hn|
ho.merge(hn) { |_,o,n| (o==n||o==''||n=='') ? nil : [o,n] }
.delete_if { |_,v| v==nil } }
.delete_if { |_,v| v.empty? }
#=> {"3"=>{"passenger_type"=>["ADT", "ADTT"]},
# "1"=>{"passenger_type"=>["ADTT", "ADT"], "last"=>["JONES", "JONESS"]}}
Here's some ugly code:
data3 = {}
data1.each do |k, v|
v2 = data2[k]
v.each do |item, val|
if v2.has_key?(item) then
if (val == nil or val == '') and (v2[item] == nil or v2[item] == '') then
next
end
if val != v2[item] then
data3[k] ||= {}
data3[k][item] = [val, v2[item]]
end
end
end
end
puts data3
prints
{"3"=>{"passenger_type"=>["ADT", "ADTT"]}, "1"=>{"passenger_type"=>["ADTT", "ADT"], "last"=>["JONES", "JONESS"]}}

How to convert deep hash to array of keys

I want to programmatically convert this:
{
"a"=>
{"1"=>
{"A"=>
{"Standard"=>"true"}
}
},
"b"=>
{"1"=>
{"A"=>
{"Standard"=>"true"}
}
}
}
to an array like this:
['a/1/A/Standard', 'b/1/A/Standard']
def extract_keys(hash)
return [] unless hash.is_a?(Hash)
hash.each_pair.map {|key, value| [key, extract_keys(value)].join('/') }
end
extract_keys(hash)
=> ["a/1/A/Standard", "b/1/A/Standard"]
From one of my other answers - adapted for your situation. See the link for a more verbose solution to flat_hash
def flat_hash(hash, k = "")
return {k => hash} unless hash.is_a?(Hash)
hash.inject({}){ |h, v| h.merge! flat_hash(v[-1], k + '/' + v[0]) }
end
example = {...} # your example hash
foo = flat_hash(example).keys
=> ["/a/1/A/Standard", "/b/1/A/Standard"]
Found this flatten lambda definition.
h = {
"a"=>
{"1"=>
{"A"=>
{"Standard"=>"true"}
}
},
"b"=>
{"1"=>
{"A"=>
{"Standard"=>"true"}
}
}
}
a = []
flatten =
lambda {|r|
(recurse = lambda {|v|
if v.is_a?(Hash)
v.to_a.map{|v| recurse.call(v)}.flatten
elsif v.is_a?(Array)
v.flatten.map{|v| recurse.call(v)}
else
v.to_s
end
}).call(r)
}
h.each do |k,v|
a << k + "/" + flatten.call(v).join("/")
end
Output:
["a/1/A/Standard/true", "b/1/A/Standard/true"]

Resources