Mapping headers via File.readlines - ruby

This works great for me.
lines = CSV.readlines("log.csv")
a = lines.map{|s| {timestamp: s[0], url: s[1], ip: s[3]} }
puts a
Amended as clearer.
lines = CSV.readlines("log.csv").map do |s|
s = {timestamp: s[0], url: s[1], ip: s[3]}
end
puts a
But I am looking at doing additional filtering using grep, and this fails miserably.
1.9.3-p448 :129 > lines = File.readlines("log.csv").grep(/watch\?v=/)
=> []
1.9.3-p448 :134 > lines.map{|s| {timestamp: s[0], url: s[1], ip: s[3]} }
=> [{:timestamp=>"\"", :url=>"2", :ip=>" "}, {:timestamp=>"\"", :url=>"2", :ip=>" "}
Solution
a = File.readlines('log.csv').grep(/watch\?v=/).map do |s|
s = s.parse_csv;
{ timestamp: s[0], url: s[1], ip: s[3] }
end
puts a
Thanks.

The CSV class adds the parse_csv method to String. So you can parse the file records one at a time like this
lines = File.readlines("log.csv").grep(/watch\?v=/)
a = lines.map{ |s| s = s.parse_csv; {timestamp: s[0], url: s[1], ip: s[3]} }
puts a
or, preferably
a = File.readlines('log.csv').grep(/watch\?v=/).map do |s|
s = s.parse_csv;
{ timestamp: s[0], url: s[1], ip: s[3] }
end
puts a

Related

How to deep_transform_values with keys in Ruby?

Active Support's deep_transform_values recursively transforms all values of a hash. However, is there a similar method that would allow to access the keys of values while transforming?
I'd like to be able to do the following:
keys_not_to_transform = ['id', 'count']
response = { result: 'ok', errors: [], data: { id: '123', price: '100.0', quotes: ['1.0', '2.0'] }, count: 10 }
response.deep_transform_values! do |key, value|
# Use value's key to help decide what to do
return value if keys_not_to_transform.any? key.to_s
s = value.to_s
if s.present? && /\A[+-]?\d+(\.\d+)?\z/.match?(s)
return BigDecimal(s)
else
value
end
end
#Expected result
# =>{:result=>"ok", :errors=>[], :data=>{:id=>"123", :price=>0.1e3, :quotes=>[0.1e1, 0.2e1]}, :count=>10}
Note that we are not interested in transforming the key itself, just having it on hand while transforming the corresponding values.
You could use Hash#deep_merge! (provided by ActiveSupport) like so:
keys_not_to_transform = ['id', 'count']
transform_value = lambda do |value|
s = value.to_s
if s.present? && /\A[+-]?\d+(\.\d+)?\z/.match?(s)
BigDecimal(s)
else
value
end
end
transform = Proc.new do |key,value|
if keys_not_to_transform.include? key.to_s
value
elsif value.is_a?(Array)
value.map! do |v|
v.is_a?(Hash) ? v.deep_merge!(v,&transform) : transform_value.(v)
end
else
transform_value.(value)
end
end
response = { result: 'ok', errors: [], data: { id: '123', price: '100.0', quotes: ['1.0', '2.0'], other: [{id: '124', price: '17.0'}] }, count: 10 }
response.deep_merge!(response, &transform)
This outputs:
#=>{:result=>"ok", :errors=>[], :data=>{:id=>"123", :price=>0.1e3, :quotes=>[0.1e1, 0.2e1], :other=>[{:id=>"124", :price=>0.17e2}]}, :count=>10}
I'd just implement the necessary transformation logic with plain old Ruby and a bit of recursion, no external dependencies needed. For example:
def transform(hash, ignore_keys: [])
hash.each_with_object({}) do |(key, value), result|
if value.is_a?(Hash)
result[key] = transform(value, ignore_keys: ignore_keys)
elsif ignore_keys.include?(key.to_s)
result[key] = value
elsif value.to_s =~ /\A[+-]?\d+(\.\d+)?\z/
result[key] = BigDecimal(value)
else
result[key] = value
end
end
end
keys_not_to_transform = %w[id count]
response = { result: 'ok', errors: [], data: { id: '123', price: '100.0' }, count: 10 }
transform(response, ignore_keys: keys_not_to_transform)
# => {:result=>"ok", :errors=>[], :data=>{:id=>"123", :price=>#<BigDecimal:5566613bb128,'0.1E3',9(18)>}, :count=>10}

Serialize an array of hashes

I have an array of hashes:
records = [
{
ID: 'BOATY',
Name: 'McBoatface, Boaty'
},
{
ID: 'TRAINY',
Name: 'McTrainface, Trainy'
}
]
I'm trying to combine them into an array of strings:
["ID,BOATY","Name,McBoatface, Boaty","ID,TRAINY","Name,McTrainface, Trainy"]
This doesn't seem to do anything:
irb> records.collect{|r| r.each{|k,v| "\"#{k},#{v}\"" }}
#=> [{:ID=>"BOATY", :Name=>"McBoatface, Boaty"}, {:ID=>"TRAINY", :Name=>"McTrainface, Trainy"}]
** edit **
Formatting (i.e. ["Key0,Value0","Key1,Value1",...] is required to match a vendor's interface.
** /edit **
What am I missing?
records.flat_map(&:to_a).map { |a| a.join(',') }
#=> ["ID,BOATY", "Name,McBoatface, Boaty", "ID,TRAINY", "Name,McTrainface, Trainy"]
records = [
{
ID: 'BOATY',
Name: 'McBoatface, Boaty'
},
{
ID: 'TRAINY',
Name: 'McTrainface, Trainy'
}
]
# strait forward code
result= []
records.each do |hash|
hash.each do |key, value|
result<< key.to_s
result<< value
end
end
puts result.inspect
# a rubyish way (probably less efficient, I've not done the benchmark)
puts records.map(&:to_a).flatten.map(&:to_s).inspect
Hope it helps.
li = []
records.each do |rec|
rec.each do |k,v|
li << "#{k.to_s},#{v.to_s}".to_s
end
end
print li
["ID,BOATY", "Name,McBoatface, Boaty", "ID,TRAINY", "Name,McTrainface,
Trainy"]
You sure you wanna do it this way?
Check out Marshal. Or JSON.
You could even do it this stupid way using Hash#inspect and eval:
serialized_hashes = records.map(&:inspect) # ["{ID: 'Boaty'...", ...]
unserialized = serialized_hashes.map { |s| eval(s) }

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
}

Using Variables For Input

I'm currently using this code to make presidential projections:
require 'csv'
require 'statistics2'
require 'pollster'
require 'uri'
include Pollster
poll = Poll.where(:chart => '2016-national-gop-primary').first
responses = poll.questions.detect { |question| question.chart == '2016-national-gop-primary' }.responses
rubio = responses.detect { |response| response[:choice] == "Rubio" }
bush = responses.detect { |response| response[:choice] == "Bush" }
walker = responses.detect { |response| response[:choice] == "Walker" }
carson = responses.detect { |response| response[:choice] == "Carson" }
huckabee = responses.detect { |response| response[:choice] == "Huckabee" }
paul = responses.detect { |response| response[:choice] == "Paul" }
cruz = responses.detect { |response| response[:choice] == "Cruz" }
difference = rubio[:value] - walker[:value]
negativechance = Statistics2.tdist(2,(difference/3))
nchance = 1 - (Statistics2.tdist(2,(difference/3)))
if difference < 0
then puts "#{nchance.to_f*100}""%" + " Walker."
puts "#{(1-nchance.to_f)*100}""%" + " Rubio."
puts "Based on latest poll at Pollster.com"
else puts "#{negativechance.to_f*100}}""%" "Rubio."
puts "#{(1-negativechance.to_f)*100}""%" + " Walker."
puts "Based on latest poll at Pollster.com"
end
I want the user to be able to input two last names to find the projection for who will win. If I change this:
difference = rubio[:value] - walker[:value]
to variables it tells me I don't have a value.
Thanks in advance.
That looks an awful lot like a KV pair (Hash), so what if you make choice the key and value the value? Using that key you can get dynamic choices very easily, but you're going to have to do some legwork here yourself.
You need to go from this:
[{a: 'foo', b: 1}, {a: 'bar', b: 21}, {a: 'baz', b: 13}]
To this:
{'foo' => 1, 'bar' => 21, 'baz' => 13}
Hint: You're going to need map and to_h
[[:a, 1]].to_h # => {a: 1}

Ruby hash of hash

I need to have a hash to collect results, for example:
results = Hash.new()
results['127.0.0.1'] = Hash.new()
results['127.0.0.2'] = Hash.new()
results['127.0.0.1']['port'] = '80'
results['127.0.0.2']['port'] = '80'
results['127.0.0.1']['ver'] = 'abc'
results['127.0.0.1']['ver'] = 'def'
It seem works fine, but now can I show results? :)
I would like have:
ip: 127.0.0.1
port: 80
ver: abc
ip: 127.0.0.2
port: 80
ver: def
Thank you very much!
results.each do |k, v|
puts "IP: #{k}, Port: #{v['port']}, Ver: #{v['ver']}"
end
Better way to define hash:
result = {
'127.0.0.1' => {
port: 80,
ver: 'abc'
},
'127.0.0.2' => {
port: 80,
ver: 'def'
}
}
and then:
result.each do |key, value|
puts "ip: #{key}"
value.each { |k,v| puts "\t#{key}: #{value}" }
end
This method will work also if you add some extra options to hash.
But if you want it only for debugging read about awesome_print.

Resources