Reputation: 3383
I have ruby hashes like this (except for approximately 20 key/values) :
{
fake: "bar",
test: "me",
other: "here"
}
{
fake: "object",
test: "foo",
other: "okay"
}
I need to create a two dimensional array from these hashes like so:
[
["fake", "bar", "object"],
["test", "me", "foo"]
]
I considered creating an array for each key and looping through objects to push their values:
fake_array = ["fake"]
items.each do |i|
fake_array << i[:fake]
end
That would mean creating an array for each array though (again, approximately 20) though and pushing attributes to their respective array. That seems silly - I'm thinking there's a cleaner way to do this. Help?
Upvotes: 1
Views: 127
Reputation: 350
In Ruby 1.9 or later the hashes are ordered. If you pre-ordered/align your two hashes, you can do this:
a = {:fake=>"bar", :test=>"me", :other=>"here"}
b = {:fake=>"object", :test=>"foo", :other=>"okay"}
a.keys.zip(a.values,b.values)
=> [[:fake, "bar", "object"], [:test, "me", "foo"], [:other, "here", "okay"]]
Upvotes: 0
Reputation: 398
Here's a concise one that I think does what you're after. Given hashes X1, X2, ... Xn
result = []
x1.zip(x2, xn) { |arr| result << arr.flatten.uniq.map(&:to_s) }
Upvotes: 0
Reputation: 3760
Try this:
require 'pp'
def hash_to_arr(arr_of_hashes)
temp_hash = {}
arr_of_hashes.each do |hash|
hash.each do |k,v|
ks = k.to_s
if(temp_hash[ks])
temp_hash[ks] << v
else
temp_hash[ks] = [v]
end
end
end
result = []
temp_hash.each do |k,v|
result << [k, *v]
end
result
end
pp hash_to_arr [
{
fake: "bar",
test: "me",
other: "here"
},
{
fake: "object",
test: "foo",
other: "okay"
}
]
# => [["fake", "bar", "object"], ["test", "me", "foo"], ["other", "here", "okay"]]
Upvotes: 0
Reputation: 110685
If arr
is an array of your hashes, I would suggest:
Code
def combine(arr)
arr.each_with_object({}) { |g,h|
g.each { |k,v| (h[k] ||=[]) << v } }.map { |k,v| [k,*v] }
end
which can alternatively be written:
def combine(arr)
arr.each_with_object(Hash.new {|h,k| h[k]=[]}) { |g,h|
g.each { |k,v| h[k] << v } }.map { |k,v| [k,*v] }
end
Example
arr = [
{
fake: "bar",
test: "me",
other: "here"
},
{
fake: "object",
test: "foo",
other: "okay"
}
]
h = combine(arr)
#=> [[:fake, "bar", "object"], [:test, "me", "foo"],
# [:other, "here", "okay"]]
Explanation
g.each { |k,v| (h[k] ||=[]) << v } }
adds the key value pairs of each hash g
in arr
to an initially empty hash h
. For each of those key-value pairs k,v
, if h
has the key k
, the value of that key in h
will be an array, so we execute:
(h[k] ||= []) << v
#=> (h[k] = h[k] || []) << v
#=> (h[k] = h[k]) << v
#=> h[k] << v
If, however, h
does not have that key, h[k] => nil
, so:
(h[k] ||= []) << v
#=> (h[k] = nil || []) << v
#=> (h[k] = []) << v
#=> h[k] = [v]
We first create the hash:
hash = arr.each_with_object(Hash.new {|h,k| h[k]=[]}) { |g,h|
g.each { |k,v| h[k] << v } }
#=> {:fake=>["bar", "object"], :test=>["me", "foo"],
# :other=>["here", "okay"]}
and then convert it to the desired array:
hash.map { |k,v| [k,*v] }
#=> [[:fake, "bar", "object"], [:test, "me", "foo"], [:other, "here", "okay"]]
Alternative
Here's another way:
def combine(arr)
arr.each_with_object({}) { |g,h|
h.update(g.merge(g) { |*_,v| [v] }) { |_,ov,nv| ov + nv } }
.map { |k,v| [k,*v] }
end
This uses the form of Hash#update (aka merge!
) that uses a block to resolve the values of keys that are present in both the hashes being merged.
Before being merged, each hash is converted to a hash whose keys are the same and whose values are arrays of those values. For example,
g = {
fake: "bar",
test: "me",
other: "here"
}
is converted to:
g.merge(g) { |*_,v| [v] }
#=> {
# fake: ["bar"],
# test: ["me"],
# other: ["here"]
# }
This gives us the same hash as that produced by the first method, and uses the same code to convert it to an array.
Upvotes: 2
Reputation: 14498
h1 = {
fake: "bar",
test: "me",
other: "here"
}
h2 = {
fake: "object",
test: "foo",
other: "okay"
}
arr = []
h1.merge(h2){|k,o,n| [k.to_s,o,n]}.each_value{ |v| arr << v}
(main):0 > arr
=> [["fake", "bar", "object"], ["test", "me", "foo"], ["other", "here", "okay"]]
Upvotes: 0