Roberto Pezzali
Roberto Pezzali

Reputation: 2494

Smart way to convert multiple arrays in hash?

I need an hint to convert four arrays into an hash.

My post params has these four arrays:

"jcrop-x"=>["0", "614", "0", "798"],
"jcrop-y"=>["0", "0", "273", "286"],
"jcrop-x2"=>["717", "666", "678", "482"],
"jcrop-y2"=>["567", "563", "529", "516"],

This is a sort of matrix: The value with index 0 of every array is the crop coordinates for my main_image. The second value is the crop coordinates for my square_image, etc.

I need to populate an array with this structure:

crop_params{
      main_image: {x: 0, y: 0, x2: 717, y2: 567},
      second_image: {x: 614, y: 0, x2: 666, y2: 563},
      third_image: {x: 0, y: 273, x2: 678, y2: 529},
      fourth_image: {x: 798, y: 286, x2: 482, y2: 516} 
}

This is my actual solution:

IMAGE_VERSION = [:main_news_img, :square_img, :vertical_img, :horizontal_img]
crop_params = {}
IMAGE_VERSION.each_with_index do |v,i|
  crop_params[v] = {}
  crop_params[v]["x"] = params['content']["jcrop-x"][i]
  crop_params[v]["y"] = params['content']["jcrop-y"][i]
  crop_params[v]["x2"] = params['content']["jcrop-x2"][i]
  crop_params[v]["y2"] = params['content']["jcrop-y2"][i]
end

I can do that with many lines of code. Is there any smart way to keep my helper clean and readable?

Upvotes: 1

Views: 110

Answers (6)

Cary Swoveland
Cary Swoveland

Reputation: 110675

I would do it this way:

params = {
  "jcrop-x"  => [  "0", "614",   "0", "798"],
  "jcrop-y"  => [  "0",   "0", "273", "286"],
  "jcrop-x2" => ["717", "666", "678", "482"],
  "jcrop-y2" => ["567", "563", "529", "516"],
}

IMAGE_VERSIONS = [:main_news_img, :square_img, :vertical_img, :horizontal_img]

def map_params(params)
  v = params.map { |key, values| [key.split('-').last, values]}
    # [["x", [  "0", "614",   "0", "798"]],["y" ,[  "0",   "0", "273", "286"]],
    #  ["x2",["717", "666", "678", "482"]],["y2",["567", "563", "529", "516"]]]
  keys, values = [v.map(&:first).map(&:to_sym), v.map(&:last).transpose]
    # keys   => [:x, :y, :x2, :y2]
    # values => [["0",   "0", "717", "567"], ["614",   "0", "666", "563"],
    #            ["0", "273", "678", "529"], ["798", "286", "482", "516"]]
  IMAGE_VERSIONS.each_with_object({}) {|k,h| h[k]=Hash[keys.zip(values.shift)]}
end     
    # { :main_news_img  => {:x=>  "0", :y=>  "0", :x2=>"717", :y2=>"567"},
    #   :square_img     => {:x=>"614", :y=>  "0", :x2=>"666", :y2=>"563"},
    #   :vertical_img   => {:x=>  "0", :y=>"273", :x2=>"678", :y2=>"529"},
    #   :horizontal_img => {:x=>"798", :y=>"286", :x2=>"482", :y2=>"516"} }

If the keys :x, :y, :x2, :y2 are not to be determined from the data, add

KEYS = [:x, :y, :x2, :y2]

and simplify the method to:

def map_params(params)
  values = params.map { |_, values| values }.transpose
    # [["0",   "0", "717", "567"], ["614",   "0", "666", "563"],
    #  ["0", "273", "678", "529"], ["798", "286", "482", "516"]]
  IMAGE_VERSIONS.each_with_object({}) {|k,h|h[k]=Hash[KEYS.zip(values.shift)]}
end  

Upvotes: 0

the Tin Man
the Tin Man

Reputation: 160551

Starting with an incoming set of parameters like:

params = {
  "jcrop-x"  => ["0", "614", "0", "798"],
  "jcrop-y"  => ["0", "0", "273", "286"],
  "jcrop-x2" => ["717", "666", "678", "482"],
  "jcrop-y2" => ["567", "563", "529", "516"],
}

I'd use:

transposed_params_values = params.values.transpose
crop_params = Hash[
  *[:main_image, :second_image, :third_image, :fourth_image].flat_map { |k|
    [
      k,
      Hash[
        [:x, :y, :x2, :y2].zip(transposed_params_values.shift)
      ]
    ]
  }
]
# => {:main_image=>{:x=>"0", :y=>"0", :x2=>"717", :y2=>"567"}, :second_image=>{:x=>"614", :y=>"0", :x2=>"666", :y2=>"563"}, :third_image=>{:x=>"0", :y=>"273", :x2=>"678", :y2=>"529"}, :fourth_image=>{:x=>"798", :y=>"286", :x2=>"482", :y2=>"516"}}

Or, written a bit more compactly:

transposed_params_values = params.values.transpose
crop_params = Hash[
  *[:main_image, :second_image, :third_image, :fourth_image].flat_map { |k|
    [k, Hash[ [:x, :y, :x2, :y2].zip(transposed_params_values.shift) ]]
  }
]
# => {:main_image=>{:x=>"0", :y=>"0", :x2=>"717", :y2=>"567"}, :second_image=>{:x=>"614", :y=>"0", :x2=>"666", :y2=>"563"}, :third_image=>{:x=>"0", :y=>"273", :x2=>"678", :y2=>"529"}, :fourth_image=>{:x=>"798", :y=>"286", :x2=>"482", :y2=>"516"}}

Here's a breakdown of what's happening:

transposed_params_values = params.values.transpose
# => [["0", "0", "717", "567"], ["614", "0", "666", "563"], ["0", "273", "678", "529"], ["798", "286", "482", "516"]]

Hash[[:x, :y, :x2, :y2].zip(transposed_params_values.first)]
# => {:x=>"0", :y=>"0", :x2=>"717", :y2=>"567"}

Upvotes: 0

Wayne Conrad
Wayne Conrad

Reputation: 107989

IMAGE_VERSIONS = [:main_news_img, :square_img, :vertical_img, :horizontal_img]

def map_params(params)
  hashes = params.map do |key, values|
    values.map do |value|
      [key.split('-').last, value.to_i]
    end
  end.transpose.map do |key_value_pairs|
    Hash[key_value_pairs]
  end
  Hash[IMAGE_VERSIONS.zip(hashes)]
end

pp map_params(params)
# => {:main_news_img=>{"x"=>0, "y"=>0, "x2"=>717, "y2"=>567},
# =>  :square_img=>{"x"=>614, "y"=>0, "x2"=>666, "y2"=>563},
# =>  :vertical_img=>{"x"=>0, "y"=>273, "x2"=>678, "y2"=>529},
# =>  :horizontal_img=>{"x"=>798, "y"=>286, "x2"=>482, "y2"=>516}}

Upvotes: 1

north636
north636

Reputation: 121

A version using zipping and mapping to achieve what you're looking for:

require 'pp'

params = {
  "jcrop-x"=>["0", "614", "0", "798"],
  "jcrop-y"=>["0", "0", "273", "286"],
  "jcrop-x2"=>["717", "666", "678", "482"],
  "jcrop-y2"=>["567", "563", "529", "516"]
}

# 1. Extract the value arrays, mapping all values to the result
#    of calling .to_i on them:
in_keys = %w{jcrop-x jcrop-y jcrop-x2 jcrop-y2}
in_values = in_keys.map { |key| params[key].map(&:to_i) }

# 2. Now transpose the array of arrays:
in_values_t = in_values.transpose

# 3. Now we have an array of arrays of numbers.  We want this to be
#    an array of hashes instead, so we map them:
out_subkeys = %i{x y x2 y2}
out_values = in_values_t.map { |a| Hash[out_subkeys.zip(a)] }

# 4. Finally, we simply zip that array with the output keys:
out_keys = %i{main_image second_image third_image fourth_image}
crop_params = Hash[out_keys.zip(out_values)]

pp crop_params

Upvotes: 0

Ajedi32
Ajedi32

Reputation: 48348

Hmm, well I'd say as is your solution is pretty good. Here's another way you might do it:

params = {
  "content" => {
    "jcrop-x"=>["0", "614", "0", "798"],
    "jcrop-y"=>["0", "0", "273", "286"],
    "jcrop-x2"=>["717", "666", "678", "482"],
    "jcrop-y2"=>["567", "563", "529", "516"],
  }
}
IMAGE_VERSION = [:main_news_img, :square_img, :vertical_img, :horizontal_img]

IMAGE_VERSION.each_with_index.with_object({}) do |image_and_index, object|
  image_version, i = image_and_index
  object[image_version] = Hash[params['content'].map{|key, value| [key.gsub(/^jcrop-/, ''), value[i]]}]
end

Upvotes: 1

BroiSatse
BroiSatse

Reputation: 44685

Assuming you always have 4 images try:

Hash[[:main_image, :second_image, :third_image, :fourth_image].zip params.values.transpose.map{|a| Hash[[:x, :y, :x2, :y2].zip(a)]}]

If not it will need to be modified slightly.

Upvotes: 1

Related Questions