Trying to find a student's grade by assignment number in Ruby - 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'

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]

Create an average of arrays from a hash in 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...

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.

How to analyze the "max" method

Can someone explain why "time" is the max value here?
my_array = %w{hello my time here is long}
my_array.max #=> "time"
Because alphabetically t in time is greater here among others in your array my_array.
Here is one way,how string comparisons happened :
'hello' > 'time' # => false
'my' > 'time' # => false
'here' > 'time' # => false
'is' > 'time' # => false
'long' > 'time' # => false
To understand the outputs of the above fragment code,you must need to see String#<=> documentation. As your my_array contains all string instances,which has called the method <=>,to build the output of max.
Documentations says Enumerable#max:
Enumerable#max,without block assumes all objects implement Comparable.
Here's how computers look at the strings and compare them.
If we look at the first characters of each word it'll help a little, because we know how the alphabet orders letters:
%w[hello my time here is long].map{ |s| s[0] }.sort # => ["h", "h", "i", "l", "m", "t"]
But that doesn't really help visualize it, so here's a look at each word's letters as a computer sees them:
%w[time tome].each do |w|
puts w.chars.map(&:ord).join(', ')
end
# >> 116, 105, 109, 101
# >> 116, 111, 109, 101
Each letter has a value. Over the years there have been many different ways of ordering letters for a computer, which caused the character to value mapping to change. EBCDIC and ASCII have been the most popular but have different orders. We're usually dealing with ASCII, or a derivative, which is set by the OS.
Look at how the characters in the words are represented by the values in the following output. It should make it easy to understand what the computer is doing then.
%w[he hello help holler hollow].sort.each do |w|
puts '"%6s": %s' % [ w, w.chars.map(&:ord).join(', ') ]
end
# >> " he": 104, 101
# >> " hello": 104, 101, 108, 108, 111
# >> " help": 104, 101, 108, 112
# >> "holler": 104, 111, 108, 108, 101, 114
# >> "hollow": 104, 111, 108, 108, 111, 119

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

Resources