My question is whether I can use a range as the value in a key:value pair in a hash. I am working on a problem where I am trying to return a letter grade (A-F) for an average of numerical grades (array of numbers). I have a working solution, but I came across something intriguing. Here is my code:
def get_grade(array)
avg = (array.inject {|num, x| num + x}) / array.length
grades = {
"A" => [90..10]
"B" => [80..89],
"C" => [70..79],
"D" => [60..69],
"F" => [0..59],
}
grades.default = "Error"
puts grades.key(avg)
end
arraya = [100,90,100,99,99]
puts get_grade(arraya)
I know I could return the letter grade with either a case or an if statement. It seems like I should be able to use a hash instead but it doesn't work. Why can't I set up a hash with a range as value? Thanks.
You could use a case statement:
def get_grade(scores)
case scores.inject(&:+) / scores.length
when 90..100; 'A'
when 80..89; 'B'
when 70..79; 'C'
when 60..69; 'D'
when 0..59; 'F'
else; 'Error'
end
end
arraya = [100,90,100,99,99]
puts get_grade(arraya)
#=> A
You may want to rewrite your method as the following:
def get_grade(array)
avg = array.inject(:+) / array.length
grades = {
"A" => (90..100),
"B" => (80..89),
"C" => (70..79),
"D" => (60..69),
"F" => (0..59),
}
grade = grades.find{|key, range| range.include?(avg) }
grade.nil? ? "Unknown" : grade.first
end
arraya = [100,90,100,99,99]
puts get_grade(arraya) # => A
Related
I have two hashes like below,
h1 = {"a" => 1, "b" => 2, "c" => 3}
h2 = {"a" => 2, "b" => 2, "d" => 3}
I want to iterate over hash1 and hash2 and find matching keys and their values and print it on console.
Example here it should return output "b" => 2 .its not working with below code,
h1.each do |key1, value1|
h2.each do |key2, value2|
if ((h2.include? key1) && (h2.include? value1))
puts "matching h2 key #{h2[key2]}and h1 key #{h1[key1]}"
else
puts " don not match h2 key #{h2[key2]}and h1 key #{h1[key1]}"
end
end
end
I am from basically C++ and Java background and its very easy to do using for loops and iterators, but using Ruby, it is very difficult.
h1.merge(h2) { |k,o,n| puts "#{k}=>#{o}" if o == n }
"b" => 2
This uses the form of Hash#merge that employs a bock to determine the values of keys that are present in both hashes being merged. See the doc for details.
The second loop is not required.
h1.each do |key1, value1|
if (h2.include? key1) and (h2[key1] == value1)
puts "Match #{key1} with value #{value1}"
else
puts "#{key1} does not match"
end
end
You might write something like
h1.each do |k,v|
if h2[k] == v
puts "matched key = #{k} and value = #{v}"
else
puts "NOT matched key = #{k} and value = #{v}"
end
end
Output
NOT matched key = a and value = 1
matched key = b and value = 2
NOT matched key = c and value = 3
If the result is the main objective, select can work too:
h1.select{|k,v| h2[k] == v }
Working on a project and trying to turn this method (I Have some more similar methods like that in my project) into a more dynamic and concise way
Data from image
def proficiency_parser(stored_data, name, race, year, title, percentage)
if stored_data.has_key?(name)
if stored_data[name].has_key?(race)
if stored_data[name][race].has_key?(year)
stored_data[name][race][year][title] = percentage
else
stored_data[name][race][year] = {title => percentage}
end
else
stored_data[name][race] = {year => {title => percentage}}
end
else
stored_data[name] = {race => {year => {title => percentage}}}
end
end
so essentially this method through my data to identify whether it meets so of those specification showing in the code, essentially I just don't want to use this amount of "elses" and "Ifs" if at all possible.
Data
stored_data
# => {"COLORADO"=>{3=>{2008=>{:math=>0.697}}}}
name
# => "COLORADO"
race
# => 3
year
# => 2008
title
# => :math
percentage
# => 0.697
Take a look at Hash#dig which is included in Ruby versions 2.3.0 or newer.
To summarize:
hash_1 = { a: { a: { a: "b" } } }
hash_2 = { c: { c: { c: "d" } } }
hash_1.dig(:a, :a, :a) # returns "b"
hash_2.dig(:a, :a, :a) # returns nil
So you could say if hash_1.dig(:a, :a) instead of
if hash_1[:a]
if hash_1[:a][:a]
# etc
There's also another way to do it, which is to rescue your NoMethod [] errors.
Here's an example of that:
if hash_1[:a][:a][:a] rescue false
puts "the key exists"
else
puts "the key doesnt exist"
end
You can use some recursive call
Input data
stored_data = {}
name = 'COLORADO'
race = 3
year = 2008
title = :math
percentage = 0.697
Methods
def proficiency_parser(stored_data, name, race, year, title, percentage)
parser(stored_data, name, {race => {year => {title => percentage}}})
end
def parser(data, key, value)
data[key] ? value.each { |k, v| parser(data[key], k, v) } : data[key] = value
end
call
proficiency_parser(stored_data, name, race, year, title, percentage)
p stored_data
# => {"COLORADO"=>{3=>{2008=>{:math=>0.697}}}}
I hope this helps
My solution to Project Euler 022 is running incorrectly and I can't see why:
$scores = {
"A" => 1,
"B" => 2,
"C" => 3,
...
"Z" => 26
}
def alphabetScore(name)
nameScore = 0
array = name.split(//)
array.each { |n| nameScore += $scores[n] }
return nameScore
end
file = File.read("p022_names.txt").split(",")
file.map! { |n| n.tr('^A-Za-z0-9','') }
totalScore = 0
file.each do |findScore|
nameScore = alphabetScore(findScore)
totalScore += nameScore
end
p totalScore
# p file # to check that 'file' is indeed an array of strings
Testing individual names e.g p alphabetScore("AGNES") returns the correct value but when totaling the score of every name I get 324536, which is incorrect (the correct answer is 871198282). My only guess as to why this isn't working is that one of the names (presumably the one which would cause totalScore to exceed 324536) is broken in some way.
Looks like you missed this part of the prompt:
Then working out the alphabetical value for each name, multiply this value by its alphabetical position in the list to obtain a name score.
How can I handle a large number of conditions in a case statement?
...I'm about to write a case statement with about 125 when's.
This is along the lines of what I'm doing now, based on each when I add a node to a Nokogiri XML document, each when has two values that get set in the node, before setting the namespace:
case var
when :string
property_uom_node = Nokogiri::XML::Node.new "test_value", #ixml.doc
property_uom_node['att'] = "val"
property_uom_node.namespace = #ixml.doc.root.namespace_definitions.find{|ns| ns.prefix=="dt"}
property_uom_node
when :integer
#do something else
when :blue
...
#100 more when statements
...
end
I'm not looking for domain specific advice, just if there is a clean way to do this without ending up with a 300 line method.
This is what I ended up doing:
lookup = {:lup => ["1","2"], :wup => ["1","2"]}
case param
when lookup.has_key?(param)
property_uom_node = Nokogiri::XML::Node.new "#{lookup[param][0]}", #ixml.doc
property_uom_node['att'] = #{lookup[param][1]}
property_uom_node.namespace = #ixml.doc.root.namespace_definitions.find{|ns| ns.prefix=="dt"}
property_uom_node
end
Many case statements can, and many should, be replaced with other structures. Basically, the idea is to separate the policy -- what you want the code to do -- from the implementation -- how the code does it.
Suppose that your case statement is keyed on a symbol (that is, each of then when clauses is a constant symbol):
case foo
when :one
puts 1
when :two
puts 2
when :three
puts 3
else
puts 'more'
end
This can be replaced mostly with a data structure:
INTS = {:one => 1, :two => 2}
key = :one
puts INTS[key] # => 1
What if there are two different values, and not just one? Then make each value its own hash:
DOGS = {
:dog1 => {:name => 'Fido', :color => 'white},
:dog2 => {:name => 'Spot', :color => 'black spots'},
}
key = :dog2
dog = DOGS[key]
puts "#{dog[:name]}'s color is #{dog[:color]}"
# => "Spot's color is black spots"
It looks like the second case statement only has one case. A hash is a good way to do a lookup(many cases). You might try it like this:
if val = lookup[param]
property_uom_node = Nokogiri::XML::Node.new(val[0], #ixml.doc)
property_uom_node['att'] = val[1]
property_uom_node.namespace = #ixml.doc.root.namespace_definitions.find{ |ns| ns.prefix == "dt" }
property_uom_node # return the node
else
# not one of our cases
end
Say I have an array:
array = [6, 6, 6, 4, 4, 6, 4, 4]
and I have another array of strings:
quadrant = ["upper_left", "upper_right", "lower_left", "lower_right"]
and I have a 8 x 8 2-d array consisting of board locations(#board) that are either nil or Checker objects.
My goal is to create a hash such:
hash = { "upper_left" => #board[array[0]][array[1]] ,
"upper_right" => #board[array[2]][array[3]] ,
"lower_left" => #board[array[4]][array[5]] ,
"lower_left" => #board[array[6]][array[7]] }
I have the following code:
jump_positions = {}
QUADRANTS.each do |quad|
array.each_slice(2) do |coord|
jump_positions[quad] = #board[coord[0]][coord[1]]
end
And then the test:
it "should assign board locations as adjacent positions and deliver that info as a whole" do
#bs.board = board.create_board
x_coord = 3
y_coord = 1
jump_assignments = #bs.assign_adjacent_positions(x_coord, y_coord)
jump_assignments["upper_left"].class.should == nil
jump_assignments["upper_right"].class.should == nil
jump_assignments["lower_left"].class.should == Checker
jump_assignments["lower_right"].class.should == Checker
end
fails because all the assignments are of class 'Checker' and turns out they're all the same 'Checker' object. I know its doing this because the loops are nested so all the 'quad' keys are getting initialize to the last board location.
Is there a way I can assign the value to the key with a value from the 'array' in one pass so that they get assigned correctly? Does this question even make sense?
I think you just need to add a little map to my answer to your other similar question:
hash = Hash[quadrant.zip(array.each_slice(2).map { |a| #board[a.first][a.last] })]
Given a board like this:
#board = [
['11', '12', ... '18'],
['21', '22', ... '28'],
...
['81', '82', ... '88']
]
the above construct gives me a hash like this:
{
"upper_left" => "77",
"upper_right" => "75",
"lower_left" => "57",
"lower_right" => "55"
}
and that seems to be what you're looking for.
mu too short made me reconsider my question, and I believe my algorithm was broken. I actually ended up breaking this method down into three interdependent methods:
def deltas_to_board_locations(deltas, x, y)
board_coords = []
deltas.each_slice(2) do |slice|
board_coords << x + slice[0]
board_coords << y + slice[1]
end
board_coords
end
def assign_adjacent_board_coords(x, y)
jump_positions = Hash[QUADRANTS.zip(deltas_to_board_locations(normal_deltas, x, y).each_slice(2))]
end
def determine_adjacent_positions_content(board, board_coords)
adjacent_content = {}
board_coords.each_pair { |quad, coords| adjacent_content[quad] = board[coords[0]][coords[1]] }
adjacent_content
end
which worked out quite nicely.