Ruby Hash: Subtracting quantities - ruby

I am trying to create a response that will allow the user to enter the key and value of the inventory to subtract from the inventory that already exists. So if there are 10 apples at the start of the inventory and I respond saying I'm selling 7 apples the remainder in the hash should be represented as 3 apples left.
I am a beginner and a bit lost so any explanation would be helpful. Thank you!
#inventory = {"apples" => 10, "bananas" => 10, "crackers" => 10, "breads" => 10}
def sell_inventory
puts "What food are we selling today?"
product = gets.chomp.downcase
puts "How many #{product} are we selling today?"
quantity = gets.to_i
#inventory.delete(product, quantity)
end

#inventory = { "apples" => 10, "bananas" => 10, "crackers" => 10, "breads" => 10 }
def sell_inventory
puts "What food are we selling today?"
product = gets.chomp.downcase
puts "How many #{product} are we selling today?"
quantity = gets.to_i
if #inventory.key?(product)
#inventory[product] -= quantity
#inventory[product] = 0 if #inventory[product] < 0
else
puts "No inventory product: #{product}"
end
end
At first I check whether product is an inventory product with Hash#key?. Otherwise I print an error.
Then I subtract the quantity. Last I check the total quantity can't be negative.
Hash.delete, which you tried, would remove the key-value-pair from the hash and returns the value. An example:
#inventory.delete("apples")
# => 8
#inventory
# => {"bananas"=>10, "crackers"=>10, "breads"=>10}

Related

Ruby: Food Menu that calculates and displays balance of your choice

Below is a method that displays a menu using a hash. I can't figure out how to use the input to then calculate and display the balance of the users choice/s. I'm also struggling with the concept of class initialization and how that could help here. Any help very welcome!
def product_menu
product_menu_hash = {
"Coffee" => 4.00,
"Soft Drink" => 4.00,
"Sandwich (Meat)" => 9.50,
"Sandwich (Veg)" => 8.00,
"Coffee Maker" => 50.00,
"Bag of Coffee (250g)" => 13.25,
}
puts "COFFEE SHOP"
product_menu_hash.each_with_index do |(item, price), index|
puts "#{index + 1} #{item} = $#{price}"
input = gets.chomp
end
end
If users enter quantities of items as integers
tot = 0
product_menu_hash = {
"Coffee" => 4.00,
"Soft Drink" => 4.00,
"Sandwich (Meat)" => 9.50,
"Sandwich (Veg)" => 8.00,
"Coffee Maker" => 50.00,
"Bag of Coffee (250g)" => 13.25,
}
puts "COFFEE SHOP"
product_menu_hash.each_with_index do |(item, price), index|
puts "#{index + 1} #{item} = $#{price}"
input = gets.chomp
totpar = input.to_i * price
puts totpar
tot += totpar
end
puts 'total: ', tot
The user is the shop assistant and I imagined them just entering the
menu choice e.g. 1 for Coffee or 2 for soft drink etc then the price
being calculated from there. Is there a way to do this?
Based on your last comment, a second answer would be
tot = 0
product_menu_hash = {
"Coffee" => 4.00,
"Soft Drink" => 4.00,
"Sandwich (Meat)" => 9.50,
"Sandwich (Veg)" => 8.00,
"Coffee Maker" => 50.00,
"Bag of Coffee (250g)" => 13.25,
}
product_choice = {}
product_menu_hash.each_with_index do |(item, price), index|
product_choice[index+1]=item
puts "#{index + 1} #{item} = $#{price}"
end
puts 'x to exit, p to print total '
choice = gets.chomp
while choice != 'x' do
if choice == 'p' then
puts 'total: ', tot
else
puts product_choice[choice.to_i],product_menu_hash[product_choice[choice.to_i]]
tot += product_menu_hash[product_choice[choice.to_i]]
end
choice = gets.chomp
end

How to get a default value with hashes in ruby

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.

Student Marks Hash

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.

reset a count in ruby when putting to screen from parallel arrays

This is home work so I would prefer not to put up my code. I have 2 parallel arrays, 1.names 2. ages. The idea is to puts all ages less than 21. I can do this. The problem is that when I puts "#{count}. #{names[count]}, #{ages[count]}" <---The beginning count prints out the index number or position of element in array. Obviously what I want is for it to start at 1. if there are three names...
name, age
name, age
name, age
NOT
5, name, age
6, name, age
I am using a while loop with an if statement. I don't need code, just would like some feedback to trigger more ideas. Thanks for your time, much appreciated.
names[name1, name2, name3]
ages[age1, age2, age3]
#view all younger than 21
count = 0
while count < names.length
if ages[count] < 21
puts "#{count}. #{names[count]}, #{ages[count]}" #works
end
count += 1
end
pause
You shouldn't have "parallel arrays" in the first place! Data that belongs together should be manipulated together, not separately.
Instead of something like
names = %w[john bob alice liz]
ages = [16, 22, 18, 23 ]
You could, for example, have a map (called Hash in Ruby):
people = { 'john' => 16, 'bob' => 22, 'alice' => 18, 'liz' => 23 }
Then you would have something like:
puts people.select {|_name, age| age > 21 }.map.
with_index(1) {|(name, age), i| "#{i}. #{name}, #{age}" }
# 1. bob, 22
# 2. liz, 23
If you have no control over the creation of those parallel arrays, then it is still better to convert them to a sensible data structure first, and avoid the pain of having to juggle them in your algorithm:
people = Hash[names.zip(ages)]
Even better yet: you should have Person objects. After all, Ruby is object-oriented, not array-oriented or hash-oriented:
class Person < Struct.new(:name, :age)
def to_s
"#{name}, #{age}"
end
def adult?
age > 21
end
end
people = [
Person.new('john', 16),
Person.new('bob', 22),
Person.new('alice', 18),
Person.new('liz', 23)]
puts people.select(&:adult?).map.with_index(1) {|p, i| "#{i}. #{p}" }
Again, if you don't have control of the creation of those two parallel arrays, you can still convert them first:
people = names.zip(ages).map {|name, age| Person.new(name, age) }

How to store and retrieve values using Ruby?

I am trying to build a "train game" based loosely on the old video game "Drug Wars." I am currently working my way through LRTHW, and I believe that I should be using OOP, but I'm not to that lesson yet.
The premise is that you have a set number of cars on your train and you can see what products are for sale in other cities (no limit on the amount you can buy or sale presuming you can fit them in your train). This code isn't complete, but I'm wondering if I'm even approaching this half way sanely in regard to creating and accessing the product prices in a reasonable manner.
#Initializing variables. Current_location should be changed to random
#in the future.
current_location = 'omaha'
train = []
new_york = []
chicago = []
omaha = []
dallas = []
seattle = []
def prompt()
print "> "
end
#Here is the selection menu. It is possible to exploit this and
#buy, sell and move all within the same turn.
#There needs to be a "safe selection" so that once you have moved you
#can't move again, but you can get info, buy and sell
#as many times as you would like.
def selection()
puts "Do you want to travel, buy, sell or get info?"
prompt; selection = gets.chomp
if selection.include? "travel"
puts "Where would you like to travel?"
prompt; city = gets.chomp
return 'city', city
elsif selection.include? "buy"
puts "Current Prices Are:"
puts "What would you like to Buy?"
elsif selection.include? "sell"
puts "Current Prices Are:"
puts "What would you like to sell?"
elsif selection.include? "info"
puts "What city or train would you like info on?"
else
puts "Would you like to exit selection or start selection again?"
end
end
#This generates a new cost for each good at the start of each turn.
def generate_costs(new_york, chicago, omaha, dallas, seattle)
new_york[0] = rand(10)
new_york[1] = rand(10) + 25
new_york[2] = rand(5) + 10
omaha[0] = rand(10)
omaha[1] = rand(10) + 25
omaha[2] = rand(5) + 10
chicago[0] = rand(25) + 5
chicago[1] = rand(5) + 10
chicago[2] = rand(4)
dallas[0] = rand(6) + 11
dallas[1] = rand(3) + 10
dallas[2] = rand(8)
seattle[0] = rand(6)
seattle[1] = rand(10) + 24
seattle[2] = rand(14) + 13
return new_york, chicago, omaha, dallas, seattle
end
# This is my main() loop. It drives the game forward.
for i in (0..5)
new_york, chicago, omaha, dallas, seattle = generate_costs(new_york, chicago, omaha, dallas, seattle)
turns = 5 - i
puts "You are currently in #{current_location}. You have #{turns} remaining."
puts "{ ___________________________ }"
#Code Here evaluates and accesses pricing based on current_location.
#Is this the correct way to do this?
fish = eval("#{current_location}[0]")
coal = eval("#{current_location}[1]")
cattle = eval("#{current_location}[2]")
puts "Fish is worth #{fish}"
puts "Coal is worth #{coal}"
puts "Cattle is worth #{cattle}"
puts "{ ___________________________ }"
change, value = selection()
if change == 'city'
current_location = value
elsif change == 'buy'
puts 'So you want to buy?'
else
puts "I don't understand what you want to do"
end
end
eval is a nasty way of accessing data ( When is `eval` in Ruby justified? ). You should consider moving things into an object.
I have improved the code slightly, storing the cities in a hash, which gets rid of the evals. I have stubbed out the generate_costs logic but you can assign it by doing:
cities[:new_york][0] = rand(10)
Ideally, the code should be re-written in an object-oriented syntax. If I get some time then I'll knock up an example for you.
Here is the code:
#Initializing variables. Current_location should be changed to random
#in the future.
current_location = :omaha
train = []
cities = {
:new_york => [],
:chicago => [],
:omaha => [],
:dallas => [],
:seattle => []
}
def prompt()
print "> "
end
#Here is the selection menu. It is possible to exploit this and
#buy, sell and move all within the same turn.
#There needs to be a "safe selection" so that once you have moved you
#can't move again, but you can get info, buy and sell
#as many times as you would like.
def selection()
puts "Do you want to travel, buy, sell or get info?"
prompt; selection = gets.chomp
if selection.include? "travel"
puts "Where would you like to travel?"
prompt; city = gets.chomp
return 'city', city
elsif selection.include? "buy"
puts "Current Prices Are:"
puts "What would you like to Buy?"
elsif selection.include? "sell"
puts "Current Prices Are:"
puts "What would you like to sell?"
elsif selection.include? "info"
puts "What city or train would you like info on?"
else
puts "Would you like to exit selection or start selection again?"
end
end
#This generates a new cost for each good at the start of each turn.
def generate_costs(cities)
cities.each do |key,city|
0.upto(2) do |i|
city[i] = rand(10)
end
end
end
# This is my main() loop. It drives the game forward.
for i in (0..5)
generate_costs(cities)
turns = 5 - i
puts "You are currently in #{current_location}. You have #{turns} remaining."
p cities
puts "{ ___________________________ }"
fish = cities[current_location][0]
coal = cities[current_location][1]
cattle = cities[current_location][2]
puts "Fish is worth #{fish}"
puts "Coal is worth #{coal}"
puts "Cattle is worth #{cattle}"
puts "{ ___________________________ }"
change, value = selection()
if change == 'city'
current_location = value
elsif change == 'buy'
puts 'So you want to buy?'
else
puts "I don't understand what you want to do"
end
end

Resources