Select most common domain from list of email addresses - ruby

I have a function that generates a random email address:
def emails
names = ["alfred", "daniel", "elisa", "ana", "ramzes"]
surnames = ["oak", "leaf", "grass", "fruit"]
providers = ["gmail", "yahoo", "outlook", "icloud"]
address = "#{names.sample}.#{surnames.sample}#{rand(100..5300)}##{providers.sample}.com"
end
Given a list of randomly generated email address:
email_list = 100.times.map { emails }
that looks like this:
daniel.oak3985#icloud.com
ramzes.grass1166#icloud.com
daniel.fruit992#yahoo.com
...
how can I select the most common provider ("gmail", "yahoo", etc.)?

Your question is similar to this one. There's a twist though : you don't want to analyze the frequency of email addresses, but their providers.
def random_email
names = ["alfred", "daniel", "elisa", "ana", "ramzes"]
surnames = ["oak", "leaf", "grass", "fruit"]
providers = ["gmail", "yahoo", "outlook", "icloud"]
address = "#{names.sample}.#{surnames.sample}#{rand(100..5300)}##{providers.sample}.com"
end
emails = Array.new(100){ random_email }
freq = emails.each_with_object(Hash.new(0)) do |email,freq|
provider = email.split('#').last
freq[provider] += 1
end
p freq
#=> {"outlook.com"=>24, "yahoo.com"=>28, "gmail.com"=>32, "icloud.com"=>16}
p freq.max_by{|provider, count| count}.first
#=> "gmail.com"

email_list = 10.times.map { emails }
#=> ["alfred.grass426#gmail.com", "elisa.oak239#icloud.com",
# "daniel.fruit1600#outlook.com", "ana.fruit3761#icloud.com",
# "daniel.grass742#yahoo.com", "elisa.oak3891#outlook.com",
# "alfred.leaf1321#gmail.com", "alfred.grass5295#outlook.com",
# "ramzes.fruit435#gmail.com", "ana.fruit4233#yahoo.com"]
email_list.group_by { |s| s[/#\K.+/] }.max_by { |_,v| v.size }.first
#=> "gmail.com"
\K in the regex means disregard everything matched so far. Alternatively, #\K could be replaced by the positive lookbehind (?<=#).
The steps are as follows.
h = email_list.group_by { |s| s[/#\K.+/] }
#=> {"gmail.com" =>["alfred.grass426#gmail.com", "alfred.leaf1321#gmail.com",
# "ramzes.fruit435#gmail.com"],
# "icloud.com" =>["elisa.oak239#icloud.com", "ana.fruit3761#icloud.com"],
# "outlook.com"=>["daniel.fruit1600#outlook.com", "elisa.oak3891#outlook.com",
# "alfred.grass5295#outlook.com"],
# "yahoo.com" =>["daniel.grass742#yahoo.com", "ana.fruit4233#yahoo.com"]}
a = h.max_by { |_,v| v.size }
#=> ["gmail.com", ["alfred.grass426#gmail.com", "alfred.leaf1321#gmail.com",
# "ramzes.fruit435#gmail.com"]]
a.first
#=> "gmail.com"
If, as here, there is a tie for most frequent, modify the code as follows to get all winners.
h = email_list.group_by { |s| s[/#\K.+/] }
# (same as above)
mx_size = h.map { |_,v| v.size }.max
#=> 3
h.select { |_,v| v.size == mx_size }.keys
#=> ["gmail.com", "outlook.com"]

Related

Ruby Trees with full path on leaves

I have an array of paths:
paths = ["home", "usr/lib/folder1/", "usr/lib/folder2/"]
I tried to make a tree with full path on the ends (leaves):
{"home" => "home", "usr" => {"lib" => {"folder1" => "usr/lib/folder1/", "folder2" => "usr/lib/folder2/"}}}
This is my code:
paths.each do |path|
current = tree
path.split('/').inject('') do |_sub_path, dir|
sub_path = File.join(dir)
current[sub_path] ||= {path => {}}
current = current[sub_path]
sub_path
end
end
Can you show me the right way for my question?
This is my first question on SO. Sorry if my English bad.
This will do it but its not tested nor is it very Rubyish...
paths = ["home", "usr/lib/folder1/", "usr/lib/folder2/"]
ans =
paths.reduce({}){
|acc, e|
last = e.split('/').last
e.split('/').reduce(acc){
|acc, n|
acc[n] = {} if acc[n].nil?
acc[n] = e if n == last
acc[n]
}
acc
}
p ans
What's your strategy for adding more paths?I.E. If you have to add another path like "home/user/data" and "/home/user/docs" or "usr/lib/folder1/data" and "usr/lib/folder2/data", what's your strategy?
This could be solved recursively.
def recurse(arr, arr_build = [])
return arr_build.join('/') if arr == [[]]
arr.group_by { |a| a.first }.
transform_values { |v| recurse(v.map { |a| a.drop(1) },
arr_build + [v.first.first]) }
end
recurse(paths.map { |s| s.split('/') })
#=> {"home"=>"home",
# "usr"=>{"lib"=>{"folder1"=>"usr/lib/folder1",
# "folder2"=>"usr/lib/folder2"}}}
See Enumerable#group_by and Hash#transform_values.
Note that when
arr = paths.map { |s| s.split('/') }
#=> [["home"], ["usr", "lib", "folder1"], ["usr", "lib", "folder2"]]
we find that
arr.group_by { |a| a.first }
#=> {"home"=>[["home"]],
# "usr"=>[["usr", "lib", "folder1"], ["usr", "lib", "folder2"]]}

Update a Hash Where the Values are Hashes

I have this hash where the keys are 0, 3, and 5 and the values are hashes.
{0=>{:occurrences=>1, :className=>"class"},
3=>{:occurrences=>3, :className=>"hello"},
5=>{:occurrences=>3, :className=>"nah"}}
How can I implement something like this:
h.map { |key|
if key[:occurrences] > 2
key[:occurrences] += 1
end
}
I know this syntax doesn't work. I want to increment the occurrence value when a condition is met and I am not sure how to access the key of a key but I would like the result to be:
{0=>{:occurrences=>1, :className=>"class"},
3=>{:occurrences=>4, :className=>"hello"},
5=>{:occurrences=>4, :className=>"nah"}}
To update the existing hash you can simply call each_value. It passes each value in your hash to the block and within the block you can update the value (based on a condition):
h = {
0=>{:occurrences=>1, :className=>"class"},
3=>{:occurrences=>3, :className=>"hello"},
5=>{:occurrences=>3, :className=>"nah"}
}
h.each_value { |v| v[:occurrences] += 1 if v[:occurrences] > 2 }
#=> {
# 0=>{:occurrences=>1, :className=>"class"},
# 3=>{:occurrences=>4, :className=>"hello"},
# 5=>{:occurrences=>4, :className=>"nah"}
# }
Just out of curiosity:
input.map do |k, v|
[k, v[:occurrences].to_i > 2 ? v.merge(occurrences: v[:occurrences] + 1) : v]
end.to_h
#⇒ {0=>{:occurrence=>1, :className=>"class"},
# 3=>{:occurrences=>4, :className=>"hello"},
# 5=>{:occurrences=>4, :className=>"nah"}}
h = { 0=>{ :occurrences=>1, :className=>"class" },
3=>{ :occurrences=>3, :className=>"hello" },
5=>{ :occurrences=>3, :className=>"nah" } }
f = h.dup
Non-destructive case
h.transform_values do |g|
g[:occurrences] > 2 ? g.merge(occurrences: g[:occurrences] + 1) : g
end
#=> { 0=>{ :occurrences=>1, :className=>"class" },
# 3=>{ :occurrences=>4, :className=>"hello" },
# 5=>{ :occurrences=>4, :className=>"nah" } }
h == f
#=> true
Destructive case
g = h.transform_values! do |g|
g[:occurrences] > 2 ? g.merge(occurrences: g[:occurrences] + 1) : g
end
#=> { 0=>{ :occurrences=>1, :className=>"class" },
# 3=>{ :occurrences=>4, :className=>"hello" },
# 5=>{ :occurrences=>4, :className=>"nah" } }
h == g
See Hash#transform_values and Hash#transform_values!, which made their debut in MRI v.2.4. Note that, in the destructive case, merge! is not needed.
For example I would want the entire hash returned but with updated
values: {0=>{:occurrence=>1, :className=>"class"},
3=>{:occurrences=>4, :className=>"hello"}, 5=>{:occurrences=>4,
:className=>"nah"}}. If the value of the occurrences key is greater
than two than I want to increment that value and still have the entire
hash.
Here you go:
h = {0=>{:occurrences=>1, :className=>"class"}, 3=>{:occurrences=>3, :className=>"hello"}, 5=>{:occurrences=>3, :className=>"nah"}}
new_h = h.map do |k, v|
if v[:occurrences] > 2
v[:occurrences] += 1
end
[k, v]
end.to_h

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
}

splitting an array into a hash

Im looking to split an array of strings and creating a hash out of it.
I have an algorithm that splits a string into an array by commas this:1, is:1, a:1, string:1
def split_answer_to_hash(str)
words = str.split(',')
answer = {}
words.each do |w|
a = w.split(':')
h = Hash[ *a.collect { |v| [ v, a[1] ] } ]
answer = h
end
answer
end
What I need to do now is to make the left side of the colon the key to the hash and the right side of the colon the value of the hash. example: {"this" =>1, "is"=>1, "a"=>1, "string"=>1 }
*a.collect is iterating through the array and making the value another key. How can I go about this with out that happening?
The easiest way is:
string = 'this:1, is:1, a:1, string:1'
hash = Hash[*string.split(/:|,/)]
#=> {"this"=>"1", " is"=>"1", " a"=>"1", " string"=>"1"}
Having just one answer to this question just won't do:
str = "this:1, is:1, a:1, string:1"
Hash[str.scan(/(?:([^:]+):(\d+)(?:,\s)?)/)]
.tap { |h| h.keys.each { |k| h[k] = h[k].to_i } }
#=> {"this"=>1, "is"=>1, "a"=>1, "string"=>1}
Object#tap is used merely to convert the values from strings to integers. If you'd prefer:
h = Hash[str.scan(/(?:([^:]+):(\d+)(?:,\s)?)/)]
h.keys.each { |k| h[k] = h[k].to_i }
h
#=> {"this"=>1, "is"=>1, "a"=>1, "string"=>1}
For Ruby 2.1, you can replace Hash[arr] with arr.to_h.

Find highest value from hash that contains "nil"

I have a hash which looks like this
#hash = {
0=>[{"name"=>"guest", "value"=>7.9}],
1=>[nil], 2=>[nil], 3=>[nil], 4=>[nil], 5=>[nil], 6=>[nil], 7=>[nil], 8=>[nil],
9=>[nil], 10=>[nil], 11=>[nil], 12=>[nil], 13=>[nil], 14=>[nil], 15=>[nil],
16=>[nil], 17=>[nil], 18=>[nil],
19=>[{"name"=>"test", "value"=>2.5}],
20=>[{"name"=>"roam", "value"=>2.5}],
21=>[{"name"=>"test2", "value"=>1.58}],
22=>[{"name"=>"dff", "value"=>1.9}],
23=>[{"name"=>"dddd", "value"=>3.16}]
}
I want the highest value from this hash in a variable. The output should be
#h = 7.9 \\only float value which should be highest among all
so I am doing like this
#hash.each do |k, v|
if !v.nil?
#h= [v.flatten.sort{ |v1, v2| v2['value'] <=> v1['value'] }.first['value']]
end
end
but sometimes it works, and most of the times it doesn't.
#hash.values.flatten.compact.map { |h| h["value"] }.max
=> 7.9
Which equates to:
Get the values of the hash as an array
Flatten all the elements in the values array
Compact to remove all nil entries
Map the remaining entries to the ["value"] element in the hash
Return the maximum of all those value
It makes a lot of assumptions about the format of your #hash though.
I prefer #Shadwell's solution, but here's another way:
hash.select { |_,v| v.first }
.max_by { |_,v| v.first["value"] }
.last
.first["value"]
#=> 7.9
The steps (with all but one n=>[nil] element removed for readabiity):
hash = { 0=>[{"name"=>"guest", "value"=>7.9}],
1=>[nil],
19=>[{"name"=>"test", "value"=>2.5}],
20=>[{"name"=>"roam", "value"=>2.5}],
21=>[{"name"=>"test2", "value"=>1.58}],
22=>[{"name"=>"dff", "value"=>1.9}],
23=>[{"name"=>"dddd", "value"=>3.16}]}
h = hash.select { |_,v| v.first }
#=> { 0=>[{"name"=>"guest", "value"=>7.9}],
# 19=>[{"name"=>"test", "value"=>2.5}],
# 20=>[{"name"=>"roam", "value"=>2.5}],
# 21=>[{"name"=>"test2", "value"=>1.58}],
# 22=>[{"name"=>"dff", "value"=>1.9}],
# 23=>[{"name"=>"dddd", "value"=>3.16}]}
a = h.max_by { |_,v| v.first["value"] }
#=> [0, [{"name"=>"guest", "value"=>7.9}]]
b = a.last
#=> [{"name"=>"guest", "value"=>7.9}]
b.first["value"]
#=> 7.9

Resources