I've seen solutions posted in other languages but not Ruby so I'm asking here.
I'm trying to create a student marks system using Ruby, this should take the student’s name and two marks from the user for that particular student.
I decided to try and store these in a hash so it would end up looking something like:
student_marks = {
"Steve" => 45, 65,
"James" => 20, 75,
"Scott" => 30, 90
}
My code attempt is as follows:
continue = "y"
student_grades = Hash.new
while continue == "y"
puts "Please enter student name"
name = gets.chomp
puts "Please enter the first grade for #{name}"
grade_one = gets.chomp.to_i
puts "Please enter the second grade for #{name}"
grade_two = gets.chomp.to_i
student_grades.each do |key, value|
student_grades[key] = name
student_grades[value] = grade_one
student_grades[value][1] = grade_two
end
puts "Do you want to continue? y or n"
continue = gets.chomp
end
puts student_grades
Obviously there is a problem with my logic in trying to populate the hash using each/iteration because I keep getting nil return. I guess I could use arrays and populate the hash from them, but is there a way to populate the hash both the keys and values using iteration from user input?
You seem to be misunderstanding how hashes work in Ruby. I would suggest reading up on it a bit.
In the meantime, try this:
student_grades = Hash.new {|h,k| h[k] = [] }
Now every time you get a student grade, you can push it into the array value for that student's hash key.
For example:
student_grades
=> {}
student_grades['Mark'] << 95
student_grades['Mark'] << 86
student_grades
=> {'Mark' => [95, 86]}
You don't need to iterate over the hash at all in each run of your loop, you should only be doing that if you need to extract some information from it.
Related
okay so, im asking 2 questions and putting each user's input into separate hashes. I am using loop to iterate through this multiple times then adding all hashes into an array.
My question is/where im stuck, how do I assign the sum of values of each hash, to a separate hash of their own.
Here's my code:
arr:[]
(1..n).each do |i|
hash=Hash.new()
puts "Please input a value for day # #{i}"
hash["day1"]=gets.chomp.to_f
puts "Please input a value for day # #{i}"
hash["day2"]=gets.chomp.to_f
arr << hash
end
I thought of doing this since this is the method i've been using to collect users input and put them in a hash but it doesn't work:/
hash[:total]= hash.each_value.inject(:+)
I basically want to sum each hash and put it into a new hash. is that even possible? it's hard cause I would have to do it outside of the block but then the system wouldn't recognize the variables:/
You can try Hash#reduce method with Hash#merge
arr = []
3.times do |i|
hash = Hash.new()
puts "Please input a value for day # #{i}"
hash["day1"] = gets.chomp.to_f
puts "Please input a value for day # #{i}"
hash["day2"] = gets.chomp.to_f
arr << hash
end
totals = arr.reduce({}) do |sums, day|
sums.merge(day) { |_, a, b| a + b }
end
puts totals
# {"day1"=>8.0, "day2"=>11.0}
I need to update this hash with as many items as the user wants. And I have a problem with updating it.
It only shows the last input from user.
For example in i = 2, the hash will only show the second key and value added, and I want both. Or if i = 3 i need all three inputs.
puts "how may items to add in hash?"
i=gets.chomp.to_i
for i in 1..i
puts "add key"
key = gets.chomp
puts "add value"
value = gets.chomp.to_f.round(2)
project = Hash.new()
project = {key => value}
project.each do |key, value|
puts "#{key} \t - \t #{value}%"
end
end
Anyone that can help?
Just move your variable initialization our of the loop. Right now you rewrite your project with blank hash on each iteration. That's why it stores only the last item. Here is your possible code:
puts "how may items to add in hash?"
project = {}
i=gets.chomp.to_i
for i in 1..i
puts "add key"
key = gets.chomp
puts "add value"
value = gets.chomp.to_f.round(2)
project[key] = value
end
project.each do |key, value|
puts "#{key} \t - \t #{value}%"
end
project.values.inject(&:+)
PS: Prefer {} over Hash.new() (https://github.com/bbatsov/ruby-style-guide#literal-array-hash)
I am trying to get a default value whilst using hashes in ruby. Looking up the documentation you use a fetch method. So if a hash is not entered then it defaults to a value. This is my code.
def input_students
puts "Please enter the names and hobbies of the students plus country of birth"
puts "To finish, just hit return three times"
#create the empty array
students = []
hobbies = []
country = []
cohort = []
# Get the first name
name = gets.chomp
hobbies = gets.chomp
country = gets.chomp
cohort = gets.chomp
while !name.empty? && !hobbies.empty? && !country.empty? && cohort.fetch(:cohort, january) do #This is to do with entering twice
students << {name: name, hobbies: hobbies, country: country, cohort: cohort} #import part of the code.
puts "Now we have #{students.count} students"
# get another name from the user
name = gets.chomp
hobbies = gets.chomp
country = gets.chomp
cohort = gets.chomp
end
students
end
You just need to give fetch a default it can handle. It doesn't know what to do with january as you haven't declared any variable with that name. If you want to set the default value to the string "january", then you just need to quote it like this:
cohort.fetch(:cohort, "january")
There are some decent examples in the documentation for fetch.
Also, cohort isn't a Hash, it's a String since gets.chomp returns a String. fetch is for "fetching" values from a Hash. The way you're using it should be throwing an error similar to: undefined method 'fetch' for "whatever text you entered":String.
Finally, since you're using it in a conditional, the result of your call to fetch is being evaluated for its truthiness. If you're setting a default, it will always be evaluated as true.
If you just want to set a default for cohort if it's empty, you can just do something like this:
cohort = gets.chomp
cohort = "january" if cohort.empty?
while !name.empty? && !hobbies.empty? && !country.empty?
students << {
name: name,
hobbies: hobbies,
country: country,
cohort: cohort
}
... # do more stuff
Hope that's helpful.
You have several options. #dinjas mentions one, likely the one you want to use. Suppose your hash is
h = { :a=>1 }
Then
h[:a] #=> 1
h[:b] #=> nil
Let's say the default is 4. Then as dinjas suggests, you can write
h.fetch(:a, 4) #=> 1
h.fetch(:b, 4) #=> 4
But other options are
h.fetch(:a) rescue 4 #=> 1
h.fetch(:b) rescue 4 #=> 4
or
h[:a] || 4 #=> 1
h[:b] || 4 #=> 4
You could also build the default into the hash itself, by using Hash#default=:
h.default = 4
h[:a] #=> 1
h[:b] #=> 4
or by defining the hash like so:
g = Hash.new(4).merge(h)
g[:a] #=> 1
g[:b] #=> 4
See Hash::new.
I have a hash
h = Hash.new{|hsh,key| hsh[key] = [] }
And its values are stored as an array
Iv added to the value array for the key like so:
h[#name] << #age
h[#name] << #grade
and im trying to access the age like this
puts h[:#name][0]
But it doesn't work?
Is there a better way of doing this?
What im trying to do is create is a hash where there is a key which has loads of values:
For example key=>name and values equal age, address, gender etc
IMHO your idea is ok.
The only mistake is.. how you access the hash. No need to add extra colon : before # sign.
Remove colon and it should work as you expect:
puts h[#name][0]
A Hash is a collection of key-value pairs like this: "employee" => "salary". It is similar to an Array, except that indexing is done via arbitrary keys of any object type, not an integer index.
The order in which you traverse a hash by either key or value may seem arbitrary and will generally not be in the insertion order. If you attempt to access a hash with a key that does not exist, the method will return nil.
A hash is used to stored large (or small) amounts of data and access it efficiently. For example lets say you have this as a hash:
prices = {
'orange' => 3.15,
'apple' => 2.25,
'pear' => 3.50
}
Now you want to call the keyword apple and get the prices of those items from some users input:
print 'Enter an item to verify price: '
item = gets.chomp
puts "The price of an #{item}: #{prices[item]}"
# <= The price of an apple: 2.25
That's a basic hash, now lets get into what you're doing, using an Array as a key.
prices = {
'apple' => ['Granny Smith', 'Red'],
'orange' => ['Good', 'Not good'],
'pear' => ['Big', 'Small']
}
print 'Enter an item for a list: '
item = gets.chomp
puts "We have the following #{item}'s available: #{prices[item]}"
# <= We have the following apple's available: ["Granny Smith", "Red"]
Now if we wanted to grab one of the types:
puts prices[item][0]
# <= Granny Smith
puts prices[item][1]
#<= Red
Now lets get into more advanced techniques like you are doing above, you're idea is great and all, but what you need to do is append the information into the hash and when you call #name don't try to call it as a symbol:
h = Hash.new{|hsh,key| hsh[key] = [] }
h[#name] = []
#<= []
h[#name] << ['apple', 'pear']
#<= [["apple", "pear"]]
h[#name] << ['orange', 'apple']
#<= [["apple", "pear"], ["orange", "apple"]]
h[#name].flatten[0]
#<= "apple"
h[#name].flatten[1]
#<= "pear"
h[#name].flatten[1, 2]
#<= ["pear", "orange"]
Alright so what did we do?
h = Hash.new{|hsh,key| hsh[key] = [] }
Created a hash with a value as an empty array.
h[#name] = []
Initialized #name to the empty array
h[#name] << ['apple', 'pear']
Appended an array containing apple, pear to the #name key.
h[#name] << ['orange', 'apple']
Appended a second array containing orange, apple to the array, so when we call h[#name][1] right now it will output the first array appended to it.
h[#name].flatten[0]
Flattened the array into a single array and called the first element of the array.
When you call your key (#name) you don't call it as a symbol, because it's already contained inside of a variable. So all you have to do is call that variable and the value for that key will be output successfully. Hopefully this clarifies a few, things, for more information on hashes check this out: http://www.tutorialspoint.com/ruby/ruby_hashes.htm
Working on trying to understand the syntax for calling on different values of a hash.
For example lets say I am trying to delete 'pants' How do go about setting the argument for something like this:
products = {124 => ['shoes', 59.99], 352 => ['shirt', 19.99], 777 => ['pants', 19.87],
667 => ['jacket', 39.99], 898 => ['shoulder_holster', 22.78]}
While writing a menu driven program for this hash I'm including error checking before deleteing or adding a key this is what I have so far:
if a == 3 # Loop delete a Product
puts "Delete a Product"
d = gets.to_s # Get value for argument
while products.has_value?( d + syntax for right here???? )!= true do
puts "This turned out false because product does not exsist!"
d = gets.to_s
end
puts "Congrats your out of the loop"
products.delete(d + again syntax problems ???? )
puts products
end
How do I enter the syntax for the argument if I where to delete pants. Would it be ([d,:number]) I'm not having luck with any resources online with how to delete or add in this scenario. Any help or code example would be appreciated,
Matt
products.to_a.select {|a| a.last.first == 'pants' }
That will get you the record that matches 'pants'.
[[777, ["pants", 19.87]]]
So I think you'll want
while !products.to_a.select {|a| a.last.first == d }.empty?
on your loop then use Dafydd's line to delete the record.
It depends on whether the user is inputing the ID number or the name "pants". If the former:
if a == 3 # Loop delete a Product
puts "Delete a Product"
d = gets # Get value for argument
until products.has_key?(d.to_i)
puts "This turned out false because product does not exsist!"
d = gets
end
puts "Congrats your out of the loop"
products.delete(d.to_i)
puts products
end
If it's "pants", then this is how you want to do it:
if a == 3 # Loop delete a Product
puts "Delete a Product"
d = gets.strip # Need to strip because otherwise the newline will wreck it
until products.find {|key, val| val.first == d}
puts "This turned out false because product does not exsist!"
d = gets.strip
end
puts "Congrats your out of the loop"
products.delete_if {|key, val| val.first == d}
puts products
end
Writing a "delete named product from hash" method
There are shorter ways of doing it, but shooting for clarity I came up with this:
products = {124 => ['shoes', 59.99], 352 => ['shirt', 19.99], 777 => ['pants', 19.87],
667 => ['jacket', 39.99], 898 => ['shoulder_holster', 22.78]}
def wipeProduct(hash, nameToDelete)
hash.each do |i|
key = i[0]
productName = i[1].first
hash.delete(key) if productName==nameToDelete
end
end
puts products.inspect
wipeProduct(products,'pants')
puts products.inspect
wipeProduct(products,'shoulder_holster')
puts products.inspect
bash-3.2$ ruby prod.rb
{352=>["shirt", 19.99], 898=>["shoulder_holster", 22.78], 667=>["jacket", 39.99], 777=>["pants", 19.87], 124=>["shoes", 59.99]}
{352=>["shirt", 19.99], 898=>["shoulder_holster", 22.78], 667=>["jacket", 39.99], 124=>["shoes", 59.99]}
{352=>["shirt", 19.99], 667=>["jacket", 39.99], 124=>["shoes", 59.99]}
I don't know if it's possible for "pants" to occur in the hash in multiple places, but since I used "hash.each(...)", the method wipeProduct(hash, nameToDelete) will test every hash entry.
The input type bug and how to fix it
When you take input, you're assigning the string you captured to d. Here's the proof:
irb(main):010:0> d = gets.to_s
12
=> "12\n"
irb(main):011:0> d.class
=> String
You can convert that string to a Fixnum like this:
irb(main):012:0> d.to_i
=> 12
irb(main):013:0> d.to_i.class
=> Fixnum
All keys in the products hash are Fixnums. Here's the proof:
irb(main):014:0> products.keys.each {|i| puts i.class}
Fixnum
Fixnum
Fixnum
Fixnum
Fixnum
=> [352, 898, 667, 777, 124]
So you need to capture the value for the argument with this line:
d = gets.to_i # Get value for argument
The deletion part of the answer:
From products, you can delete the pants entry programmatically with this:
products.delete(777)
Running it gets you this:
irb(main):003:0> products.delete(777)
=> ["pants", 19.87]
Notice that you supply the key value (in this case 777) to .delete() and that it returns an array consisting of the key and value in that order respectively.
An alternative implementation
I'm not sure if it's safe to modify a hash in a block that's iterating over the key-value pairs in the hash. If it isn't, you can just save up all the keys to be deleted and delete them after iterating over the hash:
def wipeProduct(hash, nameToDelete)
keysToDelete = []
hash.each do |i|
key = i[0]
productName = i[1].first
keysToDelete << key if productName==nameToDelete
end
keysToDelete.each {|key| hash.delete(key) }
end
Here's the neater way to delete the "pants" entry:
def wipeProduct(hash, nameToDelete)
hash.reject!{|key,value| nameToDelete==value.first}
end
The reject! block gets to see each key-value pair, and when it returns true, the key-value supplied will be removed from the hash.
if a == 3 # Loop delete a Product
puts "Delete a Product by its key number"
d = gets
while products.has_key?(d)!= false do
puts "You have selected a key that is not currently in use"
d = gets
end
puts "You have deleted"
products.delete(d)
puts products
end
This is what I ended up doing had some trouble with the until loop so swapped for a while loop though becasue it wouldn't accept newly entered keys for some reason