1

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?

5 Answers 5

2

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.

Sign up to request clarification or add additional context in comments.

Comments

0
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"]]

1 Comment

In h1, if you change fake: "bar" to real: "car", you get: arr #=> ["car", ["test", "me", "foo"], ["other", "here", "okay"], "object"].
0

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"]]

Comments

0

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) }

Comments

0

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"]]

1 Comment

This assumes both hashes have the same keys.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.