How to sort Ruby Hash with dynamic keys - ruby

I want to understand how I can sort ruby hashes with key and or their subkeys if need be. For examples:
Example of hash with sub_hashs
two_hash = {
'a' => {'displayName' => "A", 'name' => "Apple", 'Association' => {'id' => '1', 'type' => 'B2'}},
'c' => {'displayName' => "D", 'name' => "Banana", 'Association' => {'id' => '1', 'type' => 'B3'}},
'b' => {'displayName' => "C", 'name' => "Orange", 'Association' => {'id' => '1', 'type' => 'B1'}},
'd' => {'displayName' => "B", 'name' => "Kiwi", 'Association' => {'id' => '1', 'type' => 'B4'}}
}
Currently I can sort it like this, calling and specify the key/subkey to sort by.
puts (two_hash.sort_by {|h, k| k['displayName']})
puts (two_hash.sort_by {|h, k| k['Association']['type']})
I want to convert it to a proc and used it whenever I want given a subkey input.
I want it so I can just pass in a key or a subkey and it will sort for me. like so, is there a way to do this in Ruby?
sort_stuff_method(two_hash, ['displayname'])
sort_stuff_method(two_hash, ['Association']['type'])

This is one way to do that:
def sort_em(h, *nested_keys)
h.sort_by { |k,v| nested_keys.reduce(v) { |g,k| g[k] } }
end
sort_em(two_hash, 'displayName')
#=> [["a", {"displayName"=>"A", "name"=>"Apple",
# "Association"=>{"id"=>"1", "type"=>"B2"}}],
# ["d", {"displayName"=>"B", "name"=>"Kiwi",
# "Association"=>{"id"=>"1", "type"=>"B4"}}],
# ["b", {"displayName"=>"C", "name"=>"Orange",
# "Association"=>{"id"=>"1", "type"=>"B1"}}],
# ["c", {"displayName"=>"D", "name"=>"Banana",
# "Association"=>{"id"=>"1", "type"=>"B3"}}]]
sort_em(two_hash, 'Association', 'type')
#=> [["b", {"displayName"=>"C", "name"=>"Orange",
# "Association"=>{"id"=>"1", "type"=>"B1"}}],
# ["a", {"displayName"=>"A", "name"=>"Apple",
# "Association"=>{"id"=>"1", "type"=>"B2"}}],
# ["c", {"displayName"=>"D", "name"=>"Banana",
# "Association"=>{"id"=>"1", "type"=>"B3"}}],
# ["d", {"displayName"=>"B", "name"=>"Kiwi",
# "Association"=>{"id"=>"1", "type"=>"B4"}}]]
This of course works for any number of nested keys.

Is this what you are looking for,
def sort_stuff_method(input_hash, sort_key, sort_sub_key=nil)
input_hash.sort_by{|h,k| sort_sub_key.nil? ? k[sort_key] : k[sort_key][sort_sub_key]}
end
and you can use it like this,
sort_stuff_method(input_hash, 'displayName')
sort_stuff_method(input_hash, 'Association', 'Type')

Related

Ruby - Convert a string concatanated by & into a hash

I have a string of the form
str="a=b&c=d&e=f&...."
Question is how do I convert the above str in below form
{ "a" => "b" , "c" => "d" , "e" => "f" .... }
You can use this method URI::decode_www_form.
require 'uri'
URI.decode_www_form "a=b&c=d&e=f"
# => [["a", "b"], ["c", "d"], ["e", "f"]]
URI.decode_www_form("a=b&c=d&e=f").to_h
# => {"a"=>"b", "c"=>"d", "e"=>"f"}
Just out of curiosity:
▶ q = "a=b&c=d&e=f"
▶ require 'json'
#⇒ true
▶ JSON.parse "{\"#{q}\"}".gsub /[=&]/, Hash('=' => '":"', '&' => '","')
#⇒ {
# "a" => "b",
# "c" => "d",
# "e" => "f"
#}
The straight way with splits:
q.split('&').map { |e| e.split('=') }.to_h
Most simple answer is:
hash = Rack::Utils.parse_query("a=b&c=d&e=f")
=> {"a"=>"b", "c"=>"d", "e"=>"f"} #output
and if you want to revert again then:
hash.to_query
str.split("&").inject({}) do |sum, e|
k, v = e.split("=")
sum.merge(k => v)
end
=> {"a"=>"b", "c"=>"d", "e"=>"f"}

Create a combination of a array of hash

I have an array like:
hasharray=[{"a" => "b" } , {"c" => "d"} , {"e" => "f"}]
I want to create all combinations of this array hash of length min to max.
For instance, with min=0 and max=2, the code should return:
resultarray=[
{},
{"a" => "b" },
{"c" => "d"},
{"e" => "f"},
{"a" => "b" } , {"c" => "d"},
{"c" => "d"} , {"e" => "f"},
{"a" => "b" },{"e" => "f"}
]
How do I do it?
min = 0
max = 2
min.upto(max).flat_map {|n| hasharray.combination(n).to_a }
# => [
# [],
# [{"a"=>"b"}], [{"c"=>"d"}], [{"e"=>"f"}],
# [{"a"=>"b"}, {"c"=>"d"}], [{"a"=>"b"}, {"e"=>"f"}], [{"c"=>"d"}, {"e"=>"f"}]
# ]

Merge hashes based on particular key/value pair in ruby

I am trying to merge an array of hashes based on a particular key/value pair.
array = [ {:id => '1', :value => '2'}, {:id => '1', :value => '5'} ]
I would want the output to be
{:id => '1', :value => '7'}
As patru stated, in sql terms this would be equivalent to:
SELECT SUM(value) FROM Hashes GROUP BY id
In other words, I have an array of hashes that contains records. I would like to obtain the sum of a particular field, but the sum would grouped by key/value pairs. In other words, if my selection criteria is :id as in the example above, then it would seperate the hashes into groups where the id was the same and the sum the other keys.
I apologize for any confusion due to the typo earlier.
Edit: The question has been clarified since I first posted my answer. As a result, I have revised my answer substantially.
Here are two "standard" ways of addressing this problem. Both use Enumerable#select to first extract the elements from the array (hashes) that contain the given key/value pair.
#1
The first method uses Hash#merge! to sequentially merge each array element (hashes) into a hash that is initially empty.
Code
def doit(arr, target_key, target_value)
qualified = arr.select {|h|h.key?(target_key) && h[target_key]==target_value}
return nil if qualified.empty?
qualified.each_with_object({}) {|h,g|
g.merge!(h) {|k,gv,hv| k == target_key ? gv : (gv.to_i + hv.to_i).to_s}}
end
Example
arr = [{:id => '1', :value => '2'}, {:id => '2', :value => '3'},
{:id => '1', :chips => '4'}, {:zd => '1', :value => '8'},
{:cat => '2', :value => '3'}, {:id => '1', :value => '5'}]
doit(arr, :id, '1')
#=> {:id=>"1", :value=>"7", :chips=>"4"}
Explanation
The key here is to use the version of Hash#merge! that uses a block to determine the value for each key/value pair whose key appears in both of the hashes being merged. The two values for that key are represented above by the block variables hv and gv. We simply want to add them together. Note that g is the (initially empty) hash object created by each_with_object, and returned by doit.
target_key = :id
target_value = '1'
qualified = arr.select {|h|h.key?(target_key) && h[target_key]==target_value}
#=> [{:id=>"1", :value=>"2"},{:id=>"1", :chips=>"4"},{:id=>"1", :value=>"5"}]
qualified.empty?
#=> false
qualified.each_with_object({}) {|h,g|
g.merge!(h) {|k,gv,hv| k == target_key ? gv : (gv.to_i + hv.to_i).to_s}}
#=> {:id=>"1", :value=>"7", :chips=>"4"}
#2
The other common way to do this kind of calculation is to use Enumerable#flat_map, followed by Enumerable#group_by.
Code
def doit(arr, target_key, target_value)
qualified = arr.select {|h|h.key?(target_key) && h[target_key]==target_value}
return nil if qualified.empty?
qualified.flat_map(&:to_a)
.group_by(&:first)
.values.map { |a| a.first.first == target_key ? a.first :
[a.first.first, a.reduce(0) {|tot,s| tot + s.last}]}.to_h
end
Explanation
This may look complex, but it's not so bad if you break it down into steps. Here's what's happening. (The calculation of qualified is the same as in #1.)
target_key = :id
target_value = '1'
c = qualified.flat_map(&:to_a)
#=> [[:id,"1"],[:value,"2"],[:id,"1"],[:chips,"4"],[:id,"1"],[:value,"5"]]
d = c.group_by(&:first)
#=> {:id=>[[:id, "1"], [:id, "1"], [:id, "1"]],
# :value=>[[:value, "2"], [:value, "5"]],
# :chips=>[[:chips, "4"]]}
e = d.values
#=> [[[:id, "1"], [:id, "1"], [:id, "1"]],
# [[:value, "2"], [:value, "5"]],
# [[:chips, "4"]]]
f = e.map { |a| a.first.first == target_key ? a.first :
[a.first.first, a.reduce(0) {|tot,s| tot + s.last}] }
#=> [[:id, "1"], [:value, "7"], [:chips, "4"]]
f.to_h => {:id=>"1", :value=>"7", :chips=>"4"}
#=> {:id=>"1", :value=>"7", :chips=>"4"}
Comment
You may wish to consider makin the values in the hashes integers and exclude the target_key/target_value pairs from qualified:
arr = [{:id => 1, :value => 2}, {:id => 2, :value => 3},
{:id => 1, :chips => 4}, {:zd => 1, :value => 8},
{:cat => 2, :value => 3}, {:id => 1, :value => 5}]
target_key = :id
target_value = 1
qualified = arr.select { |h| h.key?(target_key) && h[target_key]==target_value}
.each { |h| h.delete(target_key) }
#=> [{:value=>2}, {:chips=>4}, {:value=>5}]
return nil if qualified.empty?
Then either
qualified.each_with_object({}) {|h,g| g.merge!(h) { |k,gv,hv| gv + hv } }
#=> {:value=>7, :chips=>4}
or
qualified.flat_map(&:to_a)
.group_by(&:first)
.values
.map { |a| [a.first.first, a.reduce(0) {|tot,s| tot + s.last}] }.to_h
#=> {:value=>7, :chips=>4}

Ruby, turn array of hashes into single hash

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

Ruby: How to turn a hash into HTTP parameters?

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"

Resources