Create an average of arrays from a hash in Ruby - ruby

So I have a hash like the following:
grade_hash = {bill: [100, 95, 92], frank: [67, 73, 84]}
I'm trying to find the average for both Bill and Frank.
I know that if I did something like:
def average (grade_hash)
grade_hash.transform_values{|num| num.reduce(:+)/num.size}
end
I can then pull out either Bill or Franks average.
How would I pull the average from all values (Bill and Frank's combined)?
I've attempted to do a .each at the end to iterate over but that doesn't seem to work because I wouldn't want to really iterate I would just want to take the sum from each created array then find an average.
Thoughts?

Try this one
def average(grade_hash)
grades = grade_hash.values.flatten
grades.sum / grades.size.to_f
end

def combined_average(grade_hash, *students)
raise ArgumentError, "There must be at least one student" if students.empty?
non_students = students - grade_hash.keys
raise ArgumentError, "#{non_students} are not students" if non_students.any?
arr = grade_hash.values_at(*students).flatten
arr.sum.fdiv(arr.size).round(1)
end
grade_hash = {bill: [100, 95, 92], frank: [67, 73, 84], julie: [99, 99, 100] }
combined_average(grade_hash, :bill) #=> 95.7
combined_average(grade_hash, :frank) #=> 74.7
combined_average(grade_hash, :julie) #=> 99.3
combined_average(grade_hash, :bill, :frank) #=> 85.2
combined_average(grade_hash, :bill, :frank, :julie) #=> 89.9
combined_average(grade_hash, :bill, :mimi, :freddie)
#=>ArgumentError: [:mimi, :freddie] are not students...
combined_average(grade_hash)
#=> ArgumentError: There must be at least one student...

Related

Ruby Proc with Parameter

I'm trying to send a parameter to a Ruby proc
p1 = [54, 21, 45, 76, 12, 11, 67, 5]
qualify = proc { |age, other| age > other }
puts p1.select(&qualify(30))
This is the error I get:
undefined method `qualify' for main:Object
age comes from the iteration of the array, and I want to have that last parameter (30) to get into the proc.
Is a proc the right tool to be using for this? I'm new to proc. I'm unclear how to get that parameter in there.
In order to use qualify in as select predicate, you need to reduce its arity (number of accepted arguments) through partial application. In other words - you need a new proc that would have other set to 30. It can be done with Method#curry, but it requires changing order of parameters:
qualify = proc { |other, age| age > other }
qualify.curry.call(30).call(10)
# => false
qualify.curry.call(30).call(40)
#=> true
I order to be able to pass this proc to select using &, you need to assign it so that it's available in the main object, e.g. by assigning it to an instance variable:
#qualify_30 = qualify.curry.call(30)
Now you can call:
p1.select{ |age| #qualify_30.call(age) }
# => [54, 45, 76, 67]
or:
p1.select(&#qualify_30)
# => [54, 45, 76, 67]
or inline:
p1.select(&qualify.curry.call(30))
# => [54, 45, 76, 67]
The easy way is to shuffle up how you define this:
p1 = [54, 21, 45, 76, 12, 11, 67, 5]
qualify = proc { |age| age > 30 }
puts p1.select(&qualify).join(',')
By moving the 30 into the qualify proc you've baked in the condition, it's no longer dynamic. Remember, the only methods that can be used with the shorthand &: trick are zero-argument ones, or single argument ones with & on a proc.
You could also use a closure to have the comparison variable exposed:
p1 = [54, 21, 45, 76, 12, 11, 67, 5]
required = 30
qualify = proc { |age| age > required }
puts p1.select(&qualify).join(',')
required = 10
puts p1.select(&qualify).join(',')
The better way is to just spell it out, that's what Ruby is all about. Here in a more idiomatic form:
p1 = [54, 21, 45, 76, 12, 11, 67, 5]
puts p1.select { |age| age > 30 }
The only reason for an intermediate Proc is if you'd want to, for some reason, save that somewhere and re-use it later.
Use the select statement in the proc itself, so that the proc would calculate and return an array.
2.1.5 :119 > qualify = proc { |age_array, age_limit| age_array.select { |age| age > age_limit } }
=> #<Proc:0xe7bc2cc#(irb):119>
2.1.5 :120 >
2.1.5 :121 >
2.1.5 :122 > qualify.call(p1, 30)
=> [54, 45, 76, 67]

Trying to find a student's grade by assignment number in Ruby

So I have a hash like the following:
data = {bill: [100, 95, 92], frank: [67, 73, 84]}
I'm trying to build it out so that it would do 95 if I put in :bill, 2.
I'm getting really caught up in the iteration.
I have, which hasn't worked:
def scores (grade_hash, student, assign_number)
grade_hash.map.with_index {|i, x| puts x-i}
end
Clearly I'm a novice at Ruby. Any suggestions?
Try this:
def scores(grade_hash, student, assign_number)
grade_hash[student][assign_number - 1]
end
puts scores(data, :bill, 2) #=> 95
Some explanation:
{bill: [100, 95, 92], frank: [67, 73, 84]}[:bill] #=> [100, 95, 92]
[100, 95, 92][1] #=> 95
In newer versions of ruby (2.3+), you can use the dig method of Array and Hash without needing a custom method for this, though the index you pass in still needs to be 0-based:
data.dig(:bill, 2) # => 92
data.dig(:bill, 1) # => 95
data.dig(:bill, 5) # => nil -- they haven't taken 6 tests, yet
data.dig(:john, 1) # => nil -- there is no student 'john'

Ruby assignment hash [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I am working on a program to calculate grades and am using a hash of values to help with the letter assignments. My hash looks like this
LETTERS = {
"A+" => 98, "A" => 95, "A-" => 92,
"B+" => 88, "B" => 85, "B-" => 82,
"C+" => 78, "C" => 75, "C-" => 72,
"D+" => 68, "D" => 65, "D-" => 62,
"F+" => 55, "F" => 40, "F-" => 25,
}
My question is how would I be able to assign, say, a 71 to a grade even though it is not an explicit value in the hash?
Firstly, in ruby we call it a hash - not a dictionary. You might do what you want with:
def grade(points)
LETTERS.find {|_, v| v <= points}.first
end
Note: Find method depends on the order of the hash - the function above will not work correctly if the hash is not ordered (desc) by values. Also - you didn't say what should happen if points are, say, 20 (below any threshold). Currently it will throw NoMethodError
I don't see the reason for using a hash here. In fact, the keys and the values in the OP's hash are the opposite, and useless.
[
[98, "A+"],
[95, "A"],
[92, "A-"],
[88, "B+"],
[85, "B"],
[82, "B-"],
[78, "C+"],
[75, "C"],
[72, "C-"],
[68, "D+"],
[65, "D"],
[62, "D-"],
[77, "F+"],
[40, "F"],
[25, "F-"],
]
.bsearch{|x, _| x <= 89}.to_a.last
# => "B+"
which turned out to be almost the same as BroiSatse's answer.
Not an exact answer, but:
You could instead use a function that returns the grade depending on the value
def get_grade(points)
return "A+" if points >= 98
return "A" if points < 98 and points >= 95
... # etc
end
That way you don't have to assign each value a grade.
Alternatively, you could assign each grade an array of points
Another possibility is to encode the logic into a method using case. I'm adding this option because #blueygh2's answer was burning my eyes :)
def grade(score)
case score
when 98..100
"A+"
when 95...98
"A"
when 92...95
"A-"
end
# and so forth
end
Using the non-inclusive ranges (with three dots) makes it work with fractional scores, but that may not be a requirement.

Ruby Byte XOR strange result - help please

I was doing some XOR of data and things were going well with my hex based XOR. It was recommend that I use a byte XOR (^) and only work with bytes. I thought that will take no time to change that but I have the some strange behaviour that I had not expected.
Could some add a little light as to why I'm getting a different result if I'm processing the string as bytes. I was expecting it to be the same.
m_hex_string ="124f33e6a118566377f237075354541f0a5a1b"
m_XOR_string ="662756c6c27732065796586974207468652870"
m_expected ="the code don't work"
m_expected_hex ="74686520636f646520646f6e277420776f726b"
def XOR_hex_strings(a,b)
(a.hex ^ b.hex).to_s(16)
end
def XOR_byte_strings(s1,s2)
xored = s1.bytes.zip(s2.bytes).map { |(a,b)| a ^ b }.pack('c*')
end
def hex_digest(hexdigest)
[hexdigest].pack("H*")
end
puts "My strings for stack overflow"
puts "'"+hex_digest(XOR_hex_strings(m_hex_string,m_XOR_string))+"'"
puts "'"+hex_digest(XOR_byte_strings(m_hex_string,m_XOR_string))+"'"
Results:
My strings for stack overflow
'the code don't work'
'tje`#ode ?on't ~mrk'
The text should be the same 'the code don't work' for both methods. I'd really like to know why rather than just a correct code fragment. thanks.
As already said in the comments, bytes doesn't take the hex format into account, it just returns the integer values for "1", "2", "4", "f" etc. You can convert the hex string with pack:
[m_hex_string].pack("H*")
# => "\x12O3\xE6\xA1\x18Vcw\xF27\aSTT\x1F\nZ\e"
unpack converts this into a byte array, just like bytes but more explicit and faster (IIRC):
[m_hex_string].pack("H*").unpack("C*")
# => [18, 79, 51, 230, 161, 24, 86, 99, 119, 242, 55, 7, 83, 84, 84, 31, 10, 90, 27]
The final method would look like:
def XOR_pack_unpack_strings(s1, s2)
s1_bytes = [s1].pack("H*").unpack("C*")
s2_bytes = [s2].pack("H*").unpack("C*")
s1_bytes.zip(s2_bytes).map { |a, b| a ^ b }.pack('C*')
end
If speed is an issue, take a look at the fast_xor gem:
require 'xor'
def XOR_fast_xor_strings(s1_hex, s2_hex)
s1 = [s1_hex].pack("H*")
s2 = [s2_hex].pack("H*")
s1.xor!(s2)
end

How to remove outside array from joining two separate arrays with collect

I have an object called Grade with two attributes material and strength.
Grade.all.collect { |g| g.material }
#=> [steel, bronze, aluminium]
Grade.all.collect { |g| g.strength }
#=> [75, 22, 45]
Now I would like to combine both to get the following output:
[steel, 75], [bronze, 22], [aluminium, 45]
I currently do this
Grade.all.collect{|e| e.material}.zip(Grade.all.collect{|g| g.strength})
#=> [[steel, 75], [bronze, 22], [aluminium, 45]]
Note: I do not want the outside array [[steel, 75], [bronze, 22], [aluminium, 45]]
Any thoughts?
Splat the array to a mere list.
*Grade.all.collect{ |g| [g.material, g.strength] }

Resources