Shortest way to convert a Hash into String - ruby

there is a Hash like this:
params = { k1: :v1, k2: :v2, etc: :etc }
i need it converted to a string like this:
k1="v1", k2="v2", etc="etc"
i have a working version:
str = ""
params.each_pair { |k,v| str << "#{k}=\"#{v}\", " }
but it smells like ten PHP spirits ...
what's the Ruby way to do this?

try this:
str = params.map {|p| '%s="%s"' % p }.join(', ')
see it in action here

Try this...
hash.collect { |k,v| "#{k} = #{v}" }.join(" ,")

Related

Is there a way to refactor this without using 3 collectors to combine strings?

I'm trying to refactor the following:
def method_name
array = ["abcdef", "ghijkl", "mnopqr"]
collector1 = ""
collector2 = ""
collector3 = ""
array.each do |string|
collector1 += string[0..1]
collector2 += string[2..3]
collector3 += string[4..5]
end
x = collector1 + "\n" + collector2 + "\n" + collector3
# "abghmn\ncdijop\nefklqr"
end
Are there any more efficient ways to write this? Or perhaps a different enumerable that will achieve the same result?
"abghmn\ncdijop\nefklqr" is the desired output!
Thanks!!
def method_name
array = ["abcdef", "ghijkl", "mnopqr"]
array.map { |s| s.chars.each_slice(2).to_a }.transpose.map(&:join).join("\n")
end
You could write the following.
array = ["abcdef", "ghijkl", "mnopqr"]
ranges = [0..1, 2..3, 4..5]
ranges.map { |r| array.map { |s| s[r] }.join }.join('\n')
#=> "abghmn\\ncdijop\\nefklqr"
or
ranges.map { |r| array.reduce('') { |t,s| t + s[r] } }.join('\n')
#=> "abghmn\\ncdijop\\nefklqr"

Best way to parse json in Ruby for the format given

For my rails app, SQL query result is received in the below format.
#data= JSON.parse(request,symbolize_names: true)[:data]
# #data sample
[{"time":"2017-11-14","A":0,"B":0,"C":0,"D":0,"E":0},
{"time":"2017-11-15","A":0,"B":0,"C":0,"D":0,"E":0},
{"time":"2017-11-16","A":2,"B":1,"C":1,"D":0,"E":1},
{"time":"2017-11-17","A":0,"B":0,"C":1,"D":0,"E":1},
{"time":"2017-11-20","A":0,"B":0,"C":0,"D":0,"E":0},
{"time":"2017-11-21","A":6,"B":17,"C":0,"D":0,"E":1}]
But I want the data in the format
[{"name":"A","data":{"2017-11-16":2,"2017-11-21":6}},
{"name":"B","data":{"2017-11-16":1,"2017-11-21":17}},
{"name":"C","data":{"2017-11-16":1,"2017-11-17":1}},
{"name":"D","data":{}},
{"name":"E","data":{"2017-11-16":1,"2017-11-17":1,"2017-11-21":1}}]
What is the best way to parse this in Ruby?
I tried using #data.each method, but it is lengthy.
I am totally new to Ruby. Any help would be appreciated.
Oddly specific question, but kinda an interesting problem so I took a stab at it. If this is coming from a SQL database I feel like the better solution would be to have SQL format the data for you as opposed to transforming it in ruby.
#data = JSON.parse(request,symbolize_names: true)[:data]
intermediate = {}
#data.each do |row|
time = row.delete(:time)
row.each do |key, val|
intermediate[key] ||= {data: {}}
intermediate[key][:data][time] = val if val > 0
end
end
transformed = []
intermediate.each do |key, val|
transformed << {name: key.to_s, data: val}
end
At the end of this transformed will contain the transformed data. Horrible variable names, and I hate having to do this in two passes. But got something working and figured I would share in case it is helpful.
I agree with csexton that it looks like a better query to source the data would be the ultimate solution here.
Anyway, here's a solution that's similar to csexton's but uses nested default Hash procs to simplify some of the operations:
def pivot(arr, column)
results = Hash.new do |hash, key|
hash[key] = Hash.new(0)
end
arr.each do |hash|
data = hash.dup
pivot = data.delete(column)
data.each_pair do |name, value|
results[name][pivot] += value
end
end
results.map { |name, data| {
name: name.to_s,
data: data.delete_if { |_, sum| sum.zero? }
}}
end
pivot(#data, :time) # => [{:name=>"A", :data=>{"2017-11-16"=>2, "2017-11-21"=>6}}, ..
Here's a more "Ruby-ish" (depending on who you ask) solution:
def pivot(arr, column)
arr
.flat_map do |hash|
hash
.to_a
.delete_if { |key, _| key == column }
.map! { |data| data << hash[column] }
end
.group_by(&:shift)
.map { |name, outer| {
name: name.to_s,
data: outer
.group_by(&:last)
.transform_values! { |inner| inner.sum(&:first) }
.delete_if { |_, sum| sum.zero? }
}}
end
pivot(#data, :time) # => [{:name=>"A", :data=>{"2017-11-16"=>2, "2017-11-21"=>6}}, ..
Quite frankly, I find it pretty unreadable and I wouldn't want to support it. :)
arr = [{"time":"2017-11-14","A":0,"B":0,"C":0,"D":0,"E":0},
{"time":"2017-11-15","A":0,"B":0,"C":0,"D":0,"E":0},
{"time":"2017-11-16","A":2,"B":1,"C":1,"D":0,"E":1},
{"time":"2017-11-17","A":0,"B":0,"C":1,"D":0,"E":1},
{"time":"2017-11-20","A":0,"B":0,"C":0,"D":0,"E":0},
{"time":"2017-11-21","A":6,"B":17,"C":0,"D":0,"E":1}]
(arr.first.keys - [:time]).map do |key|
{ name: key.to_s,
data: arr.select { |h| h[key] > 0 }.
each_with_object({}) { |h,g| g.update(h[:time]=>h[key]) } }
end
#=> [{:name=>"A", :data=>{"2017-11-16"=>2, "2017-11-21"=>6}},
# {:name=>"B", :data=>{"2017-11-16"=>1, "2017-11-21"=>17}},
# {:name=>"C", :data=>{"2017-11-16"=>1, "2017-11-17"=>1}},
# {:name=>"D", :data=>{}},
# {:name=>"E", :data=>{"2017-11-16"=>1, "2017-11-17"=>1, "2017-11-21"=>1}}]
Note that
arr.first.keys - [:time]
#=> [:A, :B, :C, :D, :E]

How to recursively convert keys of Ruby Hashes that are symbols to String

Suppose I have following hash or nested hash:
h = { :a1 => { :b1 => "c1" },
:a2 => { :b2 => "c2"},
:a3 => { :b3 => "c3"} }
I want to create a method that takes hash as a parameter and recursively convert all the keys (keys that are symbol eg. :a1) to String (eg. "a1"). So far I have come up with the following method which doesn't work and returns {"a1"=>{:b1=>"c1"}, "a2"=>{:b2=>"c2"}, "a3"=>{:b3=>"c3"}}.:
def stringify_all_keys(hash)
stringified_hash = {}
hash.each do |k, v|
stringified_hash[k.to_s] = v
if v.class == Hash
stringify_all_keys(stringified_hash[k.to_s])
end
end
stringified_hash
end
What am I doing wrong and how do a get all the keys converted to string like this:
{"a1"=>{"b1"=>"c1"}, "a2"=>{"b2"=>"c2"}, "a3"=>{"b3"=>"c3"}}
If you are using ActiveSupport already or are open to using it, then deep_stringify_keys is what you're looking for.
hash = { person: { name: 'Rob', age: '28' } }
hash.deep_stringify_keys
# => {"person"=>{"name"=>"Rob", "age"=>"28"}}
Quick'n'dirty if your values are basic objects like strings, numbers, etc:
require 'json'
JSON.parse(JSON.dump(hash))
Didn't test this, but looks about right:
def stringify_all_keys(hash)
stringified_hash = {}
hash.each do |k, v|
stringified_hash[k.to_s] = v.is_a?(Hash) ? stringify_all_keys(v) : v
end
stringified_hash
end
using plain ruby code, the below code could help.
you can monkey patched it to the ruby Hash, to use it like this my_hash.deeply_stringfy_keys
however, I do not recommend monkey batching ruby.
you can adjust the method to provide the deeply_strigify_keys! (bang) version of it.
in case you want to make a different method witch does not stringify recursively, or to control the level of stringifying then consider re-writing the below method logic so you can have it written better with considering the other variation mentioned above.
def deeply_stringify_keys(hash)
stringified_hash = {}
hash.each do |k, v|
if v.is_a?(Hash)
stringified_hash[k.to_s] = deeply_stringify_keys(v)
elsif v.is_a?(Array)
stringified_hash[k.to_s] = v.map {|i| i.is_a?(Hash)? deeply_stringify_keys(i) : i}
else
stringified_hash[k.to_s] = v
end
end
stringified_hash
end

How to split string in array in two?

I currently have a single column CSV file such as: ["firstname lastname", "firstname lastname", ...].
I would like to create a CSV file such as ["f.lastname", "f.lastname"...]; f being the first letter of the firstname.
Any idea how I should do that ?
update
Ok well, I feel that I am close thanks to you guys, here's what I got so far :
require 'csv'
filename = CSV.read("mails.csv")
mails = []
CSV.foreach(filename) do |col|
mails << filename.map { |n| n.sub(/\A(\w)\w* (\w+)\z/, '\1. \2') }
end
puts mails.to_s
But I still get an error.
update2
Ok this works just fine :
require 'csv'
mails = []
CSV.foreach('mails.csv', :headers => false) do |row|
mails << row.map(&:split).map{|f,l| "#{f[0]}.#{l}#mail.com" }
end
File.open("mails_final.csv", 'w') {|f| f.puts mails }
puts mails.to_s
Thanks a lot to all of you ;)
A solution without using regular expression:
ary = ["firstname lastname", "firstname lastname"]
ary.map(&:split).map{|f, l| "#{f[0]}. #{l}" }
#=> ["f. lastname", "f. lastname"]
ary = ["firstname lastname", "firstname lastname"]
ary.map{|a| e=a.split(" "); e[0][0]+"."+e[1]}
#=> ["f.lastname", "f.lastname"]
You need to modify your this following code:--
CSV.foreach(filename) do |col|
mails << filename.map { |n| n.sub(/\A(\w)\w* (\w+)\z/, '\1. \2') }
end
to match something like the following:--
CSV.foreach(path_to_csv_file/mails.csv, headers: true/false) do |row|
# row is kind _of CSV::Row, do not use filename.map => causing error
mails << row.to_hash.map { |n| n.sub(/\A(\w)\w* (\w+)\z/, '\1. \2') }
end
I would do that way:
array = ["firstname lastname", "firstname lastname"]
array.map { |n| "#{n[0]}.#{n.split[1]}" }

ruby string to hash conversion

I have a string like this,
str = "uu#p, xx#m, yy#n, zz#m"
I want to know how to convert the given string into a hash. (i.e my actual requirement is, how many values (before the # symbol) have the m, n and p. I don't want the counting, I need an exact value). The output would be better like this,
{"m" => ["xx", "zz"], "n" => ["yy"], "p" => ["uu"]}
Can help me anyone, please?
Direct copy/past of an IRB session:
>> str.split(/, /).inject(Hash.new{|h,k|h[k]=[]}) do |h, s|
.. v,k = s.split(/#/)
.. h[k] << v
.. h
.. end
=> {"p"=>["uu"], "m"=>["xx", "zz"], "n"=>["yy"]}
Simpler code for a newbie :)
str = "uu#p, xx#m, yy#n, zz#m"
h = {}
str.split(",").each do |x|
v,k = x.split('#')
h[k] ||= []
h[k].push(v)
end
p h
FP style:
grouped = str
.split(", ")
.group_by { |s| s.split("#")[1] }
.transform_values { |ss| ss.map { |x| s.split("#")[0] } }
#=> {"m"=>["xx", "zz"], "n"=>["yy"], "p"=>["uu"]}
This is a pretty common pattern. Using Facets.map_by:
require 'facets'
str.split(", ").map_by { |s| s.split("#", 2).reverse }
#=> {"m"=>["xx", "zz"], "n"=>["yy"], "p"=>["uu"]}

Resources