Why I can't keep the hash updated in ruby? - ruby

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)

Related

putting hashes values into another hash

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}

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.

Ruby returning from inside a method but outside a loop

Try running this code.
When method1 is run, the hash is returned twice, meaning the hash is returned and printed as intended by the 'puts method1().inspect' command.
When method2 is run, and the loop is exited second time-around, by typing "no" or "n", a bunch of seemingly random numbers are printed, instead of a lovely hash. Why is this????
def method1
loop do
print "Item name: "
item_name = gets.chomp
print "How much? "
quantity = gets.chomp.to_i
hash = {"Item"=> item_name, "quantity"=> quantity}
puts hash.inspect
return hash
end
end
puts method1().inspect
def method2
loop do
print "Item name: "
item_name = gets.chomp
print "How much? "
quantity = gets.chomp.to_i
hash = {"Item"=> item_name, "quantity"=> quantity}
puts hash.inspect
print "Add another item? "
answer = gets.chomp.downcase
break if (answer == "no") || (answer == "n")
end
return hash
end
puts method2().inspect
You've accidentally discovered the Object#hash method. You don't declare hash outside the loop, so it is not in scope to return at the end. Instead, it returns the hash() method value, which is a big negative number for that instance.
Fire up irb, and just type hash, you'll see the same thing:
(505)⚡️ irb
2.1.2 :001 > hash
=> -603961634927157790
So instead, try this:
def method2
hash = {}
loop do
# ...
Also be aware you aren't adding to the hash, you're re-creating it every time.
In method2, you're trying to return something (hash) that has gone out of scope.
In method1, you're still inside the loop where hash was defined when you return it. In method2, you're outside of the scope where hash was defined, so it has an undocumented outcome. Redefined method2 like so:
def method2
hash = nil
loop do
print "Item name: "
item_name = gets.chomp
print "How much? "
quantity = gets.chomp.to_i
hash = {"Item"=> item_name, "quantity"=> quantity}
puts hash.inspect
print "Add another item? "
answer = gets.chomp.downcase
break if (answer == "no") || (answer == "n")
end
return hash
end
Now, even though hash was initially set to nil, its scope includes the entire method, and the value will be retained.

Ruby grocery list program

I am currently learning Ruby and I'm trying to write a simple Ruby grocery_list method. Here are the instructions:
We want to write a program to help keep track of a grocery list. It takes a grocery item (like "eggs") as an argument, and returns the grocery list (that is, the item names with the quantities of each item). If you pass the same argument twice, it should increment the quantity.
def grocery_list(item)
array = []
quantity = 1
array.each {|x| quantity += x }
array << "#{quantity}" + " #{item}"
end
puts grocery_list("eggs", "eggs")
so I'm trying to figure out here how to return "2 eggs" by passing eggs twice
To help you count the different items you can use as Hash. A Hash is similar to an Array, but with Strings instead of Integers als an Index:
a = Array.new
a[0] = "this"
a[1] = "that"
h = Hash.new
h["sonja"] = "asecret"
h["brad"] = "beer"
In this example the Hash might be used for storing passwords for users. But for your
example you need a hash for counting. Calling grocery_list("eggs", "beer", "milk", "eggs")
should lead to the following commands being executed:
h = Hash.new(0) # empty hash {} created, 0 will be default value
h["eggs"] += 1 # h is now {"eggs"=>1}
h["beer"] += 1 # {"eggs"=>1, "beer"=>1}
h["milk"] += 1 # {"eggs"=>1, "beer"=>1, "milk"=>1}
h["eggs"] += 1 # {"eggs"=>2, "beer"=>1, "milk"=>1}
You can work through all the keys and values of a Hash with the each-loop:
h.each{|key, value| .... }
and build up the string we need as a result, adding
the number of items if needed, and the name of the item.
Inside the loop we always add a comma and a blank at the end.
This is not needed for the last element, so after the
loop is done we are left with
"2 eggs, beer, milk, "
To get rid of the last comma and blank we can use chop!, which "chops off"
one character at the end of a string:
output.chop!.chop!
One more thing is needed to get the complete implementation of your grocery_list:
you specified that the function should be called like so:
puts grocery_list("eggs", "beer", "milk","eggs")
So the grocery_list function does not know how many arguments it's getting. We can handle
this by specifying one argument with a star in front, then this argument will
be an array containing all the arguments:
def grocery_list(*items)
# items is an array
end
So here it is: I did your homework for you and implemented grocery_list.
I hope you actually go to the trouble of understanding the implementation,
and don't just copy-and-paste it.
def grocery_list(*items)
hash = Hash.new(0)
items.each {|x| hash[x] += 1}
output = ""
hash.each do |item,number|
if number > 1 then
output += "#{number} "
end
output += "#{item}, "
end
output.chop!.chop!
return output
end
puts grocery_list("eggs", "beer", "milk","eggs")
# output: 2 eggs, beer, milk
def grocery_list(*item)
item.group_by{|i| i}
end
p grocery_list("eggs", "eggs","meat")
#=> {"eggs"=>["eggs", "eggs"], "meat"=>["meat"]}
def grocery_list(*item)
item.group_by{|i| i}.flat_map{|k,v| [k,v.length]}
end
p grocery_list("eggs", "eggs","meat")
#=>["eggs", 2, "meat", 1]
def grocery_list(*item)
Hash[*item.group_by{|i| i}.flat_map{|k,v| [k,v.length]}]
end
grocery_list("eggs", "eggs","meat")
#=> {"eggs"=>2, "meat"=>1}
grocery_list("eggs", "eggs","meat","apple","apple","apple")
#=> {"eggs"=>2, "meat"=>1, "apple"=>3}
or as #Lee said:
def grocery_list(*item)
item.each_with_object(Hash.new(0)) {|a, h| h[a] += 1 }
end
grocery_list("eggs", "eggs","meat","apple","apple","apple")
#=> {"eggs"=>2, "meat"=>1, "apple"=>3}
Use a Hash Instead of an Array
When you want an easy want to count things, you can use a hash key to hold the name of the thing you want to count, and the value of that key is the quantity. For example:
#!/usr/bin/env ruby
class GroceryList
attr_reader :list
def initialize
# Specify hash with default quantity of zero.
#list = Hash.new(0)
end
# Increment the quantity of each item in the #list, using the name of the item
# as a hash key.
def add_to_list(*items)
items.each { |item| #list[item] += 1 }
#list
end
end
if $0 == __FILE__
groceries = GroceryList.new
groceries.add_to_list('eggs', 'eggs')
puts 'Grocery list correctly contains 2 eggs.' if groceries.list['eggs'] == 2
end
Here's a more verbose, but perhaps more readable solutions to your challenge.
def grocery_list(*items) # Notice the asterisk in front of items. It means "put all the arguments into an array called items"
my_grocery_hash = {} # Creates an empty hash
items.each do |item| # Loops over the argument array and passes each argument into the loop as item.
if my_grocery_hash[item].nil? # Returns true of the item is not a present key in the hash...
my_grocery_hash[item] = 1 # Adds the key and sets the value to 1.
else
my_grocery_hash[item] = my_grocery_hash[item] + 1 # Increments the value by one.
end
end
my_grocery_hash # Returns a hash object with the grocery name as the key and the number of occurences as the value.
end
This will create an empty hash (called dictionaries or maps in other languages) where each grocery is added as a key with the value set to one. In case the same grocery appears multiple times as a parameter to your method, the value is incremented.
If you want to create a text string and return that instead of the hash object and you can do like this after the iteration:
grocery_list_string = "" # Creates an empty string
my_grocery_hash.each do |key, value| # Loops over the hash object and passes two local variables into the loop with the current entry. Key being the name of the grocery and value being the amount.
grocery_list_string << "#{value} units of #{key}\n" # Appends the grocery_list_string. Uses string interpolation, so #{value} becomes 3 and #{key} becomes eggs. The remaining \n is a newline character.
end
return grocery_list_string # Explicitly declares the return value. You can ommit return.
Updated answer to comment:
If you use the first method without adding the hash iteration you will get a hash object back which can be used to look up the amount like this.
my_hash_with_grocery_count = grocery_list("Lemonade", "Milk", "Eggs", "Lemonade", "Lemonade")
my_hash_with_grocery_count["Milk"]
--> 1
my_hash_with_grocery_count["Lemonade"]
--> 3
Enumerable#each_with_object can be useful for things like this:
def list_to_hash(*items)
items.each_with_object(Hash.new(0)) { |item, list| list[item] += 1 }
end
def hash_to_grocery_list_string(hash)
hash.each_with_object([]) do |(item, number), result|
result << (number > 1 ? "#{number} #{item}" : item)
end.join(', ')
end
def grocery_list(*items)
hash_to_grocery_list_string(list_to_hash(*items))
end
p grocery_list('eggs', 'eggs', 'bread', 'milk', 'eggs')
# => "3 eggs, bread, milk"
It iterates an array or hash to enable building another object in a convenient way. The list_to_hash method uses it to build a hash from the items array (the splat operator converts the method arguments to an array); the hash is created so that each value is initialized to 0. The hash_to_grocery_list_string method uses it to build an array of strings that is joined to a comma-separated string.

hash methods argument values

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

Resources