iterating over sql result to produce new array of hashes - ruby

I have a result of items from SQL query. I want to use these results to create a new array of hashes called products. The path I'm on just doesn't seem right. While iterating over each row, I need to conditional process some of the keys in the rows such as pseudo code below.
items = retrieve_items_from_db
products = items.map do |row|
{
row.each do | k, v|
if k == "Color"
#go do something and add updated key, value pair to products
else puts "SKEY:#{k} VALUE: #{v}"
# add k,v to products
k => v
end
}
end
Any pointers or suggestions? I've tried using inject({}) instead of map.

What you're trying to do here would be cool, but we can't put conditional statements in hashes.
I.e. you can't do:
{
if 2 == 2
math: "correct"
else
math: "incorrect"
end
}
You would need to instead do:
if 2 == 2
{ math: "correct" }
else
{ math: "incorrect" }
end
If you need to combine multiple hashes into one, the Hash#merge method is useful.
for example:
{ a: "a", b: "DEFAULT" }.merge({ b: "b", c: "c" })
# => { a: "a", b: "b", c: "c" }
You can see that the second hash has overridden values in the first one.
You could use inject here (or reduce, which does the same thing but is less scary-sounding), but since you're converting an array to an array, it'd be easier to use map.
products = items.map do |row|
if row.keys.include?(:price)
row = row.merge(price: "#{row[:price]} dollars")
else
row = row.merge(price: "not available for sale")
end
next row
end

Made it a nested mapping and then flattened:
products = items.map do |row|
row.map do |k, v|
if k == "Color"
# go do something
# add transformed k, v to products
else
puts "SKEY:#{k} VALUE: #{v}"
k => v # add k, v to products
end
end
end.flatten

I think this way reads the easiest:
items = retrieve_items_from_db
products = items.map do |row|
product = {}
row.each do | k, v|
if k == "Color"
#go do something and add updated key, value pair to products
product[transform_key(k)] = transform_value(v)
else
puts "SKEY:#{k} VALUE: #{v}"
# add k,v to products
product[k] = v
end
end
product
end
You can also use Hash::[] to convert an array of array-pairs into a Hash, but to me this is clunkier:
items = retrieve_items_from_db
products = items.map do |row|
Hash[row.map do | k, v|
if k == "Color"
#go do something and add updated key, value pair to products
[transform_key(k), transform_value(v)]
else
puts "SKEY:#{k} VALUE: #{v}"
# add k,v to products
[k, v]
end
end]
end

Related

How to compare ruby hash with same key?

I have two hashes like this:
hash1 = Hash.new
hash1["part1"] = "test1"
hash1["part2"] = "test2"
hash1["part3"] = "test3"
hash2 = Hash.new
hash2["part1"] = "test1"
hash2["part2"] = "test2"
hash2["part3"] = "test4"
Expected output: part3
Basically, I want to iterate both of the hashes and print out "part3" because the value for "part3" is different in the hash. I can guarantee that the keys for both hashes will be the same, the values might be different. I want to print out the keys when their values are different?
I have tried iterating both hashes at once and comparing values but does not seem to give the right solution.
The cool thing about Ruby is that it is so high level that it is often basically English:
Print keys from the first hash if the values in the two hashes are different:
hash1.keys.each { |key| puts key if hash1[key] != hash2[key] }
Select the first hash keys that have different values in the two hashes and print each of them:
hash1.keys.select { |key| hash1[key] != hash2[key] }.each { |key| puts key }
Edit: I'll leave this should it be of interest, but #ndn's solution is certainly better.
p hash1.merge(hash2) { |_,v1,v2| v1==v2 }.reject { |_,v| v }.keys
# ["part3"]
hash1["part1"] = "test99"
p hash1.merge(hash2) { |_,v1,v2| v1==v2 }.reject { |_,v| v }.keys
# ["part1", "part3"]
This uses the form of Hash#merge that employs a block (here { |_,v1,v2| v1==v2 }) to determine the values of keys that are present in both hashes being merged. See the doc for an explanation of the three block variables, _, v1 and v2. The first block variable equals the common key. I've used the local variable _ for that, as is customary when the variable is not used in the block calculation.
The steps (for the original hash1):
g = hash1.merge(hash2) { |_,v1,v2| v1==v2 }
#=> {"part1"=>true, "part2"=>true, "part3"=>false}
h = g.reject { |_,v| v }
#=> {"part3"=>false}
h.keys
#=> ["part3"]
The obvious way is that of ndn, here a solution without blocks by converting to arrays, joining them and subtracting the elements that are the same, followed by converting back to hash and asking for the keys.
Next time it would be better to include what you tried so far.
((hash1.to_a + hash2.to_a) - (hash1.to_a & hash2.to_a)).to_h.keys
# ["part3"]

Recursively delete hashes with empty values

I'd like to delete empty hashes at different nested levels. And once that empty hash is deleted, I'd like to delete it's container hash as well. How would I do this?
Here is the hash I want to work on:
{
"query"=>{"filtered"=>{
"query"=>{"bool"=>{}},
"filter"=>{"query"=>{"query_string"=>{
"fields"=>[["standard_analyzed_name", "standard_analyzed_message"]],
"query"=>"Arnold AND Schwarz"
}}}
}},
"sort"=>[{"total_interactions"=>{"order"=>"desc"}}]
}
Below is the code that I have that does not remove the empty {"query"=>{"bool"=>{}} part:
def compactify_hash(main_hash)
main_hash.each do |key, value|
if(value.class == Hash && !value.empty?)
compactify_hash(value)
elsif(value.class == Hash && value.empty?)
main_hash.delete(key)
end
end
return main_hash
end
You have a few problems here:
You're modifying the hash in-place when you might not mean to, you'd probably want to name your method compactify_hash! if you really want to modify it in-place.
You have Arrays inside your Hash but you don't scan them for empty Hashes.
Most importantly, you never check your recursively compactified hashes to see if compactifying them has also emptied them. in here:
if(value.class == Hash && !value.empty?)
compactify_hash(value)
elsif(value.class == Hash && value.empty?)
main_hash.delete(key)
end
you need to check value.empty? after you compactify_hash(value).
You could do something like this instead:
def compactify_hash(main_hash)
main_hash.each_with_object({}) do |(k, v), h|
if(v.is_a?(Hash))
ch = compactify_hash(v)
h[k] = ch if(!ch.empty?)
elsif(v.is_a?(Array))
h[k] = v.map do |e|
e.is_a?(Hash) ? compactify_hash(e) : e
end
else
h[k] = v
end
end
end
I have assumed that for any hash h, you want to create another hash g which is the same as h except no nested hash in g will have a key-value pair [k,v] for which v.respond_to(:empty?) and v.empty? both return true. For example, if the following nested hash is present in h:
{ a: { b: '', c: {}, d: [] }, e: 3 }
then the corresponding nested hash in g will be:
{ e: 3 }
In effect, we "remove" the key-value pairs :b=> '', :c=> {} and :d=> [] because '', {} and [] all respond to :empty? and all are empty. That reduces the nested hash to:
{ a: {}, e: 3 }
Since the value of a is now empty, we remove that key-value pair, leaving the nested hash in g equal to:
{ e: 3 }
I believe this can be achieved as follows:
def remove(h)
loop do
h_new = remove_empties(h)
break h if h==h_new
h = h_new
end
end
def remove_empties(h)
h.each_with_object({}) do |(k,v),g|
case v
when Hash
g[k] = remove_empties(h[k]) unless v.empty?
else
g[k] = v unless v.respond_to?(:empty?) && v.empty?
end
end
end
For your hash, which I refer to as h:
remove(h)
#=> {:query=>
# {:filtered=>
# {:filter=>
# {:query=>
# {:query_string=>
# {:fields=>
[["standard_analyzed_name", "standard_analyzed_message"]],
# :query=>"Arnold AND Schwarz"
# }
# }
# }
# }
# },
# :sort=>[
# {:total_interactions=>
# {:order=>"desc"}
# }
# ]
# }
Note that the recursion is performed repeatedly until no further modifications of the hash are performed.

Create a hash map from an array that returns the largest value

I have an array of questions where each question has a category_id and a value.
I'd like to map these so that when the key (category_id) already exists in the hash, then the values are added together.
And, finally, I'd like to find the largest value in the hash:
h = Hash.new {|k, v| k[v] = 0}
#test_session.answered_questions.each do |q|
if h.key?(q.category_id)
#add q.value to the value stored in the hash
else
h = { q.category_id => q.value } #insert the "q.category_id" as key and with value "q.value"
end
end
key_with_max_value = h.max_by { |k, v| v }[0] #find the highest value
#result.category = key_with_max_value
#result.score = h[key_with_max_value].value
There is probably a lot better way to achieve this but I'm quite new to Ruby.
h = Hash.new(0)
#test_session.answered_questions.each {|q| h[q.category_id] += q.value}
#result.category, #result.score = h.max_by { |k, v| v }
Each value in the hash will be initialized to zero with Hash.new(0) and since h.max_by returns the key value pair, you can directly assign them to your #result variable.
You can simply do:
#test_session.answered_questions.each { |q| h[q.category_id] += q.value }
When a key is not present, it is assumed to have a value of 0 because of the way you initialized the hash, so it gets inserted with 0 + q.value.
See the documentation, or try it out.
Also, you can assign two variables separated by commas to h.max_by { |k, v| v }. This is called Multiple Assignment, and it works for arrays too:
a,b,c = [1,2,3]

Ruby: sorting 2d array and output similar field value to files

I have array which I read from excel (using ParseExcel) using the following code:
workbook = Spreadsheet::ParseExcel.parse("test.xls")
rows = workbook.worksheet(1).map() { |r| r }.compact
grid = rows.map() { |r| r.map() { |c| c.to_s('latin1') unless c.nil?}.compact rescue nil }
grid.sort_by { |k| k[2]}
test.xls has lots of rows and 6 columns. The code above sort by column 3.
I would like to output rows in array "grid" to many text file like this:
- After sorting, I want to print out all the rows where column 3 have the same value into one file and so on for a different file for other same value in column3.
Hope I explain this right. Thanks for any help/tips.
ps.
I search through most posting on this site but could not find any solution.
instead of using your above code, I made a test 100-row array, each row containing a 6-element array.
You pass in the array, and the column number you want matched, and this method prints into separate files rows that have the same nth element.
Since I used integers, I used the nth element of each row as the filename. You could use a counter, or the md5 of the element, or something like that, if your nth element does not make a good filename.
a = []
100.times do
b = []
6.times do
b.push rand(10)
end
a.push(b)
end
def print_files(a, column)
h = Hash.new
a.each do |element|
h[element[2]] ? (h[element[column]] = h[element[column]].push(element)) : (h[element[column]] = [element])
end
h.each do |k, v|
File.open("output/" + k.to_s, 'w') do |f|
v.each do |line|
f.puts line.join(", ")
end
end
end
end
print_files(a, 2)
Here is the same code using blocks instead of do .. end:
a = Array.new
100.times{b = Array.new;6.times{b.push rand(10)};a.push(b)}
def print_files(a, column)
h = Hash.new
a.each{|element| h[element[2]] ? (h[element[column]] = h[element[column]].push(element)) : (h[element[column]] = [element])}
h.map{|k, v| File.open("output/" + k.to_s, 'w'){|f| v.map{|line| f.puts line.join(", ")}}}
end
print_files(a, 2)

difficulty modifying two dimensional ruby array

Excuse the newbie question. I'm trying to create a two dimensional array in ruby, and initialise all its values to 1. My code is creating the two dimensional array just fine, but fails to modify any of its values.
Can anyone explain what I'm doing wrong?
def mda(width,height)
#make a two dimensional array
a = Array.new(width)
a.map! { Array.new(height) }
#init all its values to 1
a.each do |row|
row.each do |column|
column = 1
end
end
return a
end
It the line row.each do |column| the variable column is the copy of the value in row. You can't edit its value in such way. You must do:
def mda(width,height)
a = Array.new(width)
a.map! { Array.new(height) }
a.each do |row|
row.map!{1}
end
return a
end
Or better:
def mda(width,height)
a = Array.new(width)
a.map! { Array.new(height) }
a.map do |row|
row.map!{1}
end
end
Or better:
def mda(width,height)
a = Array.new(width){ Array.new(height) }
a.map do |row|
row.map!{1}
end
end
Or better:
def mda(width,height)
Array.new(width) { Array.new(height){1} }
end
each passes into the block parameter the value of each element, not the element itself, so column = 1 doesn't actually modify the array.
You can do this in one step, though - see the API docs for details on the various forms of Array#new. Try a = Array.new(width) {|i| Array.new(height) {|j| 1 } }
you can create it like this?
a=Array.new(width) { Array.new(height,1) }
column in your nested each loop is a copy of the value at that place in the array, not a pointer/reference to it, so when you change its value you're only changing the value of the copy (which ceases to exist outside the block).
If you just want a two-dimensional array populated with 1s something as simple as this will work:
def mda(width,height)
[ [1] * width ] * height
end
Pretty simple.
By the way, if you want to know how to modify the elements of a two-dimensional array as you're iterating over it, here's one way (starting from line 6 in your code):
#init all its values to 1
a.length.times do |i|
a[i].length.times do |j|
a[i][j] = 1
end
end

Resources