get rid of "duplicates" in nested hash - ruby-on-rails

I have a nested hash:
given = {
"AA" => {
:GE => nil,
"GE" => "successful",
:GR => nil,
:ZG => nil,
"ZG" => "successful",
},
"BB" => {
:MM => nil,
"MM" => "successful",
:GR => nil,
:ZZ => nil,
"ZZ" => "successful",
}
}
and my goal is to transform it into a new hash without the duplicates eg. :GE/"GE" and :ZG/"ZG" and so on.
goal = {
"AA" => {
:GE => "successful",
:GR => nil,
:ZG => "successful",
},
"BB" => {
:MM => "successful",
:GR => nil,
:ZZ => "successful",
}
}
My attempt is with Rails method index_by
given.map do |key, value|
value.index_by {|r| value[r]}
end
Or with
given.each { |key,value| temp_hash = {} (value.each { |va| va[0].each { |k,v| temp_hash|key| << val }}) given_hash[k.to_sym] = temp_hash if given.has_key?(k.to_sym)}
But I'm kind of stuck. Any help is greatly appreciated!

I'm assuming that in the case of { :GE => nil, "GE" => "successful" }, you want to use the first truthy value ("successful") and make sure the key is a symbol:
result = given.transform_values do |inner_hsh|
inner_hsh.group_by do |k,v|
k.to_sym
end.transform_values do |key_vals|
key_vals.to_h.values.find(&:itself)
end
end

Assuming that #max's assumption is correct, and also that the order of the keys in inner hashes is not important, here is another way to produce the desired return value.
given.each_with_object({}) do |(k,v),h|
h[k] = v.sort_by { |_,w| w.nil? ? 1 : 0 }.uniq { |m,_| m.to_s }.to_h
end
#=> {"AA"=>{"GE"=>"successful", "ZG"=>"successful", :GR=>nil},
# "BB"=>{"MM"=>"successful", "ZZ"=>"successful", :GR=>nil}}
See Array#uniq, particularly the sentence, "self is traversed in order, and the first occurrence is kept.".
For
k = "AA"
v = { :GE=>nil, "GE"=>"successful", :GR=>nil, :ZG=>nil, "ZG"=>"successful" }
we compute
a = v.sort_by { |_,w| w.nil? ? 1 : 0 }
#=> [["GE", "successful"], ["ZG", "successful"],
# [:GE, nil], [:GR, nil], [:ZG, nil]]
b = a.uniq { |m,_| m.to_s }
#=> [["GE", "successful"], ["ZG", "successful"], [:GR, nil]]
because "GE" precedes :GE and "ZG" precedes :ZG. Lastly,
h[k] = b.to_h
#=> {"GE"=>"successful", "ZG"=>"successful", :GR=>nil}

Related

How to search nested hash of arrays and arrays of hash and only return matching object from the parent node?

Say I have the below ruby hash nested
hash_or_array = [{
"book1" => "buyer1",
"book2" => {
"book21" => "buyer21", "book22" => ["buyer23", "buyer24", true]
},
"book3" => {
"0" => "buyer31", "1" => "buyer32", "2" => "buyer33",
"3" => [{
"4" => "buyer34",
"5" => [10, 11],
"6" => [{
"7" => "buyer35"
}]
}]
},
"book4" => ["buyer41", "buyer42", "buyer43"],
"book5" => {
"book5,1" => "buyer5"
}
}]
And I want to search for a string that matches buyer35. On match, I want it to return the following result
"book3" => {
"3" => [{
"6" => [{
"7" => "buyer35"
}]
}]
}]
All, other non matching keys,values, arrays should be omitted. I have the following example, but it doesn't quite work
def search(hash)
hash.each_with_object({}) do |(key, value), obj|
if value.is_a?(Hash)
returned_hash = search(value)
obj[key] = returned_hash unless returned_hash.empty?
elsif value.is_a?(Array)
obj[key] = value if value.any? { |v| matches(v) }
elsif matches(key) || matches(value)
obj[key] = value
end
end
end
def matches(str)
match_criteria = /#{Regexp.escape("buyer35")}/i
(str.is_a?(String) || str == true || str == false) && str.to_s.match?(match_criteria)
end
....
=> search(hash_or_array)
Any help is appreciated. I realize, I need to use recursion, but can't quite figure how to build/keep track of the matched node from the parent node.
You can use the following recursive method.
def recurse(obj, target)
case obj
when Array
obj.each do |e|
case e
when Array, Hash
rv = recurse(e, target)
return [rv] unless rv.nil?
when target
return e
end
end
when Hash
obj.each do |k,v|
case v
when Array, Hash
rv = recurse(v, target)
return {k=>rv} unless rv.nil?
when target
return {k=>v}
end
end
end
nil
end
recurse(hash_or_array, "buyer35")
#=> [{"book3"=>{"3"=>[{"6"=>[{"7"=>"buyer35"}]}]}}]
recurse(hash_or_array, "buyer24")
#=>[{"book2"=>{"book22"=>"buyer24"}}]
recurse(hash_or_array, "buyer33")
#=> [{"book3"=>{"2"=>"buyer33"}}]
recurse(hash_or_array, 11)
#=> [{"book3"=>{"3"=>[{"5"=>11}]}}]
recurse(hash_or_array, "buyer5")
#=>[{"book5"=>{"book5,1"=>"buyer5"}}]
If desired, one may write, for example,
recurse(hash_or_array, "buyer35").first
#=> {"book3"=>{"3"=>[{"6"=>[{"7"=>"buyer35"}]}]}}

Method to check hash for values which may be a single string or an array of strings

I have a hash which contains values such as....
EXAMPLE = {
"101" => "SO01",
"102" => "SO02",
"103" => "SO03",
"105" => %w(S005 SO04)
}
I want to search the hash for say SO04 and get back "105", or "SO03" and get back "103". I'm sure there is a good way to do this but it's slipping my mind currently. I had been simply using EXAMPLE.key() but that's not going to cut it now that I realize there are arrays in there.
Try this:
>> EXAMPLE = {"101" => "SO01",
"102" => "SO02",
"103" => "SO03",
"105" => %w(SO05 SO04)}
>> EXAMPLE.find { |_,v| Array(v).include?('SO02') }.try(:first)
=> "102"
>> EXAMPLE.find { |_,v| Array(v).include?('SO05') }.try(:first)
=> "105"
>> EXAMPLE.find { |_,v| Array(v).include?('SO06') }.try(:first)
=> nil
.first is needed on the end because #find returns an array of [key, value].
If you are using Ruby 2.3 or later, you can use the safe navigation operator in place of try:
>> EXAMPLE.find { |_,v| Array(v).include?('SO05') }&.first
=> "105"
>> EXAMPLE.find { |_,v| Array(v).include?('SO06') }&.first
=> nil
EXAMPLE = {
"101" => "SO01",
"102" => "SO02",
"103" => "SO03",
"105" => ["S005", "SO04"]
}
def value_to_key(h, value)
h.keys.find { |k| Array(h[k]).include?(value) }
end
value_to_key(EXAMPLE, "SO02") #=> "102"
value_to_key(EXAMPLE, "SO04") #=> "105"
value_to_key(EXAMPLE, "cat") #=> nil
Note:
Array("SO03") #=> ["SO03"]
Array(["S005", "SO04"]) #=> ["S005", "SO04"]
An equivalent, but more memory-demanding, method is shown below. The reason for the difference in memory requirements is explained in the comments.
def value_to_key(h, value)
h.keys.find { |k| [*h[k]].include?(value) }
end
[*"SO03"] #=> ["SO03"]
[*["S005", "SO04"]] #=> ["S005", "SO04"]

Rails merge multiple params together

How can I merge two params together from my permissions hash that share the same "school_id" and "plan_type'. Then delete the permission that was merged from the hash, just leaving one. There can also be more than two that match.
[{"school_id"=>"1",
"plan_type"=>"All",
"view"=>"true",
"create"=>"true",
"approve"=>"true",
"grant"=>"true",
"region_id"=>nil},
{"school_id"=>"1", "plan_type"=>"All", "edit"=>"true", "region_id"=>nil},
{"school_id"=>"2",
"plan_type"=>"All",
"edit"=>"true",
"grant"=>"true",
"region_id"=>nil}]
def create_permissions(user, params)
permissions = params[:permissions].values.map { |perm|
if perm[:plan_type] == "" || perm[:plan_type] == "All Plans"
perm[:plan_type] = "All"
end
#perm_type = get_permission_type(perm)
case
when 'school' then perm.merge(region_id: nil)
when 'region' then perm.merge(school_id: nil)
end
}.tap { |permissions|
new_permissions = []
permissions.each do |perm|
set_permissions = permissions.find {|x| (x != perm && x[:school_id] == perm[:school_id] && x[:plan_type] == perm[:plan_type]) }
end
params[:user][:region_ids] = permissions.map { |perm| perm[:region_id] }.compact
params[:user][:school_ids] = permissions.map { |perm| perm[:school_id] }.compact
}
end
Output:
[{"school_id"=>"1",
"plan_type"=>"All",
"view"=>"true",
"create"=>"true",
"approve"=>"true",
"grant"=>"true",
"region_id"=>nil},
"edit"=>"true"
{"school_id"=>"2",
"plan_type"=>"All",
"edit"=>"true",
"grant"=>"true",
"region_id"=>nil}]
Group by school_id and then reduce by merging hashes:
input.group_by { |e| e['school_id'] }
.values
.map { |v| p v.reduce(&:merge) }
To group by many fields, one might use an array of desired fields, a concatenated string, whatever:
input.group_by { |e| [e['school_id'], e['plan_type']] }
.values
.map { |v| p v.reduce(&:merge) }
or, to keep nifty captions:
input.group_by { |e| "School: #{e['school_id']}, Plan: #{e['plan_type']}" }
.map { |k,v| [k, v.reduce(&:merge)] }
.to_h
#⇒ {
# "School: 1, Plan: All" => {
# "approve" => "true",
# "create" => "true",
# "edit" => "true",
# "grant" => "true",
# "plan_type" => "All",
# "region_id" => nil,
# "school_id" => "1",
# "view" => "true"
# },
# "School: 2, Plan: All" => {
# "edit" => "true",
# "grant" => "true",
# "plan_type" => "All",
# "region_id" => nil,
# "school_id" => "2"
# }
#}
arr1 = arr.group_by { |e| [e["school_id"],e["plan_type"]] }.values
=> {["1", "All"]=>[{"school_id"=>"1", "plan_type"=>"All", "view"=>"true", "create"=>"true", "approve"=>"true", "grant"=>"true", "region_id"=>nil}, {"school_id"=>"1", "plan_type"=>"All", "edit"=>"true", "region_id"=>nil}], ["2", "All"]=>[{"school_id"=>"2", "plan_type"=>"All", "edit"=>"true", "grant"=>"true", "region_id"=>nil}]}
arr1.map{ |i| i.inject({}) { |sum, e| sum.merge e}}
=> [{"school_id"=>"1", "plan_type"=>"All", "view"=>"true", "create"=>"true", "approve"=>"true", "grant"=>"true", "region_id"=>nil, "edit"=>"true"}, {"school_id"=>"2", "plan_type"=>"All", "edit"=>"true", "grant"=>"true", "region_id"=>nil}]

Removing a hash from an array of hashes?

h = {
:vehicle => [
[0] {
:make => "Honda",
:year => 2010
},
[1] {
:make => "Kia",
:year => 2014
},
[2] {
:make => "Saturn",
:year => 2005
}
]
}
I would like to remove {:make=>"Kia", :year=>2014} so that h is:
h = {
:vehicle => [
[0] {
:make => "Honda",
:year => 2010
},
[1] {
:make => "Saturn",
:year => 2005
}
]
}
I tried:
h[:vehicle].delete_if{ |_,v| v == "Kia" }
#=> does nothing
h.delete_if{ |_,v| v == "Kia" }
#=> does nothing
h[:vehicle].tap { |_,v| v.delete("Kia") }
#=> does nothing
h.delete("Kia")
#=> nil
h[:vehicle].delete("Kia")
#=> nil
Here's where I'm getting a headache:
h[:vehicle].include?("Kia")
#=> false
h[:vehicle][1]
#=> {:make=>"Kia", :year=>2014}
h[:vehicle][1].include?("Kia")
#=> false
Thanks for the help.
h[:vehicle].delete_if { |h| h[:make] == 'Kia' }
Will return a copy of h with Kia removed. Note that although its a somewhat strange way to do it, your first example does work for me. Remember that you have to look at the returned value to see the result - delete_if does not modify the original hash.

In Rails, what is the best way to compact a hash into a nested hash

Say I have this:
[
{ :user_id => 1, :search_id => a},
{ :user_id => 1, :search_id => b},
{ :user_id => 2, :search_id => c},
{ :user_id => 2, :search_id => d}
]
and I want to end up with:
[
{ :user_id => 1, :search_id => [a,b]},
{ :user_id => 2, :search_id => [c,d]}
]
What is the best way to do that?
Very strange requirement indeed. Anyway
[ { :user_id => 1, :search_id => "a"},
{ :user_id => 1, :search_id => "b"},
{ :user_id => 2, :search_id => "c"},
{ :user_id => 2, :search_id => "d"} ] \
.map{ |h| h.values_at(:user_id, :search_id) } \
.group_by(&:first) \
.map{ |k, v| { :user_id => k, :search_id => v.map(&:last) } }
array.group_by{|x| x[:user_id] }.values.map do |val|
{ user_id: val.first[:user_id],
search_id: val.inject([]){|me, el| me << el[:search_id]} }
end
First off, I think the cleaner output structure here is to allow the user IDs to be the hash keys and the list of search IDs to be the values:
{
1 => [a, b],
2 => [c, d]
}
There might be a clever way using Rails helpers to get this structure, but it's not too bad to do it manually, either:
output = {}
input.each do |row|
key = row[:user_id]
value = row[:search_id]
output[key] ||= []
output[key] << value
end
I agree with Matchus representation of the data and just want to suggest a shorter version for it (input being the initial array).
input.each.with_object(Hash.new {|h,k| h[k] = []}) do |k,o|
o[k[:user_id]] << k[:search_id]
end
EDIT: this is Ruby > 1.9
input.inject({}) do
|m, h| (m[h[:user_id]] ||= []) << h[:search_id]; m
end.inject([]) { |m, (k, v)| m << { :user_id => k, :search_id => v }; m }

Resources