Rails: Grouping Model records, then manipulating values - ruby

I am grouping Model instances, by attribute, then manipulating the hash values.
Product.create(id: 1, name: "alpha", value: "apple")
Product.create(id: 2, name: "beta", value: "bongo")
...
We want the form: [["alpha"],[["apple"],[1]]],[[beta],[["bongo"],[2]]]...]
array = []
array1 = []
Product.all.group_by(&:name).each do |a|
a[1].each do |b|
array1 << [b.value,b.id]
end
array << [a[0],array1]
array1 = []
end
Where a and b are iterator variables, array1 contains the ith a[1] values, and array contains the desired output structure.
This works, but is ugly. Can you accomplish this more cleanly?

array = Product.all.group_by(&:name).map { |name, products|
[name, products.map { |product| [product.value, product.id] }]
}
I believe this is what you want, but not 100% sure. Please try to use descriptive names, it takes a lot of effort to figure what array1, b and similar non-identifying identifiers are. It is also nice if you post an example of the output structure.

Related

Merging Three hashes and getting this resultant hash

I have read the xls and have formed these three hashes
hash1=[{'name'=>'Firstname',
'Locator'=>'id=xxx',
'Action'=>'TypeAndWait'},
{'name'=>'Password',
'Locator'=>'id=yyy',
'Action'=>'TypeAndTab'}]
Second Hash
hash2=[{'Test Name'=>'Example',
'TestNumber'=>'Test1'},
{'Test Name'=>'Example',
'TestNumber'=>'Test2'}]
My Thrid Hash
hash3=[{'name'=>'Firstname',
'Test1'=>'four',
'Test2'=>'Five',
'Test3'=>'Six'},
{'name'=>'Password',
'Test1'=>'Vicky',
'Test2'=>'Sujin',
'Test3'=>'Sivaram'}]
Now my resultant hash is
result={"Example"=>
{"Test1"=>
{'Firstname'=>
["id=xxx","four", "TypeAndWait"],
'Password'=>
["id=yyy","Vicky", "TypeAndTab"]},
"Test2"=>
{'Firstname'=>
["id=xxx","Five", "TypeAndWait"],
'Password'=>
["id=yyy","Sujin", "TypeAndTab"]}}}
I have gotten this result, but I had to write 60 lines of code in my program, but I don't think I have to write such a long program when I use Ruby, I strongly believe some easy way to achieve this. Can some one help me?
The second hash determines the which testcase has to be read, for an example, test3 is not present in the second testcase so resultant hash doesn't have test3.
We are given three arrays, which I've renamed arr1, arr2 and arr3. (hash1, hash2 and hash3 are not especially good names for arrays. :-))
arr1 = [{'name'=>'Firstname', 'Locator'=>'id=xxx', 'Action'=>'TypeAndWait'},
{'name'=>'Password', 'Locator'=>'id=yyy', 'Action'=>'TypeAndTab'}]
arr2 = [{'Test Name'=>'Example', 'TestNumber'=>'Test1'},
{'Test Name'=>'Example', 'TestNumber'=>'Test2'}]
arr3=[{'name'=>'Firstname', 'Test1'=>'four', 'Test2'=>'Five', 'Test3'=>'Six'},
{'name'=>'Password', 'Test1'=>'Vicky', 'Test2'=>'Sujin', 'Test3'=>'Sivaram'}]
The drivers are the values "Test1" and "Test2" in the hashes that are elements of arr2. Nothing else in that array is needed, so let's extract those values (of which there could be any number, but here there are just two).
a2 = arr2.map { |h| h['TestNumber'] }
#=> ["Test1", "Test2"]
Next we need to rearrange the information in arr3 by creating a hash whose keys are the elements of a2.
h3 = a2.each_with_object({}) { |test,h|
h[test] = arr3.each_with_object({}) { |f,g| g[f['name']] = f[test] } }
#=> {"Test1"=>{"Firstname"=>"four", "Password"=>"Vicky"},
# "Test2"=>{"Firstname"=>"Five", "Password"=>"Sujin"}}
Next we need to rearrange the content of arr1 by creating a hash whose keys match the keys of values of h3.
h1 = arr1.each_with_object({}) { |g,h| h[g['name']] = g.reject { |k,_| k == 'name' } }
#=> {"Firstname"=>{"Locator"=>"id=xxx", "Action"=>"TypeAndWait"},
# "Password"=>{"Locator"=>"id=yyy", "Action"=>"TypeAndTab"}}
It is now a simple matter of extracting information from these three objects.
{ 'Example'=>
a2.each_with_object({}) do |test,h|
h[test] = h3[test].each_with_object({}) do |(k,v),g|
f = h1[k]
g[k] = [f['Locator'], v, f['Action']]
end
end
}
#=> {"Example"=>
# {"Test1"=>{"Firstname"=>["id=xxx", "four", "TypeAndWait"],
# "Password"=>["id=yyy", "Vicky", "TypeAndTab"]},
# "Test2"=>{"Firstname"=>["id=xxx", "Five", "TypeAndWait"],
# "Password"=>["id=yyy", "Sujin", "TypeAndTab"]}}}
What do you call hash{1-2-3} are arrays in the first place. Also, I am pretty sure you have mistyped hash1#Locator and/or hash3#name. The code below works for this exact data, but it should not be hard to update it to reflect any changes.
hash2.
map(&:values).
group_by(&:shift).
map do |k, v|
[k, v.flatten.map do |k, v|
[k, hash3.map do |h3|
# lookup a hash from hash1
h1 = hash1.find do |h1|
h3['name'].start_with?(h1['Locator'])
end
# can it be nil btw?
[
h1['name'],
[
h3['name'][/.*(?=-id)/],
h3[k],
h1['Action']
]
]
end.to_h]
end.to_h]
end.to_h

How to compare two arrays in Ruby

I'm trying to compare two arrays:
compareFrom = []
compareTo = ["John Doe", "Eric Schulz", "Tom Jerry"]
I tried the following:
arrayField1 = []
for r in empData
compareFrom << r.employeeName
end
if compareFrom.include?(compareTo)
#yes got it
end
I cannot figure out why I get false even though compareFrom has the same values as compareTo.
Is there anything that I need to change in the code?
Array#include? only tests membership of one element in an array. Your compare_from.include?(compare_to) tests whether compare_to is an element of compare_from, and would e.g. return true in case compare_from is [1, 2, 3, ["John Doe", "Eric Schulz", "Tom Jerry"], 5].
If you want to see if all elements of compare_to are in compare_from, compare_to.all? { |element| compare_from.include?(element) } is idiomatic and legible but slow; tadman's (compare_from & compare_to).size == compare_to.size is much more performant. A third option, when speaking of subsets, and the one I'd likely prefer, is to use sets:
require 'set'
Set[compare_to].subset?(Set[compare_from])
This code boils down to:
compare_from = emp_data.map(&:employee_name)
Where that's calling the employee_name method on each of the items in the emp_data array and returning a new array with the result. You can easily test overlap on two arrays using & to find the intersection:
compare_to = ["John Doe", "Eric Schulz", "Tom Jerry"]
common = compare_from & compare_to
If that array common has any entries then you have matches.

Merge duplicate values in json using ruby

I have the following item.json file
{
"items": [
{
"brand": "LEGO",
"stock": 55,
"full-price": "22.99",
},
{
"brand": "Nano Blocks",
"stock": 12,
"full-price": "49.99",
},
{
"brand": "LEGO",
"stock": 5,
"full-price": "199.99",
}
]
}
There are two items named LEGO and I want to get output for the total number of stock for the individual brand.
In ruby file item.rb i have code like:
require 'json'
path = File.join(File.dirname(__FILE__), '../data/products.json')
file = File.read(path)
products_hash = JSON.parse(file)
products_hash["items"].each do |brand|
puts "Stock no: #{brand["stock"]}"
end
I got output for stock no individually for each brand wherein I need the stock to be summed for two brand name "LEGO" displayed as one.
Anyone has solution for this?
json = File.open(path,'r:utf-8',&:read) # in case the JSON uses UTF-8
items = JSON.parse(json)['items']
stock_by_brand = items
.group_by{ |h| h['brand'] }
.map do |brand,array|
[ brand,
array
.map{ |item| item['stock'] }
.inject(:+) ]
end
.to_h
#=> {"LEGO"=>60, "Nano Blocks"=>12}
It works like this:
Enumerable#group_by takes the array of items and creates a hash mapping the brand name to an array of all item hashes with that brand
Enumerable#map turns each brand/array pair in that hash into an array of the brand (unchanged) followed by:
Enumerable#map on the array of items picks out just the "stock" counts, and then
Enumerable#inject sums them all together
Array#to_h then turns that array of two-value arrays into a hash, mapping the brand to the sum of stock values.
If you want simpler code that's less functional and possibly easier to understand:
stock_by_brand = {} # an empty hash
items.each do |item|
stock_by_brand[ item['brand'] ] ||= 0 # initialize to zero if unset
stock_by_brand[ item['brand'] ] += item['stock']
end
p stock_by_brand #=> {"LEGO"=>60, "Nano Blocks"=>12}
To see what your JSON string looks like, let's create it from your hash, which I've denoted h:
require 'json'
j = JSON.generate(h)
#=> "{\"items\":[{\"brand\":\"LEGO\",\"stock\":55,\"full-price\":\"22.99\"},{\"brand\":\"Nano Blocks\",\"stock\":12,\"full-price\":\"49.99\"},{\"brand\":\"LEGO\",\"stock\":5,\"full-price\":\"199.99\"}]}"
After reading that from a file, into the variable j, we can now parse it to obtain the value of "items":
arr = JSON.parse(j)["items"]
#=> [{"brand"=>"LEGO", "stock"=>55, "full-price"=>"22.99"},
# {"brand"=>"Nano Blocks", "stock"=>12, "full-price"=>"49.99"},
# {"brand"=>"LEGO", "stock"=>5, "full-price"=>"199.99"}]
One way to obtain the desired tallies is to use a counting hash:
arr.each_with_object(Hash.new(0)) {|g,h| h.update(g["brand"]=>h[g["brand"]]+g["stock"])}
#=> {"LEGO"=>60, "Nano Blocks"=>12}
Hash.new(0) creates an empty hash (represented by the block variable h) with with a default value of zero1. That means that h[k] returns zero if the hash does not have a key k.
For the first element of arr (represented by the block variable g) we have:
g["brand"] #=> "LEGO"
g["stock"] #=> 55
Within the block, therefore, the calculation is:
g["brand"] => h[g["brand"]]+g["stock"]
#=> "LEGO" => h["LEGO"] + 55
Initially h has no keys, so h["LEGO"] returns the default value of zero, resulting in { "LEGO"=>55 } being merged into the hash h. As h now has a key "LEGO", h["LEGO"], will not return the default value in subsequent calculations.
Another approach is to use the form of Hash#update (aka merge!) that employs a block to determine the values of keys that are present in both hashes being merged:
arr.each_with_object({}) {|g,h| h.update(g["brand"]=>g["stock"]) {|_,o,n| o+n}}
#=> {"LEGO"=>60, "Nano Blocks"=>12}
1 k=>v is shorthand for { k=>v } when it appears as a method's argument.

How to create an array out of certain values in hashes, which are contained in an array

I have an array of hashes:
a = [{name: "ben", sex: "m"},{name: "sarah", sex: "f"}]
What is the easiest way to create an array out of this with just the names? So I end up with:
b = ["ben", "sarah"]
I know you can do the following, but just wondering if there's a shortcut
b = []
a.each do |x|
b << x[:name]
end
Thanks for reading.
b = a.map { |hash| hash[:name] }
This is pretty basic Ruby, take a look at the Enumerable module and study all the methods carefully. [edit] Some random links about the topic: 1, 2, 3.

Ruby Array Intersection, Hash.has_value? for intersection with array[i+1] based on hash values

I am trying to populate an array with an intersection of an array and hash based on hash values:
array2 = ["hi","world","One"]
VALS = {:one => "One", :two => "Two"}
array2 = VALS.values & array2
print array2
Works fine for intersection of hash values and array2[i], but if I want to instead populate with array2[i+1] element from array2, I am lost.
Also tried:
array2.select { |i| VALS.has_value? i ? array2[i+1] : nil }
But no luck.
Any ideas?
array = []
array2.each_with_index{|v,i| array << (VALS.has_value? i ? array2[i+1] : nil)}
It sounds to me like you're trying to treat your array as a series of either internal keys and values, or external keys into the inverse of vals. So the correct result from your example would be:
{"hi" => "World", "One" => :one}
Is that right? If so, here:
invertedvals = vals.invert
Hash[*array2.map {|x| [x,invertedvals[x]]}.flatten.compact]
If not, please clarify what result you're trying to produce...
Here is what I ended up with as an answer to my own question:
array1.each do |i|
puts "#"*5 + i + "#"*5 if !array2.include? i
if array2.include? i
result << i+":" << array2[array2.index(i)+1]
end
end
Is there a speedier algorithm for this? Would like to use:
array1 & array2
Also used inject at one point, benchmark showed very little difference between any of them.

Resources