Correct way to search an Array instead of where - arrays

How to query search an Array with where? What's the correct way?
U have an association like this:
# Foo has many bars
Foo.first.bars
Controller:
def index
#bars = []
#datas = Foo.where(email: current_user.email)
#datas.map { |d| #bars.push(d.bar).where("name like ?", "%#{params[:email]}%") }
respond_to do |format|
format.html
format.json { render json: #bars }
end
end
Instead of where for Array, what is the correct query term?

You can use the select method to filter out values in an array given a certain condition.
[1,2,3,4,5].select { |num| num.even? } #=> [2, 4]
Or for your particular example:
#bars = #datas.map { |d| d.bars }.select { |b| b.name.include? params[:email] }
However, since you actually don't have an array of bars and would have to create it, this is simply an unnecessary step and a more straightforward solution would be:
#datas = Foo.where(email: current_user.email)
# #bars is an array
#bars = #datas.map { |d| d.bars.where("name like ?", "%#{params[:email]}%") }
or
# #bars is an ActiveRecord object
#bars = Bar.where(id: #datas.pluck(:id)).where("name like ?", "%#{params[:email]}%")

Related

In Ruby, how can I sort an array of hashes

I am new to Ruby, could someone help?
I have some product data Json that I need to sort by the expiry date, however everything thing I have tried with .sort_by so far is erroring.
The Json is in this format
{"wh_Repeating":[
{
"wh": {
"Item_Number": "111166",
"Expiry_Date": "2023-05-05"
}
},
{
"wh": {
"Item_Number": "111167",
"Expiry_Date": "2023-05-01"
}
},
{
"wh": {
"Item_Number": "111168",
"Expiry_Date": "2023-05-09"
}
}]}
in Ruby that shows as
{:wh_Repeating=>[
{:wh=>{:Item_Number=>"111166", :Expiry_Date=>"2023-05-05"}},
{:wh=>{:Item_Number=>"111167", :Expiry_Date=>"2023-05-01"}},
{:wh=>{:Item_Number=>"111168", :Expiry_Date=>"2023-05-09"}}
]}
tried alsorts
latest attempt was
sorted = jsonIn["wh_Repeating"]
sorted.sort_by { |k,v| v[:"Expiry_Date"] }
puts sorted
which gave me
undefined method `sort_by' for nil:NilClass (NoMethodError)
(Exception)
Your hash keys are symbols not strings.
jsonIn["wh_Repeating"] should be jsonIn[:wh_Repeating]
Also, sorted.sort_by { |k,v| v[:"Expiry_Date"] } does not mutate sorted.
sort_by does not mutate the receiver. In other words, the value of sorted remains the same. There is a bang version (sort_by!) that does mutate (a side-effect) but the use of mutating functions is discouraged.
This does what you want to do.
jsonIn[:wh_Repeating].sort_by { |h| h.dig(:wh, :Expiry_Date) }
I would do it like this:
data = {:wh_Repeating=>
[{:wh=>{:Item_Number=>"111166", :Expiry_Date=>"2023-05-05"}},
{:wh=>{:Item_Number=>"111167", :Expiry_Date=>"2023-05-01"}},
{:wh=>{:Item_Number=>"111168", :Expiry_Date=>"2023-05-09"}}]}
data[:wh_Repeating].sort_by! { |hash| hash[:wh][:Expiry_Date] }
data
#=> {:wh_Repeating=>
[{:wh=>{:Item_Number=>"111167", :Expiry_Date=>"2023-05-01"}},
{:wh=>{:Item_Number=>"111166", :Expiry_Date=>"2023-05-05"}},
{:wh=>{:Item_Number=>"111168", :Expiry_Date=>"2023-05-09"}}]}
I have assumed the original hash (h below) is not to be mutated (modified).
h = { :wh_Repeating=>[
{:wh=>{:Item_Number=>"111166", :Expiry_Date=>"2023-05-05"}},
{:wh=>{:Item_Number=>"111167", :Expiry_Date=>"2023-05-01"}},
{:wh=>{:Item_Number=>"111168", :Expiry_Date=>"2023-05-09"}}
]
}
As this hash has a single key (:wh_Repeating), we can simply write the structure of the desired hash as
{ :wh_Repeating=>... }
and then compute the value of :wh_Repeating.
{ :wh_Repeating=>h[:wh_Repeating].sort_by { |g| g[:wh][:Expiry_Date] } }
#=> { :wh_Repeating=>[
# {:wh=>{:Item_Number=>"111167", :Expiry_Date=>"2023-05-01"}},
# {:wh=>{:Item_Number=>"111166", :Expiry_Date=>"2023-05-05"}},
# {:wh=>{:Item_Number=>"111168", :Expiry_Date=>"2023-05-09"}}
# ]
# }
The original hash h is unchanged, which can be easily verified.

Adding a count/tally to an array of hashes [duplicate]

I have an array of hashes:
[{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3>}, {"Dry Goods"=>2}]
I need to use inject here I think but I've really been struggling.
I want a new hash that reflects the sum of the previous hash's duplicate keys:
[{"Vegetable"=>15}, {"Dry Goods"=>5}]
I'm in control of the code that outputs this hash so I can modify it if necessary. The results were mainly hashes because this could end up nested any number of levels deep and then it's easy to call flatten on the array but not flatten the keys/values of the hash too:
def recipe_pl(parent_percentage=nil)
ingredients.collect do |i|
recipe_total = i.recipe.recipeable.total_cost
recipe_percentage = i.ingredient_cost / recipe_total
if i.ingredientable.is_a?(Purchaseitem)
if parent_percentage.nil?
{i.ingredientable.plclass => recipe_percentage}
else
sub_percentage = recipe_percentage * parent_percentage
{i.ingredientable.plclass => sub_percentage}
end
else
i.ingredientable.recipe_pl(recipe_percentage)
end
end
end
ar = [{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3}, {"Dry Goods"=>2}]
p ar.inject{|memo, el| memo.merge( el ){|k, old_v, new_v| old_v + new_v}}
#=> {"Vegetable"=>15, "Dry Goods"=>5}
Hash.merge with a block runs the block when it finds a duplicate; inject without a initial memo treats the first element of the array as memo, which is fine here.
Simply use:
array = [{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3}, {"Dry Goods"=>2}]
array.inject{|a,b| a.merge(b){|_,x,y| x + y}}
ar = [{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3}, {"Dry Goods"=>2}]
While the Hash.merge technique works fine, I think it reads better with an inject:
ar.inject({}) { |memo, subhash| subhash.each { |prod, value| memo[prod] ||= 0 ; memo[prod] += value } ; memo }
=> {"Dry Goods"=>5, "Vegetable"=>15}
Better yet, if you use Hash.new with a default value of 0:
ar.inject(Hash.new(0)) { |memo, subhash| subhash.each { |prod, value| memo[prod] += value } ; memo }
=> {"Dry Goods"=>5, "Vegetable"=>15}
Or if inject makes your head hurt:
result = Hash.new(0)
ar.each { |subhash| subhash.each { |prod, value| result[prod] += value } }
result
=> {"Dry Goods"=>5, "Vegetable"=>15}
I'm not sure that a hash is what you want here, because I don't multiple entries in each hash. so I'll start by changing your data representation a little.
ProductCount=Struct.new(:name,:count)
data = [ProductCount.new("Vegetable",10),
ProductCount.new("Vegetable",5),
ProductCount.new("Dry Goods",3),
ProductCount.new("Dry Goods",2)]
If the hashes can have multiple key-value pairs, then what you probably want to do is
data = [{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3>}, {"Dry Goods"=>2}]
data = data.map{|h| h.map{|k,v| ProductCount.new(k,v)}}.flatten
Now use the facets gem as follows
require 'facets'
data.group_by(&:name).update_values{|x| x.map(&:count).sum}
The result is
{"Dry Goods"=>5, "Vegetable"=>15}
If have two hashes with multiple keys:
h1 = { "Vegetable" => 10, "Dry Goods" => 2 }
h2 = { "Dry Goods" => 3, "Vegetable" => 5 }
details = {}
(h1.keys | h2.keys).each do |key|
details[key] = h1[key].to_i + h2[key].to_i
end
details

Creating hash from array adding new keys

I have an array looking like this:
data =[[01, 777], [02, 888]]
Now I want to create a hash from it like below:
n_clip = [{"name"=>"01", "rep"=>"777"},{"name"=>"02", rep=>"888"}]
I tried to do this in that way:
n_clip = []
data.each do |a|
n_clip << Array[Hash[a.map {|| ["name", a.first]}], Hash[a.map {|| ["rep", a.last]}]]
end
but it doesn't work because I get:
n_clip = [[{"name"=>"01"},{"rep"="777"}], [{"name"=>"01"},{"rep"="777"}]]
and definitively it isn't what I expected.
data.map { |arr| { 'name' => arr[0], 'rep' => arr[1] } }
i would rather use symbols as hash keys
data.map { |arr| { name: arr[0], rep: arr[1] } }
If you wish to create an array of two hashes, each having the same two keys, the other answers are fine. The following handles the case where there are an arbitrary number of keys and data may contain an arbitrary number of elements.
def hashify(keys, arr_of_vals)
[keys].product(arr_of_vals).map { |ak,av| Hash[ak.zip(av)] }
end
keys = %w| name rep |
#=> ["name", "rep"]
arr_of_vals = [["01", "777"], ["02", "888"]]
hashify(keys, arr_of_vals)
#=> [{"name"=>"01", "rep"=>"777"}, {"name"=>"02", "rep"=>"888"}]
In your problem arr_of_vals must first be derived from [[1, 777], [02, 888]], but that is a secondary (rather mundane) problem that I will not address.
Another example:
keys = %w| name rep group |
#=> ["name", "rep", "group"]
arr_of_vals = [[1, 777, 51], [2, 888, 52], [1, 2, 53], [3, 4, 54]]
hashify(keys, arr_of_vals)
#=> [{"name"=>1, "rep"=>777, "group"=>51}, {"name"=>2, "rep"=>888, "group"=>52},
# {"name"=>1, "rep"=>2, "group"=>53}, {"name"=>3, "rep"=>4, "group"=>54}]
data.map { |name, rep| { 'name' => name.to_s, 'rep' => rep.to_s } }

How to array_wrap an hash of hashes

inv = {"C"=>{"CPS"=>{"CP"=>{"name"=>"a"}}}} is my object
I want
inv = {"C"=>{"CPS"=>{"CP"=>[{"name"=>"a"}]}}}
I tried
inv["C"]["CPS"].inject({}) do |result, (k, v)|
k = Array.wrap(v)
end
=> [{"name"=>"a"}]
but still inv={"C"=>{"CPS"=>{"CP"=>{"name"=>"a"}}}}
tries map also
Another option is to use tap
inv["C"]["CPS"].tap do |h|
h["CP"] = [h["CP"]] #or Array.wrap(h["CP"]) in rails
end
inv
#=> {"C"=>{"CPS"=>{"CP"=>[{"name"=>"a"}]}}}
tap will yield the current object so you can modify it in place.
Update
Inspired by #CarySwoveland's broader application you could use something like this as well.
class HashWrapper
attr_reader :original_hash
attr_accessor :target_keys
def initialize(h,*target_keys)
#original_hash = h
#target_keys = target_keys
end
def wrapped_hash
#wrapped_hash ||= {}
end
def wrap_me
original_hash.each do |k,v|
value = v.is_a?(Hash) ? HashWrapper.new(v,*target_keys).wrap_me : v
wrapped_hash[k] = wrap(k,value)
end
wrapped_hash
end
private
def wrap(k,v)
target_keys.include?(k) ? [v] : v
end
end
Then implementation is as follows
wrapper = HashWrapper.new(inv,"CP")
wrapper.wrap_me
#=> {"C"=>
{"CPS"=>
{"CP"=>
[
{"name"=>"a"}
]
}
}
}
new_wrapper = HashWrapper.new(inv,"CP","CPS")
new_wrapper.wrap_me
#=> {"C"=>
{"CPS"=>
[
{"CP"=>
[
{"name"=>"a"}
]
}
]
}
}
This assumes unique keys all the way through the hierarchy otherwise nested keys of the same name will be wrapped in the same fashion from the bottom up.
e.g.
inv = {"C"=>{"CPS"=>{"CP"=>{"name"=>"a"}},"CP" => "higher level"}}
HashWrapper.new(inv,"CP").wrap_me
#=> {"C"=>
{"CPS"=>
{"CP"=>
[
{"name"=>"a"}
]
},
"CP"=>
[
"higher level"
]
}
}
This should do it:
hash = {"C"=>{"CPS"=>{"CP"=>{"name"=>"a"}}}}
val = hash["C"]["CPS"]["CP"]
val_as_arr = [val] # can optionally call flatten here
hash["C"]["CPS"]["CP"] = val_as_arr
puts hash
# => {"C"=>{"CPS"=>{"CP"=> [{"name" => "a"}] }}}
basically
get the value
convert to array
set the value
There is no iteration required here i.e. map or reduce
I suggest you use recursion, in the form of a compact and easily readable method that has broader application than solutions that only work with your specific hash.
def wrap_it(h)
h.each { |k,v| h[k] = v.is_a?(Hash) ? wrap_it(v) : [v] }
h
end
h = { "C"=>{ "CPS"=>{ "CP"=>{ "name"=>"a" } } } }
wrap_it(h)
#=> {"C"=>{"CPS"=>{"CP"=>{"name"=>["a"]}}}}
h = { "C"=>{ "CPS"=>{ "CP"=>{ "CPPS"=> { "name"=>"cat" } } } } }
wrap_it(h)
#=> {"C"=>{"CPS"=>{"CP"=>{"CPPS"=>{"name"=>["cat"]}}}}}
h = { "C"=>{ "CPS"=>{ "CP"=>{ "CPPS"=> { "name"=>"cat" } },
"DP"=>{ "CPPPS"=>"dog" } } } }
wrap_it(h)
#=> {"C"=>{"CPS"=>{"CP"=>{"CPPS"=>{"name"=>["cat"]}}, "DP"=>{"CPPPS"=>["dog"]}}}}

How do I change the first elements of a 2 dimensional array?

I have an array
[[-20,23],[-80,65], ... []]
and I need
[["20",23],["80",65], ... []]
I have no idea how to deal with it.
Here is my code:
#posts = Post.featured_post.where(new_follow: true)
posts = (#posts.map { |post| "-#{ post[:ss_group_id] }_#{ post[:post_id] }" }).join(',') # here make parameters for request
posts = '"' + posts + '"'
posts_response = get_request(code_constructor('API.get', { posts: posts },[])) # here is response from API
noexist_posts = #posts.pluck(:vk_group_id, :post_id) - (posts_response[0].map { |h| h.values_at('owner_id', 'id') })
.map { |a| [a[0].abs.to_s, a[1]] } # here is what I want
I tried to find which posts don't exist.
A more inefficient, but cooler looking alternative to regular reassignment:
x.map { |first, *rest| [first.abs.to_s, *rest] }
You could do something like this
result = your_array.map{|a| [a[0].abs.to_s),a[1]]}
For some reason noone has posted the "regular reassignment" yet, so here it is (assuming you want to change the array in place):
x.each { |numbers| numbers[0] = numbers.first.abs.to_s }

Resources