Related
I put a hash in a method:
def example
#_hash ||= {:a => {}, :b => {}}
end
I call that hash from another method, and add key/value to the sub-hash like so:
example[:a][:c] = "test"
This will change the hash to:
{:a => {:c => "test"}, :b => {}}
I don't get why I can still update the :c hash by calling the method again like so:
example[:a][:c] = "test2" #=> {:a => {:c => "test2"}, :b => {}}
This is odd because the equals/or operator should not assign the values again if #_hash is not nil/false. So I assume we do example.[]= method to change the value of an individual hash key/value. If that's true, then why is it that, when I remove ||= from the hash method like so:
def example
#_hash = {:a => {}, :b => {}}
end
nothing is changed?
The reason for this is that, when you call the example method again, it correctly returns the following hash:
example #=> {:a => {:c => "test"}, :b => {}} # variable like syntax
example() #=> {:a => {:c => "test"}, :b => {}} # method like syntax
But, when you do this:
example[:a][:c] = "test2"
What you are really doing is:
hash = example() #=> {:a => {:c => "test"}, :b => {}}
hash[:a][:c] = "test2" #=> {:a => {:c => "test2"}, :b => {}}
And, therefore, the hash is updated.
I have the following Array of Hashes:
a = [{:a => 1, :b => "x"}, {:a => 2, :b => "y"}]
I need to turn it into:
z={"x" => 1, "y" => 2}
or:
z={1 => "x", 2 => "y"}
Can I do this in a clean and functional way?
Something like this:
Hash[a.map(&:values)] # => {1=>"x", 2=>"y"}
if you want the other way:
Hash[a.map(&:values).map(&:reverse)] # => {"x"=>1, "y"=>2}
incorporating the suggestion from #squiguy:
Hash[a.map(&:values)].invert
I've read through quite a few of posts, but none seem to do just this, which is a bit tricky.
Say I have a hash that contains an array as one of its values.
hash = {
:a => 'one',
:arr => [
{:id => 'ten', :amount => 10, :b => 'two'},
{:id => 'twenty', :amount => 20, :b => 'two'},
{:id => 'apple', :amount => 7, :b => 'applesauce'}
],
:c => 3
}
I want to convert this to an array of hashes (which would be of the size of the contained array), as follows:
# => [
{:a => 'one', :id => 'ten', :amount => 10, :b => 'two', :c => 3},
{:a => 'one', :id => 'twenty', :amount => 20, :b => 'two', :c => 3},
{:a => 'one', :id => 'apple', :amount => 7, :b => 'applesauce', :c => 3}
]
The conversion should maintain whatever key/value pairs are inside and outside the array, and ideally I could pass in the key of the array to ask it perform the action:
flatten_hash_array(hash, :arr)
I realize that the Ruby flatten in the Array class is not what we need. Grasping for a verb! Any help would be appreciated.
This should do the job, barring validity checks.
def flatten_hash_array(hash, key)
hash[key].map {|entry| entry.merge(hash.reject {|k| k == key})}
end
We have the following datastructures:
{:a => ["val1", "val2"], :b => ["valb1", "valb2"], ...}
And I want to turn that into
[{:a => "val1", :b => "valb1"}, {:a => "val2", :b => "valb2"}, ...]
And then back into the first form. Anybody with a nice looking implementation?
This solution works with arbitrary numbers of values (val1, val2...valN):
{:a => ["val1", "val2"], :b => ["valb1", "valb2"]}.inject([]){|a, (k,vs)|
vs.each_with_index{|v,i| (a[i] ||= {})[k] = v}
a
}
# => [{:a=>"val1", :b=>"valb1"}, {:a=>"val2", :b=>"valb2"}]
[{:a=>"val1", :b=>"valb1"}, {:a=>"val2", :b=>"valb2"}].inject({}){|a, h|
h.each_pair{|k,v| (a[k] ||= []) << v}
a
}
# => {:a=>["val1", "val2"], :b=>["valb1", "valb2"]}
Using a functional approach (see Enumerable):
hs = h.values.transpose.map { |vs| h.keys.zip(vs).to_h }
#=> [{:a=>"val1", :b=>"valb1"}, {:a=>"val2", :b=>"valb2"}]
And back:
h_again = hs.first.keys.zip(hs.map(&:values).transpose).to_h
#=> {:a=>["val1", "val2"], :b=>["valb1", "valb2"]}
Let's look closely what the data structure we are trying to convert between:
#Format A
[
["val1", "val2"], :a
["valb1", "valb2"], :b
["valc1", "valc2"] :c
]
#Format B
[ :a :b :c
["val1", "valb1", "valc1"],
["val2", "valb2", "valc3"]
]
It is not diffculty to find Format B is the transpose of Format A in essential , then we can come up with this solution:
h={:a => ["vala1", "vala2"], :b => ["valb1", "valb2"], :c => ["valc1", "valc2"]}
sorted_keys = h.keys.sort_by {|a,b| a.to_s <=> b.to_s}
puts sorted_keys.inject([]) {|s,e| s << h[e]}.transpose.inject([]) {|r, a| r << Hash[*sorted_keys.zip(a).flatten]}.inspect
#[{:b=>"valb1", :c=>"valc1", :a=>"vala1"}, {:b=>"valb2", :c=>"valc2", :a=>"vala2"}]
m = {}
a,b = Array(h).transpose
b.transpose.map { |y| [a, y].transpose.inject(m) { |m,x| m.merge Hash[*x] }}
My attempt, perhaps slightly more compact.
h = { :a => ["val1", "val2"], :b => ["valb1", "valb2"] }
h.values.transpose.map { |s| Hash[h.keys.zip(s)] }
Should work in Ruby 1.9.3 or later.
Explanation:
First, 'combine' the corresponding values into 'rows'
h.values.transpose
# => [["val1", "valb1"], ["val2", "valb2"]]
Each iteration in the map block will produce one of these:
h.keys.zip(s)
# => [[:a, "val1"], [:b, "valb1"]]
and Hash[] will turn them into hashes:
Hash[h.keys.zip(s)]
# => {:a=>"val1", :b=>"valb1"} (for each iteration)
This will work assuming all the arrays in the original hash are the same size:
hash_array = hash.first[1].map { {} }
hash.each do |key,arr|
hash_array.zip(arr).each {|inner_hash, val| inner_hash[key] = val}
end
You could use inject to build an array of hashes.
hash = { :a => ["val1", "val2"], :b => ["valb1", "valb2"] }
array = hash.inject([]) do |pairs, pair|
pairs << { pair[0] => pair[1] }
pairs
end
array.inspect # => "[{:a=>["val1", "val2"]}, {:b=>["valb1", "valb2"]}]"
Ruby documentation has a few more examples of working with inject.
That is pretty easy with a plain hash like
{:a => "a", :b => "b"}
which would translate into
"a=a&b=b"
But what do you do with something more complex like
{:a => "a", :b => ["c", "d", "e"]}
which should translate into
"a=a&b[0]=c&b[1]=d&b[2]=e"
Or even worse, (what to do) with something like:
{:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]
Thanks for the much appreciated help with that!
For basic, non-nested hashes, Rails/ActiveSupport has Object#to_query.
>> {:a => "a", :b => ["c", "d", "e"]}.to_query
=> "a=a&b%5B%5D=c&b%5B%5D=d&b%5B%5D=e"
>> CGI.unescape({:a => "a", :b => ["c", "d", "e"]}.to_query)
=> "a=a&b[]=c&b[]=d&b[]=e"
http://api.rubyonrails.org/classes/Object.html#method-i-to_query
If you are using Ruby 1.9.2 or later, you can use URI.encode_www_form if you don't need arrays.
E.g. (from the Ruby docs in 1.9.3):
URI.encode_www_form([["q", "ruby"], ["lang", "en"]])
#=> "q=ruby&lang=en"
URI.encode_www_form("q" => "ruby", "lang" => "en")
#=> "q=ruby&lang=en"
URI.encode_www_form("q" => ["ruby", "perl"], "lang" => "en")
#=> "q=ruby&q=perl&lang=en"
URI.encode_www_form([["q", "ruby"], ["q", "perl"], ["lang", "en"]])
#=> "q=ruby&q=perl&lang=en"
You'll notice that array values are not set with key names containing [] like we've all become used to in query strings. The spec that encode_www_form uses is in accordance with the HTML5 definition of application/x-www-form-urlencoded data.
Update: This functionality was removed from the gem.
Julien, your self-answer is a good one, and I've shameless borrowed from it, but it doesn't properly escape reserved characters, and there are a few other edge cases where it breaks down.
require "addressable/uri"
uri = Addressable::URI.new
uri.query_values = {:a => "a", :b => ["c", "d", "e"]}
uri.query
# => "a=a&b[0]=c&b[1]=d&b[2]=e"
uri.query_values = {:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]}
uri.query
# => "a=a&b[0][c]=c&b[0][d]=d&b[1][e]=e&b[1][f]=f"
uri.query_values = {:a => "a", :b => {:c => "c", :d => "d"}}
uri.query
# => "a=a&b[c]=c&b[d]=d"
uri.query_values = {:a => "a", :b => {:c => "c", :d => true}}
uri.query
# => "a=a&b[c]=c&b[d]"
uri.query_values = {:a => "a", :b => {:c => "c", :d => true}, :e => []}
uri.query
# => "a=a&b[c]=c&b[d]"
The gem is 'addressable'
gem install addressable
No need to load up the bloated ActiveSupport or roll your own, you can use Rack::Utils.build_query and Rack::Utils.build_nested_query. Here's a blog post that gives a good example:
require 'rack'
Rack::Utils.build_query(
authorization_token: "foo",
access_level: "moderator",
previous: "index"
)
# => "authorization_token=foo&access_level=moderator&previous=index"
It even handles arrays:
Rack::Utils.build_query( {:a => "a", :b => ["c", "d", "e"]} )
# => "a=a&b=c&b=d&b=e"
Rack::Utils.parse_query _
# => {"a"=>"a", "b"=>["c", "d", "e"]}
Or the more difficult nested stuff:
Rack::Utils.build_nested_query( {:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}] } )
# => "a=a&b[][c]=c&b[][d]=d&b[][e]=e&b[][f]=f"
Rack::Utils.parse_nested_query _
# => {"a"=>"a", "b"=>[{"c"=>"c", "d"=>"d", "e"=>"e", "f"=>"f"}]}
Here's a short and sweet one liner if you only need to support simple ASCII key/value query strings:
hash = {"foo" => "bar", "fooz" => 123}
# => {"foo"=>"bar", "fooz"=>123}
query_string = hash.to_a.map { |x| "#{x[0]}=#{x[1]}" }.join("&")
# => "foo=bar&fooz=123"
Steal from Merb:
# File merb/core_ext/hash.rb, line 87
def to_params
params = ''
stack = []
each do |k, v|
if v.is_a?(Hash)
stack << [k,v]
else
params << "#{k}=#{v}&"
end
end
stack.each do |parent, hash|
hash.each do |k, v|
if v.is_a?(Hash)
stack << ["#{parent}[#{k}]", v]
else
params << "#{parent}[#{k}]=#{v}&"
end
end
end
params.chop! # trailing &
params
end
See http://noobkit.com/show/ruby/gems/development/merb/hash/to_params.html
class Hash
def to_params
params = ''
stack = []
each do |k, v|
if v.is_a?(Hash)
stack << [k,v]
elsif v.is_a?(Array)
stack << [k,Hash.from_array(v)]
else
params << "#{k}=#{v}&"
end
end
stack.each do |parent, hash|
hash.each do |k, v|
if v.is_a?(Hash)
stack << ["#{parent}[#{k}]", v]
else
params << "#{parent}[#{k}]=#{v}&"
end
end
end
params.chop!
params
end
def self.from_array(array = [])
h = Hash.new
array.size.times do |t|
h[t] = array[t]
end
h
end
end
I know this is an old question, but I just wanted to post this bit of code as I could not find a simple gem to do just this task for me.
module QueryParams
def self.encode(value, key = nil)
case value
when Hash then value.map { |k,v| encode(v, append_key(key,k)) }.join('&')
when Array then value.map { |v| encode(v, "#{key}[]") }.join('&')
when nil then ''
else
"#{key}=#{CGI.escape(value.to_s)}"
end
end
private
def self.append_key(root_key, key)
root_key.nil? ? key : "#{root_key}[#{key.to_s}]"
end
end
Rolled up as gem here: https://github.com/simen/queryparams
{:a=>"a", :b=>"b", :c=>"c"}.map{ |x,v| "#{x}=#{v}" }.reduce{|x,v| "#{x}&#{v}" }
"a=a&b=b&c=c"
Here's another way. For simple queries.
The best approach it is to use Hash.to_params which is the one working fine with arrays.
{a: 1, b: [1,2,3]}.to_param
"a=1&b[]=1&b[]=2&b[]=3"
require 'uri'
class Hash
def to_query_hash(key)
reduce({}) do |h, (k, v)|
new_key = key.nil? ? k : "#{key}[#{k}]"
v = Hash[v.each_with_index.to_a.map(&:reverse)] if v.is_a?(Array)
if v.is_a?(Hash)
h.merge!(v.to_query_hash(new_key))
else
h[new_key] = v
end
h
end
end
def to_query(key = nil)
URI.encode_www_form(to_query_hash(key))
end
end
2.4.2 :019 > {:a => "a", :b => "b"}.to_query_hash(nil)
=> {:a=>"a", :b=>"b"}
2.4.2 :020 > {:a => "a", :b => "b"}.to_query
=> "a=a&b=b"
2.4.2 :021 > {:a => "a", :b => ["c", "d", "e"]}.to_query_hash(nil)
=> {:a=>"a", "b[0]"=>"c", "b[1]"=>"d", "b[2]"=>"e"}
2.4.2 :022 > {:a => "a", :b => ["c", "d", "e"]}.to_query
=> "a=a&b%5B0%5D=c&b%5B1%5D=d&b%5B2%5D=e"
If you are in the context of a Faraday request, you can also just pass the params hash as the second argument and faraday takes care of making proper param URL part out of it:
faraday_instance.get(url, params_hsh)
I like using this gem:
https://rubygems.org/gems/php_http_build_query
Sample usage:
puts PHP.http_build_query({"a"=>"b","c"=>"d","e"=>[{"hello"=>"world","bah"=>"black"},{"hello"=>"world","bah"=>"black"}]})
# a=b&c=d&e%5B0%5D%5Bbah%5D=black&e%5B0%5D%5Bhello%5D=world&e%5B1%5D%5Bbah%5D=black&e%5B1%5D%5Bhello%5D=world
2.6.3 :001 > hash = {:a => "a", :b => ["c", "d", "e"]}
=> {:a=>"a", :b=>["c", "d", "e"]}
2.6.3 :002 > hash.to_a.map { |x| "#{x[0]}=#{x[1].class == Array ? x[1].join(",") : x[1]}"
}.join("&")
=> "a=a&b=c,d,e"