I have an array. I need to perform a different calculation on each element. I thought I could do something like the following:
def calc(a, b, c)
arr = [a, b, c]
arr.map { |i| (i[0] * 600), (i[1] * 800), (i[2] * 1000) }
end
calc(5, 8, 15)
but this does not work. How can I perform different calculations on each element of a single array?
Here are some other implementations that might be helpful. By putting the multipliers into an array, we can use zip to connect the element in the input array with the appropriate multiplier value. In addition, that makes it simple to abstract the logic further by removing the multiplier values from the logic that does the multiplication (in multiply_arrays and transform_arrays).
#!/usr/bin/env ruby
VALUES = [1, 1, 1]
MULTIPLIERS = [600, 800, 1000]
def transform(*values)
values.zip(MULTIPLIERS).map { |x, y| x * y }
end
def multiply_arrays(array1, array2)
array1.zip(array2).map { |n1, n2| n1 * n2 }
end
def transform_arrays(array1, array2, method_name)
array1.zip(array2).map { |n1, n2| n1.public_send(method_name, n2) }
end
p transform(*VALUES) # [600, 800, 1000]
p multiply_arrays(VALUES, MULTIPLIERS) # [600, 800, 1000]
p transform_arrays(VALUES, MULTIPLIERS, :*) # [600, 800, 1000]
If the calculations need to be substantially different (different operators, values, more complex logic), than I'd consider using an array of lambdas:
def transform_with_lambdas(values, transforms)
values.zip(transforms).map do |value, transform|
transform.(value)
end
end
TRANSFORMS = [
->(x) { x * 600 },
->(x) { x + 100 },
->(x) { x / 3.0 },
]
p transform_with_lambdas(VALUES, TRANSFORMS) # [600, 101, 0.3333333333333333]
Here is a solution which will help you to apply different operations on two different operands:
def calc(first_operand_arr, operator_arr, second_operand_arr)
result_arr = []
operator_arr.each_with_index do |o, i|
result_arr << (first_operand_arr[i]).method(o).(second_operand_arr[i])
end
result_arr
end
calc([5, 8, 15], ['+', '-', '*'], [5, 3, 2])
def calc *arr
ops = [600, 800, 1000]
arr.map { |x| x * ops.shift }
end
calc(5, 8, 15)
#=> [3000, 6400, 15000]
You could generalize this as follows:
def calc(*arr)
arr.map { |op1, op2, m| op1.send(m, op2) }
end
calc [5, 6, :*], [2, 3, :+], [10, 8, :-]
#=> [30, 5, 2]
Here is an option using a second array of lambda's that can be arbitrary functions of each entry in your main array.
operands = [1.0,2.0,3.0]
operations = [
->(e) { e * 10} ,
->(e) { e + 10 },
->(e) { e * e }
]
results = operands.each_with_index.map { |operand, index| operations[index].call(operand) }
puts results
Edit
I just noticed this is a really a variation on Keith Bennett's answer above, I will leave it here, since it is different in how the lambda is retrieved from the array.
Related
I am trying on of the online tutorials to have a dictionary of nine numbers and create another dictionary with statistics, below is the code with the input data, and the result as well
import numpy as np
a = [0, 1, 2, 3, 4, 5, 6, 7, 8]
arr = np.array(a).reshape(3, 3).astype(int)
result = {
"mean": [],
"variance": [],
"standard deviation": [],
"max": [],
"min": [],
"sum": []
}
# Creating a function1
def calculate1(a):
calculate1 = arr.mean(axis = a)
return(calculate1)
result["mean"].append(calculate1(0))
result["mean"].append(calculate1(1))
result["mean"].append(calculate1(None))
# Creating a function2
def calculate2(a):
calculate2 = arr.var(axis = a)
return(calculate2)
result["variance"].append(calculate2(0))
result["variance"].append(calculate2(1))
result["variance"].append(calculate2(None))
# Creating a function3
def calculate3(a):
calculate3 = arr.std(axis = a)
return(calculate3)
result["standard deviation"].append(calculate3(0))
result["standard deviation"].append(calculate3(1))
result["standard deviation"].append(calculate3(None))
# Creating a function4
def calculate4(a):
calculate4 = arr.max(axis = a)
return(calculate4)
result["max"].append(calculate4(0))
result["max"].append(calculate4(1))
result["max"].append(calculate4(None))
# Creating a function5
def calculate5(a):
calculate5 = arr.min(axis = a)
return(calculate5)
result["min"].append(calculate5(0))
result["min"].append(calculate5(1))
result["min"].append(calculate5(None))
# Creating a function6
def calculate6(a):
calculate6 = arr.sum(axis = a)
return(calculate6)
result["sum"].append(calculate6(0))
result["sum"].append(calculate6(1))
result["sum"].append(calculate6(None))
for k, v in result.items():
print(k, v)
And here is the result
mean [array([3., 4., 5.]), array([1., 4., 7.]), 4.0]
variance [array([6., 6., 6.]), array([0.66666667, 0.66666667, 0.66666667]), 6.666666666666667]
standard deviation [array([2.44948974, 2.44948974, 2.44948974]), array([0.81649658, 0.81649658, 0.81649658]), 2.581988897471611]
max [array([6, 7, 8]), array([2, 5, 8]), 8]
min [array([0, 1, 2]), array([0, 3, 6]), 0]
sum [array([ 9, 12, 15]), array([ 3, 12, 21]), 36]
I have two questions here:
1- Is there a way that I can combine or minimize the number of functions to one or something like that. Please note that I (have to) use the function.
2- The output is correct (in values), however I am not sure why the word (array) is printing as well, and when I check the type of the values inside the dictionary, it shows that they are <class 'list'>, so where this array word is coming from?
I tried tolist value and plenty of online suggestions but nothing worked
Any help or suggestion is highly appreciated
You can store your functions inside a dict and then iterate over it:
from pprint import pprint
import numpy as np
def main():
arr = np.random.rand(3, 3)
functions = {
"mean": lambda axis: arr.mean(axis=axis),
"var": lambda axis: arr.var(axis=axis),
"std": lambda axis: arr.std(axis=axis),
"max": lambda axis: arr.max(axis=axis),
"min": lambda axis: arr.min(axis=axis),
"sum": lambda axis: arr.sum(axis=axis),
}
axes = (0, 1, None)
result = {}
for funcname, func in functions.items():
result[funcname] = [func(axis).tolist() for axis in axes]
# Alternatively:
result = {
funcname: [func(axis).tolist() for axis in axes]
for funcname, func in functions.items()
}
pprint(result)
if __name__ == "__main__":
main()
Prints:
{'max': [[0.33149413492721314, 0.9252576833729358, 0.9616249059176883],
[0.37580580905770067, 0.9616249059176883, 0.9252576833729358],
0.9616249059176883],
'mean': [[0.23391570323037428, 0.4063894010374775, 0.6764668740080081],
[0.20197437573445387, 0.4652236940918113, 0.6495739084495947],
0.43892399275862],
'min': [[0.0958037701384552, 0.13431354800720574, 0.37580580905770067],
[0.0958037701384552, 0.15959697173229104, 0.33149413492721314],
0.0958037701384552],
'std': [[0.10039824223253171, 0.3670404461719236, 0.23941075106262735],
[0.1239187264736742, 0.35412651334119355, 0.24424967197333333],
0.3170854368356986],
'sum': [[0.7017471096911229, 1.2191682031124325, 2.029400622024024],
[0.6059231272033616, 1.395671082275434, 1.948721725348784],
3.95031593482758],
'var': [[0.010079807043382115, 0.13471868912608476, 0.057317507724371324],
[0.015355850770857285, 0.12540558745119054, 0.05965790225908093],
0.10054317425328584]}
As for why there is "array" printed, it is because, e.g., np.mean(arr, axis=1) returns a numpy array.
# multi_map
# Write a method multi_map that accepts an array, an optional number (n), and a block as arguments.
# The method should return a new array where each element of the original array is repeatedly run through the block n times.
# If the number argument is not passed in, then the the elements should be run through the block once.
# Examples
def multi_map(arr, &blc, *n)
narr = []
return narr
end
p multi_map(['pretty', 'cool', 'huh?']) { |s| s + '!'} # ["pretty!", "cool!", "huh?!"]
p multi_map(['pretty', 'cool', 'huh?'], 1) { |s| s + '!'} # ["pretty!", "cool!", "huh?!"]
p multi_map(['pretty', 'cool', 'huh?'], 3) { |s| s + '!'} # ["pretty!!!", "cool!!!", "huh?!!!"]
p multi_map([4, 3, 2, 7], 1) { |num| num * 10 } # [40, 30, 20, 70]
p multi_map([4, 3, 2, 7], 2) { |num| num * 10 } # [400, 300, 200, 700]
p multi_map([4, 3, 2, 7], 4) { |num| num * 10 } # [40000, 30000, 20000, 70000]
I need to make a method that takes an array, optional and a block. I get an error with the arguments being called when I run this skeleton program in rspec. Why is this the case?
Consider the following:
irb(main):010:1* def mm(arr, *n, &blc)
irb(main):011:1* p arr; n.each(&blc)
irb(main):012:0> end
=> :mm
irb(main):013:0> mm("hello", "world") { |x| puts x }
"hello"
world
=> ["world"]
irb(main):014:0>
You've got the *n and &blc in the wrong order in the parameter list.
There is an array with some numbers. All numbers are equal except for one. I'm trying to get this type of thing:
find_uniq([ 1, 1, 1, 2, 1, 1 ]) == 2
find_uniq([ 0, 0, 0.55, 0, 0 ]) == 0.55
I tried this:
def find_uniq(arr)
arr.uniq.each{|e| arr.count(e)}
end
It gives me the two different values in the array, but I'm not sure how to pick the one that's unique. Can I use some sort of count or not? Thanks!
This worked:
def find_uniq(arr)
return nil if arr.size < 3
if arr[0] != arr[1]
return arr[1] == arr[2] ? arr[0] : arr[1]
end
arr.each_cons(2) { |x, y| return y if y != x }
end
Thanks pjs and Cary Swoveland.
I would do this:
[ 1, 1, 1, 2, 1, 1 ]
.tally # { 1=>5, 2=>1 }
.find { |_, v| v == 1 } # [2, 1]
.first # 2
Or as 3limin4t0r suggested:
[ 1, 1, 1, 2, 1, 1 ]
.tally # { 1=>5, 2=>1 }
.invert[1] # { 5=>1, 1=>2 } => 2
The following doesn't use tallies and will short circuit the search when a unique item is found. First, it returns nil if the array has fewer than 3 elements, since there's no way to answer the question in that case. If that check is passed, it works by comparing adjacent values. It performs an up-front check that the first two elements are equal—if not, it checks against the third element to see which one is different. Otherwise, it iterates through the array and returns the first value it finds which is unequal. It returns nil if there is not a distinguished element in the array.
def find_uniq(arr)
return nil if arr.size < 3
if arr[0] == arr[1]
arr.each.with_index do |x, i|
i += 1
return arr[i] if arr[i] != x
end
elsif arr[1] == arr[2]
arr[0]
else
arr[1]
end
end
This also works with non-numeric arrays such as find_uniq(%w(c c c d c c c c)).
Thanks to Cary Swoveland for reminding me about each_cons. That can tighten up the solution considerably:
def find_uniq(arr)
return nil if arr.size < 3
if arr[0] != arr[1]
return arr[1] == arr[2] ? arr[0] : arr[1]
end
arr.each_cons(2) { |x, y| return y if y != x }
end
For all but tiny arrays this method effectively has the speed of Enumerable#find.
def find_uniq(arr)
multi = arr[0,3].partition { |e| e == arr.first }
.sort_by { |e| -e.size }.first.first
arr.find { |e| e != multi }
end
find_uniq [1, 1, 1, 2, 1, 1] #=> 2
find_uniq [0, 0, 0.55, 0, 0] #=> 0.55
find_uniq [:pig, :pig, :cow, :pig] #=> :cow
The wording of the question implies the array contains at least three elements. It certainly cannot be empty or have two elements. (If it could contain one element add the guard clause return arr.first if arr.size == 1.)
I examine the first three elements to determine the object that has duplicates, which I assign to the variable multi. I then am able to use find. find is quite fast, in part because it short-circuits (stops enumerating the array when it achieves a match).
If
arr = [1, 1, 1, 2, 1, 1]
then
a = arr[0,3].partition { |e| e == arr.first }.sort_by { |e| -e.size }
#=> [[1, 1, 1], []]
multi = a.first.first
#=> 1
If any of these:
arr = [2, 1, 1, 1, 1, 1]
arr = [1, 2, 1, 1, 1, 1]
arr = [1, 1, 2, 1, 1, 1]
apply then
a = arr[0,3].partition { |e| e == arr.first }.sort_by { |e| -e.size }
#=> [[1, 1], [2]]
multi = a.first.first
#=> 1
Let's compare the computational performace of the solutions that have been offered.
def spickermann1(arr)
arr.tally.find { |_, v| v == 1 }.first
end
def spickermann2(arr)
arr.tally.invert[1]
end
def spickermann3(arr)
arr.tally.min_by(&:last).first
end
def pjs(arr)
if arr[0] == arr[1]
arr.each.with_index do |x, i|
i += 1
return arr[i] if arr[i] != x
end
elsif arr[1] == arr[2]
arr[0]
else
arr[1]
end
end
I did not include #3limin4t0r's solution because of the author's admission that it is relatively inefficient. I did include, however, include two variants of #spikermann's answer, one ("spickermann2") having been proposed by #3limin4t0r in a comment.
require 'benchmark'
def test(n)
puts "\nArray size = #{n}"
arr = Array.new(n-1,0) << 1
Benchmark.bm do |x|
x.report("Cary") { find_uniq(arr) }
x.report("spickermann1") { spickermann1(arr) }
x.report("spickermann2") { spickermann2(arr) }
x.report("spickermann3") { spickermann3(arr) }
x.report("PJS") { pjs(arr) }
end
end
test 100
Array size = 100
user system total real
Cary 0.000032 0.000009 0.000041 ( 0.000029)
spickermann1 0.000022 0.000015 0.000037 ( 0.000019)
spickermann2 0.000017 0.000002 0.000019 ( 0.000016)
spickermann3 0.000019 0.000002 0.000021 ( 0.000018)
PJS 0.000042 0.000025 0.000067 ( 0.000034)
test 10_000
Array size = 10_000
user system total real
Cary 0.001101 0.000091 0.001192 ( 0.001119)
spickermann1 0.000699 0.000096 0.000795 ( 0.000716)
spickermann2 0.000794 0.000071 0.000865 ( 0.000896)
spickermann3 0.000776 0.000081 0.000857 ( 0.000781)
PJS 0.001140 0.000113 0.001253 ( 0.001300)
test 1_000_000
Array size = 1_000_000
user system total real
Cary 0.061148 0.000787 0.061935 ( 0.063022)
spickermann1 0.043598 0.000474 0.044072 ( 0.044590)
spickermann2 0.044909 0.000663 0.045572 ( 0.046371)
spickermann3 0.042907 0.000210 0.043117 ( 0.043162)
PJS 0.072766 0.000226 0.072992 ( 0.073168)
I attribute the apparent superiority of #spickermann's answer to the fact that Enumerable#tally has no block to evaluate (unlike, for example, Enumerable#find in my answer).
Your code can be fixed by using find instead of each:
def find_uniq(arr)
arr.uniq.find { |e| arr.count(e) == 1 }
end
However this is quite inefficient since uniq needs to iterate the full collection. After finding the unique values the arr collection is iterated 1 or 2 more times by count (assuming there are only two unique values), depending on the position of the values in the uniq result.
For simple solution I suggest looking at the answer of spickermann which only iterates the full collection once.
For your specific scenario you could technically increase performance by short-circuiting the tally. This is done by manually tallying and breaking the loop if the tally contains 2 distinct values and at least 3 items are tallied.
def find_uniq(arr)
tally = Hash.new(0)
arr.each_with_index do |item, index|
break if tally.size == 2 && index >= 3
tally[item] += 1
end
tally.invert[1]
end
In this code if user type 2, two times and 1, two times. Then there's two maximum elements and both Kinder and Twix should be printed. But how ? I probably can do this with if method but this will make my code even longer. Any cool version? Can I do this with just one if?
a = [0, 0, 0,]
b = ["Kinder", "Twix", "Mars"]
while true
input = gets.chomp.to_i
if input == 1
a[0] += 1
elsif input == 2
a[1] += 1
elsif input == 3
a[2] += 1
elsif input == 0
break
end
end
index = a.index(a.max)
chocolate = b[index] if index
print a.max,chocolate
The question really has nothing to do with how the array a is constructed.
def select_all_max(a, b)
mx = a.max
b.values_at(*a.each_index.select { |i| a[i] == mx })
end
b = ["Kinder", "Twix", "Mars"]
p select_all_max [0, 2, 1], b
["Twix"]
p select_all_max [2, 2, 1], b
["Kinder", "Twix"]
See Array#values_at.
This could alternatively be done in a single pass.
def select_all_max(a, b)
b.values_at(
*(1..a.size-1).each_with_object([0]) do |i,arr|
case a[i] <=> arr.last
when 0
arr << i
when 1
arr = [i]
end
end
)
end
p select_all_max [0, 2, 1], b
["Twix"]
p select_all_max [2, 2, 1], b
["Kinder", "Twix"]
p select_all_max [1, 1, 1], b
["Kinder", "Twix", "Mars"]
One way would be as follows:
First, just separate the input-gathering from the counting, so we'll just gather input in this step:
inputs = []
loop do
input = gets.chomp.to_i
break if input.zero?
inputs << input
end
Now we can tally up the inputs. If you have Ruby 2.7 you can simply do counts_by_input = inputs.tally to get { "Twix" => 2, "Kinder" => 2 }. Otherwise, my preferred approach is to use group_by with transform_values:
counts_by_input = inputs.group_by(&:itself).transform_values(&:count)
# => { "Twix" => 2, "Kinder" => 2 }
Now, since we're going to be extracting values based on their count, we want to have the counts as keys. Normally we might invert the hash, but that won't work in this case because it will only give us one value per key, and we need multiple:
inputs_by_count = counts_by_input.invert
# => { 2 => "Kinder" }
# This doesn't work, it removed one of the values
Instead, we can use another group_by and transform_values (the reason I like these methods is because they're very versatile ...):
inputs_by_count = counts_by_input.
group_by { |input, count| count }.
transform_values { |keyvals| keyvals.map(&:first) }
# => { 2 => ["Twix", "Kinder"] }
The transform_values code here is probably a bit confusing, but one important thing to understand is that often times, calling Enumerable methods on hashes converts them to [[key1, val1], [key2, val2]] arrays:
counts_by_input.group_by { |input, count| count }
# => { 2 => [["Twix", 2], ["Kinder", 2]] }
Which is why we call transform_values { |keyvals| keyvals.map(&:first) } afterwards to get our desired format { 2 => ["Twix", "Kinder"] }
Anyway, at this point getting our result is very easy:
inputs_by_count[inputs_by_count.keys.max]
# => ["Twix", "Kinder"]
I know this probably all seems a little insane, but when you get familiar with Enumerable methods you will be able to do this kind of data transformation pretty fluently.
Tl;dr, give me the codez
inputs = []
loop do
input = gets.chomp.to_i
break if input.zero?
inputs << input
end
inputs_by_count = inputs.
group_by(&:itself).
transform_values(&:count).
group_by { |keyvals, count| count }.
transform_values { |keyvals| keyvals.map(&:first) }
top_count = inputs_by_count.keys.max
inputs_by_count[top_count]
# => ["Twix", "Kinder"]
How about something like this:
maximum = a.max # => 2
top_selling_bars = a.map.with_index { |e, i| b[i] if e == maximum }.compact # => ['Kinder', 'Twix']
p top_selling_bars # => ['Kinder', 'Twix']
If you have
a = [2, 2, 0,]
b = ['Kinder', 'Twix', 'Mars']
You can calculate the maximum value in a via:
max = a.max #=> 2
and find all elements corresponding to that value via:
b.select.with_index { |_, i| a[i] == max }
#=> ["Kinder", "Twix"]
I am trying to get the sum of points and average grade for each student inside this combination of hashes and arrays but all my attempts only return the general sum for all entries. Any ideas?
student_data =
{"ST4"=>[{:student_id=>"ST4", :points=> 5, :grade=>5},
{:student_id=>"ST4", :points=>10, :grade=>4},
{:student_id=>"ST4", :points=>20, :grade=>5}],
"ST1"=>[{:student_id=>"ST1", :points=>10, :grade=>3},
{:student_id=>"ST1", :points=>30, :grade=>4},
{:student_id=>"ST1", :points=>45, :grade=>2}],
"ST2"=>[{:student_id=>"ST2", :points=>25, :grade=>5},
{:student_id=>"ST2", :points=>15, :grade=>1},
{:student_id=>"ST2", :points=>35, :grade=>3}],
"ST3"=>[{:student_id=>"ST3", :points=> 5, :grade=>5},
{:student_id=>"ST3", :points=>50, :grade=>2}]}
The desired hash can be obtained thusly.
student_data.transform_values do |arr|
points, grades = arr.map { |h| h.values_at(:points, :grade) }.transpose
{ :points=>points.sum, :grades=>grades.sum.fdiv(grades.size) }
end
#=> {"ST4"=>{:points=>35, :grades=>4.666666666666667},
# "ST1"=>{:points=>85, :grades=>3.0},
# "ST2"=>{:points=>75, :grades=>3.0},
# "ST3"=>{:points=>55, :grades=>3.5}}
The first value passed to the block is the value of the first key, 'ST4' and the block variable arr is assigned that value:
a = student_data.first
#=> ["ST4",
# [{:student_id=>"ST4", :points=> 5, :grade=>5},
# {:student_id=>"ST4", :points=>10, :grade=>4},
# {:student_id=>"ST4", :points=>20, :grade=>5}]
# ]
arr = a.last
#=> [{:student_id=>"ST4", :points=> 5, :grade=>5},
# {:student_id=>"ST4", :points=>10, :grade=>4},
# {:student_id=>"ST4", :points=>20, :grade=>5}]
The block calculations are as follows. The first value of arr passed by map to the inner block is
h = arr.first
#=> {:student_id=>"ST4", :points=>5, :grade=>5}
h.values_at(:points, :grade)
#=> [5, 5]
After the remaining two elements of arr are passed to the block we have
b = arr.map { |h| h.values_at(:points, :grade) }
#=> [[5, 5], [10, 4], [20, 5]]
Then
points, grades = b.transpose
#=> [[5, 10, 20], [5, 4, 5]]
points
#=> [5, 10, 20]
grades
#=> [5, 4, 5]
We now simply form the hash that is the value of 'ST4'.
c = points.sum
#=> 35
d = grades.sum
#=> 14
e = grades.size
#=> 3
f = c.fdiv(d)
#=> 4.666666666666667
The value of 'ST4' in student_data therefore maps to the hash
{ :points=>c, :grades=>f }
#=> {:points=>35, :grades=>4.666666666666667}
The mappings of the remaining keys of student_data are computed similarly.
See Hash#transform_values, Enumerable#map, Hash#values_at, Array#transpose, Array#sum and Integer#fdiv.
Whatever you expect can be achieved as below,
student_data.values.map do |z|
z.group_by { |x| x[:student_id] }.transform_values do |v|
{
points: v.map { |x| x[:points] }.sum, # sum of points
grade: (v.map { |x| x[:grade] }.sum/v.count.to_f).round(2) # average of grades
}
end
end
As exact expected output format is not specified, obtained in following way,
=> [
{"ST4"=>{:points=>35, :grade=>4.67}},
{"ST1"=>{:points=>85, :grade=>3.0}},
{"ST2"=>{:points=>75, :grade=>3.0}},
{"ST3"=>{:points=>55, :grade=>3.5}}
]
For Ruby 2.6 using Object#then or Object#yield_self for Ruby 2.5
student_data.transform_values { |st| st
.each_with_object(Hash.new(0)) { |h, hh| hh[:sum_points] += h[:points]; hh[:sum_grade] += h[:grade]; hh[:count] += 1.0 }
.then{ |hh| {tot_points: hh[:sum_points], avg_grade: hh[:sum_grade]/hh[:count] } }
}
How it works?
Given the array for each student:
st = [{:student_id=>"ST4", :points=> 5, :grade=>5}, {:student_id=>"ST4", :points=>10, :grade=>4}, {:student_id=>"ST4", :points=>20, :grade=>5}]
First build a hash adding and counting using Enumerable#each_with_object with a Hash#default set at zero (Hash.new(0))
step1 = st.each_with_object(Hash.new(0)) { |h, hh| hh[:sum_points] += h[:points]; hh[:sum_grade] += h[:grade]; hh[:count] += 1.0 }
#=> {:sum_points=>35, :sum_grade=>14, :count=>3.0}
Then use then! (yield_self for Ruby 2.5)
step2 = step1.then{ |hh| {tot_points: hh[:sum_points], avg_grade: hh[:sum_grade]/hh[:count] }}
#=> {:tot_points=>35, :avg_grade=>4.666666666666667}
Put all together using Hash#transform_values as in the first snippet of code