jeff
jeff

Reputation: 387

converting a hash into array in ruby

I need the next hash:

x = {
  params: {
    user_params1: { name: "stephen", dir: "2001", dir2: nil },
    user_params2: { name: "josh", dir: "jhon", dir2: nil }
}

to return a new hash of arrays like this:

x = {
  params: {
    user_params1: ["stephen","201", ""],
    user_params2: ["josh","jhon",""]
}

Upvotes: 2

Views: 1362

Answers (2)

jvillian
jvillian

Reputation: 20263

Given:

x = {
      params: {
        user_params1: { name: "stephen", dir: "2001", dir2: nil },
        user_params2: { name: "josh", dir: "jhon", dir2: nil }
      }
    }

Try:

x[:params] = x[:params].each_with_object({}) do |(k,v), returning|
  returning[k] = v.map{|k,v| v}
end

Which will yield:

{:params=>{:user_params1=>["stephen", "2001", nil], :user_params2=>["josh", "jhon", nil]}}

If you want empty strings instead of nils (as in your example), do:

x[:params] = x[:params].each_with_object({}) do |(k,v), returning|
  returning[k] = v.map{|k,v| v.to_s}
end

If you don't want to modify x, then just create a new hash and do the same:

y ={}
y[:params] = x[:params].each_with_object({}) do |(k,v), returning|
  returning[k] = v.map{|k,v| v.to_s}
end

Since you're not doing anything with that k in v.map, you could just do v.values.map(&:to_s) (stolen shamelessly from Gerry's answer) - which is cleaner, IMO, but costs you one extra character(!) - and end up with:

y ={}
y[:params] = x[:params].each_with_object({}) do |(k,v), returning|
  returning[k] = v.values.map(&:to_s)
end

As Sebastian points out, there is syntactic sugar for this:

y[:params] = x[:params].transform_values do |value|
  # Then use one of:
  #   hash.values.map { |value| value.nil? ? '' : value }
  #   hash.values.map { |value| value ? value : '' }
  #   hash.values.map { |value| value || '' }
  #   hash.values.map(&:to_s)      
end

Interestingly, if you look at the source code, you'll see that the each_with_object and tranform_values mechanics are quite similar:

def transform_values
  return enum_for(:transform_values) unless block_given?
  result = self.class.new
  each do |key, value|
    result[key] = yield(value)
  end
  result
end

You could imagine this re-written as:

def transform_values
  return enum_for(:transform_values) unless block_given?
  each_with_object(self.class.new) do |(key, value), result|
    result[key] = yield(value)
  end
end

Which, at its root (IMO), is pretty much what Gerry and I came up with.

Seems to me this cat is well-skinned.

Upvotes: 1

Gerry
Gerry

Reputation: 10497

You use each_with_object (twice in case you have more thane one key on the top level); for example:

x.each_with_object({}) do |(k, v), result|
  result[k] = v.each_with_object({}) do |(k1, v1), result1|
    result1[k1] = v1.values.map(&:to_s)
  end
end

#=> {:params=>{:user_params1=>["stephen", "2001", ""], :user_params2=>["josh", "jhon", ""]}}

Upvotes: 0

Related Questions