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
Related
I get the following error in
Q1. Blackjack #score individual cards scores "two" as 2
Failure/Error: expect(score(["two"])).to eq(2)
ArgumentError:
wrong number of arguments (given 1, expected 0)
# ./questions/question_1.rb:78:in `score'
# ./spec/question_1_spec.rb:80:in `block (4 levels) in <top (required)>'
for every number
question_1.rb
My code:
def random_card
cards = ["two", "three", "four", "five", "six", "seven",
"eight", "nine", "ten",
"jack", "queen", "king", "ace"]
cards[rand(13)]
end
def move
#hand = Array.new
is_stick = false
while is_stick == false
puts "hit or stick"
call = gets.chomp
if call == "hit"
#hand.push(random_card())
score()
puts "Score so far: " + #total.to_s
elsif call == "stick"
is_stick = true
end
end
end
def score # Line 78
#total = 0
#hand.each do # Line 80
puts #values
#total += #values[card]
end
#total
end
def run_game
move()
if #total <= 21
puts "You scored: " + #total.to_s
else
puts "You busted with: " + #total.to_s
end
end
#values = {
"two" => 2,
"three" => 3,
"four" => 4,
"five" => 5,
"six" => 6,
"seven" => 7,
"eight" => 8,
"nine" => 9,
"ten" => 10,
"jack" => 10,
"queen" => 10,
"king" => 10,
"ace" => 11
}
This is the rspec file I'm using, whichhis is part of a challenge can't be changed:
require_relative "../questions/question_1"
require "mastery_answer_code_quality"
describe "Q1. Blackjack" do
describe "#random_card" do
it "returns all the cards in the suit" do
expect_any_instance_of(Object).to receive(:rand).with(13).and_return(0)
expect(random_card).to eq("two") # Does this for each card
end
end
describe "#move" do
context "user inputs hit" do
let (:user_input) { "hit\n" }
it 'returns `"hit"`' do
allow_any_instance_of(Object).to receive(:gets).and_return(user_input)
expect(move).to eq("hit")
end
end
context "user inputs stick" do
let (:user_input) { "stick\n" }
it 'returns `"stick"`' do
allow_any_instance_of(Object).to receive(:gets).and_return(user_input)
expect(move).to eq("stick")
end
end
context "user inputs blah and then a valid move" do
let (:user_input) { ["blah\n", "hit\n"] }
it 'returns valid move (`"hit"`)' do
allow_any_instance_of(Object).to receive(:gets).and_return(*user_input)
expect(move).to eq("hit")
end
end
end
describe "#score" do
describe "individual cards" do
it 'scores `"two"` as 2' do
expect(score(["two"])).to eq(2) # does this for each card
end
end
describe "adding up card scores" do
it 'scores `"two"`, `"jack"` and `"ace"` as 23' do
expect(score(["two", "jack", "ace"])).to eq(23)
end
end
end
describe "#run_game" do
describe "showing score so far as game is played" do
let (:user_input) { ["hit\n",
"hit\n",
"stick\n"] }
let (:expected_output) { ["Score so far: 7",
"Score so far: 17"].join("\n.*") }
it "`puts`es scores for two hits" do
srand(1)
set_user_input_and_check_expected_output
end
end
describe "`puts`ing outcome of game" do
context "player takes too many cards and busts" do
let (:user_input) { ["hit\n",
"hit\n",
"hit\n",
"stick\n"] }
let (:expected_output) { "You busted with: 28\n" }
it "`puts`es You busted with: 28" do
srand(1)
set_user_input_and_check_expected_output
end
end
context "player doesn't take too many cards" do
let (:user_input) { ["hit\n",
"hit\n",
"stick\n"] }
let (:expected_output) { "You scored: 17\n" }
it "`puts`es You scored: 17" do
srand(1)
set_user_input_and_check_expected_output
end
end
end
def set_user_input_and_check_expected_output
allow_any_instance_of(Object)
.to receive(:gets).and_return(*user_input)
expect { run_game }
.to output(/#{expected_output}/m).to_stdout
end
end
it "has acceptable code quality" do
code_quality = MasteryAnswerCodeQuality.build(__FILE__)
expect(code_quality.acceptable?).to(eq(true), code_quality.problems)
end
end
Score gets an array as parameter in the specs but your code does not pass it.
You need to use something like this:
def score(card) # Line 78
#total = 0
#hand.each do # Line 80
puts #values
#total += #values[card]
end
#total
end
undefined method '[]' for nil:NilClass means that an array or hash is nil, which you try to access using []. Unfortunately you don't share a stacktrace (error output containing the line numbers for called methods as you posted for the other ArgumentError). I think that neither #hand nor #values is defined somewhere for the test, see edit at the bottom.
Your code uses a lot of instance variables like #hand which are maybe not needed, I'd suggest to use local variables, instead of using those, and return values from methods which can then be used as parameters in other methods.
The specs only call the score method and nothing else .
Therefore neither #handnor #values instance variables defined outside the method are available. You'd need to define this in the specs, e.g. by using instance_variable_set(:#hand, ["three"]) or instance_variable_set(:#values, { "three" => 3 }) in the tests as shown above.
But as you can't change the predefined specs for the challenge this is not possible I guess. And this would of course subsequently lead to other errors, which need a fix as well. This is maybe the wrong approach though, take little steps to get things green, refactor later.
I have added the lines to set instance variables as a reference in my example below:
```
it 'scores `"three"` as 3' do
instance_variable_set(:#hand, ["three"])
instance_variable_set(:#values, { "three" => 3 })
expect(score(["three"])).to eq(3)
end
```
Here's my complete solution, which just runs green, I haven't done any refactorings.
def random_card
cards = ["two", "three", "four", "five", "six", "seven",
"eight", "nine", "ten", "jack", "queen", "king", "ace"]
cards[rand(13)]
end
def move
case gets.chomp
when "hit"
"hit"
when "stick"
"stick"
else
"hit"
end
end
def score(cards)
values = {
"two" => 2,
"three" => 3,
"four" => 4,
"five" => 5,
"six" => 6,
"seven" => 7,
"eight" => 8,
"nine" => 9,
"ten" => 10,
"jack" => 10,
"queen" => 10,
"king" => 10,
"ace" => 11
}
total = 0
current_result = 0
cards.each do |card|
total += values[card]
current_result = current_result + values[card]
puts "Score so far: #{current_result}"
end
total
end
def run_game
cards = []
total_score = 0
while move == 'hit' && total_score <= 21
cards.unshift random_card
total_score = score(cards)
end
if total_score <= 21
puts "You scored: #{total_score}"
else
puts "You busted with: #{total_score}"
end
end
I could use any improvements to improve my code. I think most of the method have the same layout but I am not getting the desired output, so any help would be great. If you want to see the exercise online, its called the Bachelor Nested Iteration. I really have no clue why I am not getting my desired output, to me my working out makes sense I guess.
for the get_first_name_of_season_winner method, no matter what arguments I pass through when I call it, I always get "Beth Smalls" as an output when it shouldn't be the case. If I pass "Season 29", the output should be "Ashley Yeats"
for the get_contestant_name method, it's the same thing. It always returns "Beth Smalls" no matter what occupation I pass through. For example if I call it like this
get_contestant_name(thebachelor, "Chiropractic Assistant" )
it should return "Becca Tilley" as an output but it doesn't.
for the count_contestant_by_hometown, it should return the number of contestants which are from the hometown thats passed in the method, however, no matter which argument I pass, I get the number 4 as an output.
for the get_occupation, it should return the name of the person corresponding to the hometown being passed in the method, but I always get "Beth Smalls" no matter which hometown I pass through it.
The final method, I have no idea how to do it. It takes in two arguments––the data hash and a string of a season. Iterate through the hash and return the average age of all of the contestants for that season.
thebachelor = {
"season 30": [
{
"name": "Beth Smalls",
"age": "26",
"hometown": "Great Falls, Virginia",
"occupation": "Nanny/Freelance Journalist",
"status": "Winner"
},
{
"name": "Becca Tilley",
"age": "27",
"hometown": "Shreveport, Louisiana",
"occupation": "Chiropractic Assistant",
"status": "Eliminated Week 8"
}
],
"season 29": [
{
"name": "Ashley Yeats",
"age": "24",
"hometown": "Denver, Colorado",
"occupation": "Dental Assitant",
"status": "Winner"
},
{
"name": "Sam Grover",
"age": "29",
"hometown": "New York, New York",
"occupation": "Entertainer",
"status": "Eliminated Week 6"
}
]
}
Now the methods. get_first_name_of_season_winner is
def get_first_name_of_season_winner(data, season)
#this method returns the first name of that seasons winner
#pass the season of the show, and then it returns only th FIRST NAME of the winner for that season
#iterate through the inital hash to access the season number
#then iterate through the array, to access the hash inside
#acess the "status" to get the output
data.each do |season, contestant_Data|
contestant_Data.each do |a|
a.each do |attribute, value|
if value == "Winner"
return a[:name]
end
end
end
end
end
get_first_name_of_season_winner(thebachelor, "season 29") #returns the full name of only "Beth Smalls"
get_contestant_name is:
def get_contestant_name(data, occupation) #this method takes in the data hash and an occupation string and returns the name of the woman who has that occupation
#iterate through the initial hash to access the seasons
#iterate through the seasons to access the arrays inside
#access the occupation element of the array
#return the person who has the occupation
data.each do |season, contestant_data|
contestant_data.each do |a|
a.each do |attribute, value|
if attribute == :occupation
return a[:name]
end
end
end
end
end
get_contestant_name(thebachelor, "Chiropractic Assistant" ) #returns the full name of only "Beth Smalls"
count_contestant_by_hometown is:
def count_contestant_by_hometown(data, hometown) #this method should return the number of contestants from the hometown passed
#include a counter variable
#iterate through the hash to access the seasons
#access the array
#access the hometown key in the hash
#keep count
counter = 0
data.each do |season, contestant_data|
contestant_data.each do |a|
a.each do |attribute, value|
if attribute == :hometown
counter += 1
end
end
end
end
return counter
end
count_contestant_by_hometown(thebachelor, "Denver, Colorado") #returns the number 4, I have no idea why
get_occupation is:
def get_occupation(data, hometown) #should return the occupation of of the first contestant who hails from the hometown
data.each do |season, contestant_data|
contestant_data.each do |a|
a.each do |attribute, value|
if attribute == :hometown
return a[:name]
end
end
end
end
end
get_occupation(thebachelor, "Denver, Colorado") #returns "Beth Smalls" when it should return "Ashley Yeats"
average_age_for_season is:
def average_age_for_season(data, season) #returns the average age of all contestants for that season
I think a big problem comes from the data you're passing in. Take for example, a working solution for your final issue.
To get the data for a single season, you can use:
def average_age_for(data, season)
contestants = data[season]
contestants.sum { |contestant| contestant[:age].to_f } / contestants.count
end
average_age_for(thebatchelor, :"season 30")
#=> 26.5
Note that you need to pass :"season 30", rather than simply "season 30". That's because your data is is using symbolised strings as keys, rather than just strings.
Replace your data's keys with strings:
thebachelor = {
"season 30" => [
{
"name" => "Beth Smalls",
"age" => "26",
"hometown" => "Great Falls, Virginia",
"occupation" => "Nanny/Freelance Journalist",
"status" => "Winner"
},
{
"name" => "Becca Tilley",
"age" => "27",
"hometown" => "Shreveport, Louisiana",
"occupation" => "Chiropractic Assistant",
"status" => "Eliminated Week 8"
}
],
"season 29" => [
{
"name" => "Ashley Yeats",
"age" => "24",
"hometown" => "Denver, Colorado",
"occupation" => "Dental Assitant",
"status" => "Winner"
},
{
"name" => "Sam Grover",
"age" => "29",
"hometown" => "New York, New York",
"occupation" => "Entertainer",
"status" => "Eliminated Week 6"
}
]
}
Then look for a string in the method:
def average_age_for(data, season)
contestants = data[season]
# vvvvvvv
contestants.sum { |contestant| contestant["age"].to_f } / contestants.count
# ^^^^^^^
end
And this takes shape.
You can then do:
1)
def get_first_name_of_season_winner(data, season)
data[season].detect { |contestant| contestant["status"] == "Winner" }["name"].split.first
end
get_first_name_of_season_winner(thebachelor, "season 29")
#=> "Ashley"
2)
def get_contestant_name(data, occupation)
data.values.flatten.detect { |contestant| contestant["occupation"] == occupation }
end
get_contestant_name(thebachelor, "Chiropractic Assistant")
#=> {"name"=>"Becca Tilley", "age"=>"27", "hometown"=>"Shreveport, Louisiana", "occupation"=>"Chiropractic Assistant", "status"=>"Eliminated Week 8"}
3)
def count_contestant_by_hometown(data, town)
data.values.flatten.select { |contestant| contestant["hometown"] == town }.count
end
count_contestant_by_hometown(thebachelor, "New York, New York")
#=> 1
4)
def get_occupation(data, hometown)
data.values.flatten.detect { |contestant| contestant["hometown"] == hometown }["occupation"]
end
get_occupation(thebachelor, "New York, New York")
#=> "Entertainer"
Generalised && Optimised Solution:
Following method will work to get whatever you need from your hash thebachelor,
def get_information(data, required, season, optional, hash= {})
data = season.nil? ? data.values.flatten : data[season]
selected = data.select { |x| (hash.inject(true) { |m, (k,v)| m &&= (x[k] == v) }) }
required_data = selected.map { |x| x[required] }
if optional == :average && required == :age
(required_data.map(&:to_i).sum / required_data.count.to_f).round(2)
else
(optional == :count) ? required_data.count : required_data
end
end
Data need to be provided as below,
data - hash input from which you want to retrieve data
required - output inner hash attribute you want in output.
season - If you want to retrieve data season specific, provide season, else nil to get from all season.
Optional - It can be set nil, unless you want count or average. Otherwise pass :count or :average as optional argument.
hash - filter option. If you want to filter inner data by attributes like :hometown or :status or :occupation. Just provide it. otherwise it will set empty hash.
See examples below,
# Get name of season winner for season 'season 29'
get_information(thebachelor, :name, :'season 29', nil, status: 'Winner')
# => ["Ashley Yeats"]
# Get name of all winners irrespective of season
get_information(thebachelor, :name, nil, nil, status: 'Winner')
# => ["Beth Smalls", "Ashley Yeats"]
# Get contestant name for occupation "Chiropractic Assistant"
get_information(thebachelor, :name, nil, nil, occupation: "Chiropractic Assistant")
# => ["Becca Tilley"]
# Count contestant by home town "Denver, Colorado"
get_information(thebachelor, :name, nil, :count, hometown: "Denver, Colorado")
# => 1
# Get occupation of contestant who hails from hometown "Denver, Colorado"
get_information(thebachelor, :occupation, nil, nil, hometown: "Denver, Colorado")
# => ["Dental Assitant"]
# Get Average age for season :"season 29"
get_information(thebachelor, :age, :"season 29", :average)
# => 26.5
This method provide moreover than you asked in your question.
First, I would reformat your data so it's easy to select/detect:
data = data.map { |key, value| value.transform_keys(&:to_sym).merge(season: key) }
so now it looks like
[{
season: "season 30",
name: "Beth Smalls",
age: "26",
hometown: "Great Falls, Virginia",
occupation: "Nanny/Freelance Journalist",
status: "Winner"
},...
]
Now it's much easier to filter and detect:
def get_first_name_of_season_winner(data, season)
p = ->(v) { v[:season] == season && v[:status] == 'Winner' }
data.detect(&p)[:name][/\w+/]
end
def get_contestant_name(data, occupation)
p = ->(v) { v[:occupation] == occupation }
data.detect(&p)[:name]
end
def count_contestant_by_hometown(data, hometown)
p = ->(v) { v[:hometown] = hometown }
data.select(&p).count
end
def get_occupation(data, hometown)
p = ->(v) { v[:hometown] = hometown }
data.detect(&p)[:occupation]
end
def average_age_for_season(data, season)
p = ->(v) { v[:season] = season }
ages = data.select(&p).map { |datum| datum[:age] }
ages.sum.fdiv(ages.count) unless ages.empty?
end
In general, all these problems are of two types:
1. Given an array of data, find all the items that satisfy a certain condition
2. Given an array of data, find the first item that satisfies a certain condition
And you always solve them with select/detect and a block/proc.
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}
I am trying to create a method for the classic bottles of beers output.
Given a number, #beers, I want to convert it to the word version of the number. I wanted to split the number into an array and then compare with a list of numbers to words:
class BeerSong
attr_accessor :beers
def initialize(beers)
if beers > 99
beers = 99
elsif beers < 1
beers = "Zero"
end
#beers = beers
end
def worded
num_name = {
90 => "Ninety", 80 => "Eighty", 70 => "Seventy",60 => "Sixty",
50 => "Fifty",40 => "Forty",30 => "Thirty",20 => "Twenty",
19 => "Nineteen", 18 => "Eighteen", 17 => "Seventeen", 16 => "Sixteen",
15 => "Fifteen",14 => "Fourteen", 13 =>"Thirteen", 12 => "Twelve",
11 => "Eleven", 10 => "Ten", 9 => "Nine",8 => "Eight", 7 => "Seven",
6 => "Six",5 => "Five",4 => "Four",3 => "Three",2 => "Two",1 => "One"
}
worded = ""
beers = #beers
split_beers = beers.to_s.split
num_name.each do |number, name|
split_number = number.to_s.split
if beers == number
worded << name
else
number > 19 && split_number[0].to_i == split_beers[0].to_i
worded << name
worded << "-"
end
end
num_name.each do |number, name|
if number < 10 && split_beers[1].to_i == number
worded << name
end
end
worded
end
def print_song
while #beers.to_i > 2
puts "#{worded} bottles of beer on the wall,"
puts "#{worded} bottles of beer,"
puts "Take one down, pass it around,"
#beers -= 1
puts "#{worded} bottles of beer on the wall.\n\n"
end
if #beers.to_i == 2
puts "#{worded} bottles of beer on the wall,"
puts "#{worded} bottles of beer,"
puts "Take one down, pass it around,"
#beers -= 1
puts "#{worded} bottle of beer on the wall.\n\n"
end
if #beers.to_i == 1
puts "#{worded} bottle of beer on the wall,"
puts "#{worded} bottle of beer,"
puts "Take one down, pass it around,"
puts "Zero bottles of beer on the wall.\n\n"
end
if #beers.to_i == 0
print ""
end
end
end
I am trying to compare the first digit to get the tens, then compare the second digit for the units separated by a hyphen.
Your lines:
split_beers = beers.to_s.split
split_number = number.to_s.split
assume an array as a result which is not the case because you don't provide a pattern to split on
so the default is used. See: http://ruby-doc.org/core-2.0.0/String.html#method-i-split
puts "10".to_s.split.inspect # gives ==> ["10"]
puts "10".to_s.split('',2).inspect # gives ==> ["1", "0"]
The last is what you want.
I want to open a text file with three lines
3 televisions at 722.49
1 carton of eggs at 14.99
2 pairs of shoes at 34.85
and turn it into this:
hash = {
"1"=>{:item=>"televisions", :price=>722.49, :quantity=>3},
"2"=>{:item=>"carton of eggs", :price=>14.99, :quantity=>1},
"3"=>{:item=>"pair of shoes", :price=>34.85, :quantity=>2}
}
I'm very stuck not sure how to go about doing this. Here's what I have so far:
f = File.open("order.txt", "r")
lines = f.readlines
h = {}
n = 1
while n < lines.size
lines.each do |line|
h["#{n}"] = {:quantity => line[line =~ /^[0-9]/]}
n+=1
end
end
No reason for anything this simple to look ugly!
h = {}
lines.each_with_index do |line, i|
quantity, item, price = line.match(/^(\d+) (.*) at (\d+\.\d+)$/).captures
h[i+1] = {quantity: quantity.to_i, item: item, price: price.to_f}
end
File.open("order.txt", "r") do |f|
n,h = 0,{}
f.each_line do |line|
n += 1
line =~ /(\d) (.*) at (\d*\.\d*)/
h[n.to_s] = { :quantity => $1.to_i, :item => $2, :price => $3 }
end
end
hash = File.readlines('/path/to/your/file.txt').each_with_index.with_object({}) do |(line, idx), h|
/(?<quantity>\d+)\s(?<item>.*)\sat\s(?<price>\d+(:?\.\d+)$)/ =~ line
h[(idx + 1).to_s] = {:item => item, :price => price.to_f, :quantity => quantity.to_i}
end
I don't know ruby so feel free to ignore my answer as I'm just making assumptions based on documentation, but I figured I'd provide a non-regex solution since it seems like overkill in a case like this.
I'd assume you can just use line.split(" ") and assign position [0] to quantity, position [-1] to price, and then assign item to [1..-3].join(" ")
Per the first ruby console I could find:
test = "3 televisions at 722.49"
foo = test.split(" ")
hash = {1=>{:item=>foo[1..-3].join(" "),:quantity=>foo[0], :price=>foo[-1]}}
=> {1=>{:item=>"televisions", :quantity=>"3", :price=>"722.49"}}