settheline
settheline

Reputation: 3383

Generate a ruby two-dimensional array from ruby hashes

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

Answers (5)

xjlin0
xjlin0

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

Dave N
Dave N

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

Anand
Anand

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

Cary Swoveland
Cary Swoveland

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

AGS
AGS

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

Related Questions