2

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",""]
}
jeff
  • 367
  • 4
  • 19
  • 1
    That second thing isn't valid. In particular, these parts: `{"stephen","201", ""}` and `{"josh","jhon",""}`. And, actually, neither is that first thing (you appear to be missing a close bracket). – jvillian Apr 19 '18 at 15:32
  • 1
    https://stackoverflow.com/questions/9560335/ruby-hash-to-array-of-values – Kapitula Alexey Apr 19 '18 at 15:38
  • @jvillian you're right, I already update my post – jeff Apr 19 '18 at 15:42
  • I have to imagine that *one* of these is going to work for you! – jvillian Apr 19 '18 at 16:39
  • 1
    What is the code you are having trouble with? What trouble do you have with your code? Do you get an error message? What is the error message? Is the result you are getting not the result you are expecting? What result do you expect and why, what is the result you are getting and how do the two differ? Is the behavior you are observing not the desired behavior? What is the desired behavior and why, what is the observed behavior, and in what way do they differ? Please, provide a [mcve]. – Jörg W Mittag Apr 19 '18 at 17:48
  • 1
    Can you provide a *precise* specification of what it is that you want to happen, including any and all rules, exceptions from those rules, corner cases, special cases, boundary cases, and edge cases? Can you provide sample inputs and outputs demonstrating what you expect to happen, both in normal cases, and in all the exceptions, corner cases, special cases, boundary cases, and edge cases? Please, also make sure to provide a [mcve]. – Jörg W Mittag Apr 19 '18 at 17:48
  • 1
    "I need the next hash to return a new hash of arrays like this" – You do it by writing a program which does that. If you have a problem with your program, carefully read the documentation of all the methods, classes, modules, and libraries you are using, write tests for your programs, trace the execution with pen and paper, single-step it in a debugger, then sleep on it, start again from the beginning, sleep on it again, and *then and only then* narrow your problem down to a concise, focused, simple, short, reproducible [mcve] and ask a specific, focused, narrow question on [so]. – Jörg W Mittag Apr 19 '18 at 17:49

2 Answers2

1

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.

jvillian
  • 19,953
  • 5
  • 31
  • 44
0

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", ""]}}
Gerry
  • 10,337
  • 3
  • 31
  • 40