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) }
Related
I've got a variable I'd like to use as a key to a hash that contains its own key and array.
e.g.
custArray = Array.new
custArray << {"c1001" => {"purchases" => ["prod01"]}}
I want to be able to do something like:
if custArray[:c1001].exists?
custArray[{:c1001["purchases"]} << "prod02"]
end
but I'm just totally stuck.
You can resolve it with:
if c = custArray.find { |h| h.key? 'c1001' }
c.dig('c1001', 'purchases') << "prod2"
end
Or if you can have more than one result with this key:
custArray.select { |h| h.key? 'c1001' }.each do |c|
c.dig('c1001', 'purchases') << "prod2"
end
If you only want to update the first instance of the array you can do:
target = custArray.find { |hash| hash.key? 'c1001' }
target['c1001']['purchases'] << 'prod02' if target
If you want to update all instances of the array you can do (backslashes are for console purposes only):
custArray \
.select { |hash| hash.key? 'c1001' } \
.each { |hash| hash['c1001']['purchases'] << 'prod02' }
You can use select from the array of hashes to see if the key is there:
target = custArray.find { |h| h.key? 'c1001' }
target['c1001']['purchases'] << "prod02" unless target.nil?
Or if array contains multiple hashes with the same key:
custArray.select { |h| h.key? 'c1001' }.each do |h|
h['c1001']['purchases'] << "prod02"
end
Also, you can write something similar to code that you already provide
custArray.each do |h|
h['c1001']['purchases'] << 'prod02' if h.keys.include?('c1001')
end
That allows reducing the count of iteration loops
custArray.find { |h| h.key?('c1001') }&.dig('c1001', 'purchases')&.push("prod02")
#=> ["prod01", "prod02"]
custArray
#=> [{"c1001"=>{"purchases"=>["prod01", "prod02"]}}]
custArray.find { |h| h.key?('c1002') }&.dig('c1002', 'purchases')&.push("prod02")
#=> nil
custArray
#=> [{"c1001"=>{"purchases"=>["prod01"]}}]
custArray.find { |h| h.key?('c1001') }&.dig('c1001', 'popsicles')&.push("prod02")
#=> nil
custArray
#=> [{"c1001"=>{"purchases"=>["prod01"]}}]
& is Ruby's Safe Navigation Operator. See also Hash#dig. Both made their debut in Ruby v2.3.
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]
Given I have this hash:
h = { a: 'a', b: 'b', c: { d: 'd', e: 'e'} }
And I convert to OpenStruct:
o = OpenStruct.new(h)
=> #<OpenStruct a="a", b="b", c={:d=>"d", :e=>"e"}>
o.a
=> "a"
o.b
=> "b"
o.c
=> {:d=>"d", :e=>"e"}
2.1.2 :006 > o.c.d
NoMethodError: undefined method `d' for {:d=>"d", :e=>"e"}:Hash
I want all the nested keys to be methods as well. So I can access d as such:
o.c.d
=> "d"
How can I achieve this?
You can monkey-patch the Hash class
class Hash
def to_o
JSON.parse to_json, object_class: OpenStruct
end
end
then you can say
h = { a: 'a', b: 'b', c: { d: 'd', e: 'e'} }
o = h.to_o
o.c.d # => 'd'
See Convert a complex nested hash to an object.
I came up with this solution:
h = { a: 'a', b: 'b', c: { d: 'd', e: 'e'} }
json = h.to_json
=> "{\"a\":\"a\",\"b\":\"b\",\"c\":{\"d\":\"d\",\"e\":\"e\"}}"
object = JSON.parse(json, object_class:OpenStruct)
object.c.d
=> "d"
So for this to work, I had to do an extra step: convert it to json.
personally I use the recursive-open-struct gem - it's then as simple as RecursiveOpenStruct.new(<nested_hash>)
But for the sake of recursion practice, I'll show you a fresh solution:
require 'ostruct'
def to_recursive_ostruct(hash)
result = hash.each_with_object({}) do |(key, val), memo|
memo[key] = val.is_a?(Hash) ? to_recursive_ostruct(val) : val
end
OpenStruct.new(result)
end
puts to_recursive_ostruct(a: { b: 1}).a.b
# => 1
edit
Weihang Jian showed a slight improvement to this here https://stackoverflow.com/a/69311716/2981429
def to_recursive_ostruct(hash)
hash.each_with_object(OpenStruct.new) do |(key, val), memo|
memo[key] = val.is_a?(Hash) ? to_recursive_ostruct(val) : val
end
end
Also see https://stackoverflow.com/a/63264908/2981429 which shows how to handle arrays
note
the reason this is better than the JSON-based solutions is because you can lose some data when you convert to JSON. For example if you convert a Time object to JSON and then parse it, it will be a string. There are many other examples of this:
class Foo; end
JSON.parse({obj: Foo.new}.to_json)["obj"]
# => "#<Foo:0x00007fc8720198b0>"
yeah ... not super useful. You've completely lost your reference to the actual instance.
Here's a recursive solution that avoids converting the hash to json:
def to_o(obj)
if obj.is_a?(Hash)
return OpenStruct.new(obj.map{ |key, val| [ key, to_o(val) ] }.to_h)
elsif obj.is_a?(Array)
return obj.map{ |o| to_o(o) }
else # Assumed to be a primitive value
return obj
end
end
My solution is cleaner and faster than #max-pleaner's.
I don't actually know why but I don't instance extra Hash objects:
def dot_access(hash)
hash.each_with_object(OpenStruct.new) do |(key, value), struct|
struct[key] = value.is_a?(Hash) ? dot_access(value) : value
end
end
Here is the benchmark for you reference:
require 'ostruct'
def dot_access(hash)
hash.each_with_object(OpenStruct.new) do |(key, value), struct|
struct[key] = value.is_a?(Hash) ? dot_access(value) : value
end
end
def to_recursive_ostruct(hash)
result = hash.each_with_object({}) do |(key, val), memo|
memo[key] = val.is_a?(Hash) ? to_recursive_ostruct(val) : val
end
OpenStruct.new(result)
end
require 'benchmark/ips'
Benchmark.ips do |x|
hash = { a: 1, b: 2, c: { d: 3 } }
x.report('dot_access') { dot_access(hash) }
x.report('to_recursive_ostruct') { to_recursive_ostruct(hash) }
end
Warming up --------------------------------------
dot_access 4.843k i/100ms
to_recursive_ostruct 5.218k i/100ms
Calculating -------------------------------------
dot_access 51.976k (± 5.0%) i/s - 261.522k in 5.044482s
to_recursive_ostruct 50.122k (± 4.6%) i/s - 250.464k in 5.008116s
My solution, based on max pleaner's answer and similar to Xavi's answer:
require 'ostruct'
def initialize_open_struct_deeply(value)
case value
when Hash
OpenStruct.new(value.transform_values { |hash_value| send __method__, hash_value })
when Array
value.map { |element| send __method__, element }
else
value
end
end
Here is one way to override the initializer so you can do OpenStruct.new({ a: "b", c: { d: "e", f: ["g", "h", "i"] }}).
Further, this class is included when you require 'json', so be sure to do this patch after the require.
class OpenStruct
def initialize(hash = nil)
#table = {}
if hash
hash.each_pair do |k, v|
self[k] = v.is_a?(Hash) ? OpenStruct.new(v) : v
end
end
end
def keys
#table.keys.map{|k| k.to_s}
end
end
Basing a conversion on OpenStruct works fine until it doesn't. For instance, none of the other answers here properly handle these simple hashes:
people = { person1: { display: { first: 'John' } } }
creds = { oauth: { trust: true }, basic: { trust: false } }
The method below works with those hashes, modifying the input hash rather than returning a new object.
def add_indifferent_access!(hash)
hash.each_pair do |k, v|
hash.instance_variable_set("##{k}", v.tap { |v| send(__method__, v) if v.is_a?(Hash) } )
hash.define_singleton_method(k, proc { hash.instance_variable_get("##{k}") } )
end
end
then
add_indifferent_access!(people)
people.person1.display.first # => 'John'
Or if your context calls for a more inline call structure:
creds.yield_self(&method(:add_indifferent_access!)).oauth.trust # => true
Alternatively, you could mix it in:
module HashExtension
def very_indifferent_access!
each_pair do |k, v|
instance_variable_set("##{k}", v.tap { |v| v.extend(HashExtension) && v.send(__method__) if v.is_a?(Hash) } )
define_singleton_method(k, proc { self.instance_variable_get("##{k}") } )
end
end
end
and apply to individual hashes:
favs = { song1: { title: 'John and Marsha', author: 'Stan Freberg' } }
favs.extend(HashExtension).very_indifferent_access!
favs.song1.title
Here is a variation for monkey-patching Hash, should you opt to do so:
class Hash
def with_very_indifferent_access!
each_pair do |k, v|
instance_variable_set("##{k}", v.tap { |v| v.send(__method__) if v.is_a?(Hash) } )
define_singleton_method(k, proc { instance_variable_get("##{k}") } )
end
end
end
# Note the omission of "v.extend(HashExtension)" vs. the mix-in variation.
Comments to other answers expressed a desire to retain class types. This solution accommodates that.
people = { person1: { created_at: Time.now } }
people.with_very_indifferent_access!
people.person1.created_at.class # => Time
Whatever solution you choose, I recommend testing with this hash:
people = { person1: { display: { first: 'John' } }, person2: { display: { last: 'Jingleheimer' } } }
If you are ok with monkey-patching the Hash class, you can do:
require 'ostruct'
module Structurizable
def each_pair(&block)
each do |k, v|
v = OpenStruct.new(v) if v.is_a? Hash
yield k, v
end
end
end
Hash.prepend Structurizable
people = { person1: { display: { first: 'John' } }, person2: { display: { last: 'Jingleheimer' } } }
puts OpenStruct.new(people).person1.display.first
Ideally, instead of pretending this, we should be able to use a Refinement, but for some reason I can't understand it didn't worked for the each_pair method (also, unfortunately Refinements are still pretty limited)
I'm trying to make page with table which content is data from two arrays.
I have two lists(arrays) with hashes:
arr1 = [
{ "device"=>100, "phone"=>"12345" },
...,
{ "device"=>102, "phone"=>"12346" }
]
arr2 = [
{ "device"=>100, "type"=>"mobile", "name"=>"nokia" },
...,
{ "device"=>102, "type"=>"VIOP", "name"=>"smth" }
]
How can I join hashes from arr1 and arr2 by "device" to get a result array:
result = [
{ "device"=>100, "phone"=>"12345", "type"=>"mobile", "name"=>"nokia" },
...,
{ "device"=>102, "phone"=>"12346", "type"=>"VIOP", "name"=>"smth" }
]
Page which consist table with result array, loads very slowly and I need to find the fastest way to generate result_array.
Help me please.
This would work:
(arr1 + arr2).group_by { |i| i["device"] }.map { |d,(i1,i2)| i1.merge(i2)}
#=> [{"device"=>100, "phone"=>"12345", "type"=>"mobile", "name"=>"nokia"}, {"device"=>102, "phone"=>"12346", "type"=>"VIOP", "name"=>"smth"}]
Multiple ways to tackle it. Here is a quite readable way to do it:
# prepare an index hash for easier access of data by device
first_by_device = arr1.group_by {|a| a['device'] }
# build a new array joining both data hashes for each item
result = arr2.map do |item|
device = item['device']
item.merge first_by_device(device)
end
(arr1 + arr2).group_by { |i| i["device"] }.values.map{|x|x.reduce(&:merge)}
Maybe it's not the prettiest one, but it works:
result = arr1.collect{|a| h = arr2.select{|b| b["device"] ==
a["device"]}.first; h ? a.merge(h) : a }
Do you need something faster for large amount of data?
h = Hash.new
arr1.each do |a|
h[ a["device" ] ] ||= Hash.new
h[ a["device" ] ].merge!(a)
end
arr2.each do |a|
h[ a["device" ] ] ||= Hash.new
h[ a["device" ] ].merge!(a)
end
result = h.values
What's a convenient way to get a list of all Hash keys (with nesting) separated by dots?
Given I have a hash:
{ level1: { level21: { level31: 'val1',
level32: 'val2' },
level22: 'val3' }
}
Desired output (array of strings) which represents all key paths in a hash:
level1.level21.level31
level1.level21.level32
level1.level22
My current solution:
class HashKeysDumper
def self.dump(hash)
hash.map do |k, v|
if v.is_a? Hash
keys = dump(v)
keys.map { |k1| [k, k1].join('.') }
else
k.to_s
end
end.flatten
end
end
It also available as gist (with specs).
Well, it depends on what you mean by cleaner, but here's a smaller version that…
Will work on subclasses Hashes or Hash-alikes
Extends Hash, making it look cleaner in your code.
class Hash
def keydump
map{|k,v|v.keydump.map{|a|"#{k}.#{a}"} rescue k.to_s}.flatten
end
end
results:
{ level1: { level21: { level31: 'val1',
level32: 'val2' },
level22: 'val3' }
}.keydump
=> ["level1.level21.level31", "level1.level21.level32", "level1.level22"]
Here is my vision of this:
h = { 'level1' => { 'level2' => { 'level31' => 'val1', 'level32' => 'val2' } } }
class Hash
def nested_keys
self.inject([]) { |f, (k,v)| f += [k, v.is_a?(Hash) ? v.nested_keys : []] }.flatten
end
end
keys = h.nested_keys
p keys
#=> ["level1", "level2", "level31", "level32"]
k1, k2 = keys.shift, keys.shift
puts [k1, k2, keys.shift].join('.')
#=> level1.level2.level31
puts [k1, k2, keys.shift].join('.')
#=> level1.level2.level32
Here is a Working Demo
I just committed some code to RubyTree that adds from_hash() which would allow you to do this:
require 'rubytree'
Tree::TreeNode.from_hash(hash).each_leaf.map{|n| "#{n.name}.#{n.parentage.map(&:name).reverse.join('.')}" }
=> ["level1.level21.level31", "level1.level21.level32", "level1.level22"]
Aside from the gem require, it's a one-liner :)