What are hashes in Ruby? - ruby

This code is in my book, there are 2 lines that I don't understand.
# library.rb
class Library
def initialize
#books ={} #creating a new hash
end
def addBook(book)
#books[book.category]||=[] #category is a symbol. I DONT UNDERSTAND THIS LINE
#books[book.category] << book #AND THIS ONE
end
end

#books ={}
Above line will create a new Hash
#books[book.category]||=[]
This means #books is a Hash and book.category is it's key and if that key not exist assign an empty array
||= -> So this means or-equals
|| means if #books has value it will not assign empty array, else it will put an empty array
So if #books[book.category] is a Array, in which you can insert as many category values
In this line we will insert book value into the hash, where book.category is the key
#books[book.category] << book
If you try this
#books ={}
#books[book.category].push(book) # This will give you the error `undefined method 'push' for nilclass`
because
#books[book.category].class will return NilClass

a ||= b means "if a is logically false (nil, false, undefined), assign b to it". You can read more about ||=, called "double pipe or equals" here.
So, in your code, #books[book.category] ||= [] means that an empty array ([]) will be assigned to books[book.category] if it's nil - otherwise it will remain intact.
Then, in the line below (#books[book.category] << book), book is appended (<<) to the array. Notice, however, that you have a syntax error (you open a { and you close with a [).

Related

Ruby get user input, add to array. Verify if user input matches array of objects

I have a small method that will be called on a initialized 'Order' class object, to get user input, asking them what item they would like to add from a menu to their order. The method is:
def add_to_order
#order = []
while true
puts "Item:"
#item = gets.chomp
break if #item.empty?
puts "Qty?"
#qty = gets.chomp
#order << [#item, #qty]
end
end
I have the menu stored in an array, as a 'hash' of objects, created previously from initializing new objects from a class called 'Dish'. These were added to an instance variable called #menu, which is:
#menu=
[[#<Dish:0x007fa1f8953b10 #dish={:name=>"Chicken Stew", :price=>4.5}>,
#<Dish:0x007fa1f883daf0 #dish={:name=>"Beef & Ale Pie", :price=>5.0}>,
#<Dish:0x007fa1f89d86f8 #dish={:name=>"Chicken & Leek Pie", :price=>4.0}>,
#<Dish:0x007fa1f8b5b048 #dish={:name=>"Vegetable Pie", :price=>3.5}>,
#<Dish:0x007fa1f8d03378 #dish={:name=>"Fish Pie", :price=>5.5}>,
#<Dish:0x007fa1f8df1c08 #dish={:name=>"Chips", :price=>2.0}>,
#<Dish:0x007fa1f9993a70 #dish={:name=>"Mushy Peas", :price=>1.5}>]]
My question is - when getting user input, how can I check whether or not the item they type in, is on the list contained in the #menu, when this #menu is not a normal array, but if more an 'array of object information? I tried the following but it did not work.
def add_to_order
#order = []
while true
raise 'ERROR!' if #menu[0].include?(#item)
.......
end
You could use find (or its alias detect) that takes an block:
#menu[0].find { |dish| dish.name == #item }
This would return the first dish from the menu which name matches #item or nil if there wasn't a matching dish.
Hint: You might want to consider storing your menu in a hash instead of an array, because a hash would allow faster lookups.

How can I create a method that takes a hash (with or without an assigned value) as an argument?

So I am working through test first and am a little stuck. Here is my code so far:
class Dictionary
attr_accessor :entries, :keywords, :item
def initialize
#entries = {}
end
def add(item)
item.each do |words, definition|
#entries[words] = definition
end
end
def keywords
#entries.keys
end
end#class
I am stuck at the rspec test right here:
it 'add keywords (without definition)' do
#d.add('fish')
#d.entries.should == {'fish' => nil}
#d.keywords.should == ['fish']
end
How can I switch my add method around to take either a key/value pair, or just a key with the value set to nil? The first test specifies that the hash is empty when it is created so I cant give it default values there.
One might check the type of the parameter passed to the add method. Whether it’s not an Enumerable, which is apparently a mixin included in Arrays, Hashes etc., just assign it’s value to nil:
def add(item)
case item
when Enumerable
item.each do |words, definition|
#entries[words] = definition
end
else
#entries[item] = nil
end
end
Please note that case uses “case equality” to check argument type.
If you are always passing Strings to the method, you could just have a default value for the second string... Something like the following:
def add(word, definition = nil)
#entries[word] = definition
end
So your code might look something like this:
class Dictionary
attr_accessor :entries, :keywords, :item
def initialize
#entries = {}
end
def add(word, definition = nil)
#entries[word] = definition
end
def keywords
#entries.keys
end
end#class
If you want multiple additions (i.e. add key: "word", with: "many", options: nil), that design might not work for you and you would need to create a solution that would work on the lines of what #mudasobwa suggested. Perhaps:
def add(word, definition = nil)
return #entries[word] = definition unless word.is_a?(Enumerable)
return #entries.update word if word.is_a?(Hash)
raise "What?!"
end
Update, as par request
I updated the method above to allow for words that aren't strings (as you pointed out).
When passing a hash to a method, it is considered as a single parameter.
Key => Value pairs are an implied hash, so when passing a hash to a method, the following are generally the same:
Hash.new.update key: :value
Hash.new.update({key: :value})
Consider the following:
def test(a,b = nil)
puts "a = #{a}"
puts "b = #{b}"
end
test "string"
# => a = string
# => b =
test "string", key: :value, key2: :value2
# => a = string
# => b = {:key=>:value, :key2=>:value2}
test key: :value, key2: :value2, "string"
# Wrong Ruby Syntax due to implied Hash, would raise exception:
# => SyntaxError: (irb):8: syntax error, unexpected '\n', expecting =>
test({key: :value, key2: :value2}, "string")
# correct syntax.
This is why, when you pass add 'fish' => 'aquatic', it's considered only one parameter, a hash - as opposed to add 'fish', 'aquatic' which passes two parameters to the method.
If your method must accept different types of parameters (strings, hashes, numerals, symbols, arrays), you will need to deal with each option in a different way.
This is why #mudasobwa suggested checking the first parameter's type. His solution is pretty decent.
My version is a bit shorter to code, but it runs on the same idea.
def add(word, definition = nil)
return #entries[word] = definition unless word.is_a?(Enumerable)
return #entries.update word if word.is_a?(Hash)
raise "What?!"
end

Subclassing Ruby Hash, object has no methods of Hash?

I'm creating a object of hash in order to write a little script that reads in a file a line at a time, and assigns arrays into my hash class. I get wildly different results depending if I subclass Hash or not, plus using super changes things which I don't' understand.
My main issue is that without subclassing hash ( < Hash) it works perfectly, but I get no methods of Hash (like to iterate over the keys and get things out of it.... Subclassing Hash lets me do those things, but it seems that only the last element of the hashed arrays is ever stored.... so any insight into how you get the methods of a subclass. The Dictionary class is a great example I found on this site, and does exactly what I want, so I'm trying to understand how to use it properly.
filename = 'inputfile.txt.'
# ??? class Dictionary < Hash
class Dictionary
def initialize()
#data = Hash.new { |hash, key| hash[key] = [] }
end
def [](key)
#data[key]
end
def []=(key,words)
#data[key] += [words].flatten
#data[key]
# super(key,words)
end
end
listData = Dictionary.new
File.open(filename, 'r').each_line do |line|
line = line.strip.split(/[^[:alpha:]|#|\.]/)
puts "LIST-> #{line[0]} SUB-> #{line[1]} "
listData[line[0]] = ("#{line[1]}")
end
puts '====================================='
puts listData.inspect
puts '====================================='
print listData.reduce('') {|s, (k, v)|
s << "The key is #{k} and the value is #{v}.\n"
}
If anyone understands what is going on here subclassing hash, and has some pointers, that would be excellent.
Running without explicit < Hash:
./list.rb:34:in `<main>': undefined method `reduce' for #<Dictionary:0x007fcf0a8879e0> (NoMethodError)
That is the typical error I see when I try and iterate in any way over my hash.
Here is a sample input file:
listA billg#microsoft.com
listA ed#apple.com
listA frank#lotus.com
listB evanwhite#go.com
listB joespink#go.com
listB fredgrey#stop.com
I can't reproduce your problem using your code:
d = Dictionary.new #=> #<Dictionary:0x007f903a1adef8 #data={}>
d[4] << 5 #=> [5]
d[5] << 6 #=> [6]
d #=> #<Dictionary:0x007f903a1adef8 #data={4=>[5], 5=>[6]}>
d.instance_variable_get(:#data) #=> {4=>[5], 5=>[6]}
But of course you won't get reduce if you don't subclass or include a class/module that defines it, or define it yourself!
The way you have implemented Dictionary is bound to have problems. You should call super instead of reimplementing wherever possible. For example, simply this works:
class Dictionary < Hash
def initialize
super { |hash, key| hash[key] = [] }
end
end
d = Dictionary.new #=> {}
d['answer'] << 42 #=> [42]
d['pi'] << 3.14 #=> [3.14
d #=> {"answer"=>[42], "pi"=>[3.14]}
If you want to reimplement how and where the internal hash is stored (i.e., using #data), you'd have to reimplement at least each (since that is what almost all Enumerable methods call to) and getters/setters. Not worth the effort when you can just change one method instead.
While Andrew Marshall's answer
already correct, You could also try this alternative below.
Going from your code, We could assume that you want to create an object that
act like a Hash, but with a little bit different behaviour. Hence our first
code will be like this.
class Dictionary < Hash
Assigning a new value to some key in the dictionary will be done differently
in here. From your example above, the assignment won't replace the previous
value with a new one, but instead push the new value to the previous or to
a new array that initialized with the new value if the key doesn't exist yet.
Here I use the << operator as the shorthand of push method for Array.
Also, the method return the value since it's what super do (see the if part)
def []=(key, value)
if self[key]
self[key] << value
return value # here we mimic what super do
else
super(key, [value])
end
end
The advantage of using our own class is we could add new method to the class
and it will be accessible to all of it instance. Hence we need not to
monkeypatch the Hash class that considered dangerous thing.
def size_of(key)
return self[key].size if self[key]
return 0 # the case for non existing key
end
Now, if we combine all above we will get this code
class Dictionary < Hash
def []=(key, value)
if self[key]
self[key] << value
return value
else
super(key, [value])
end
end
def size_of(key)
return self[key].size if self[key]
return 0 # the case for non existing key
end
end
player_emails = Dictionary.new
player_emails["SAO"] = "kirito#sao.com" # note no << operator needed here
player_emails["ALO"] = "lyfa#alo.com"
player_emails["SAO"] = "lizbeth#sao.com"
player_emails["SAO"] = "asuna#sao.com"
player_emails.size_of("SAO") #=> 3
player_emails.size_of("ALO") #=> 1
player_emails.size_of("GGO") #=> 0
p listData
#=> {"SAO" => ["kirito#sao.com", "lizbeth#sao.com", "asuna#sao.com"],
#=> "ALO" => ["lyfa#alo.com"] }
But, surely, the class definition could be replaced with this single line
player_emails = Hash.new { [] }
# note that we wont use
#
# player_emails[key] = value
#
# instead
#
# player_emails[key] << value
#
# Oh, if you consider the comment,
# it will no longer considered a single line
While the answer are finished, I wanna comment some of your example code:
filename = 'inputfile.txt.'
# Maybe it's better to use ARGF instead,
# so you could supply the filename in the command line
# and, is the filename ended with a dot? O.o;
File.open(filename, 'r').each_line do |line|
# This line open the file anonimously,
# then access each line of the file.
# Please correct me, Is the file will properly closed? I doubt no.
# Saver version:
File.open(filename, 'r') do |file|
file.each_line do |line|
# ...
end
end # the file will closed when we reach here
# ARGF version:
ARGF.each_line do |line|
# ...
end
# Inside the each_line block
line = line.strip.split(/[^[:alpha:]|#|\.]/)
# I don't know what do you mean by that line,
# but using that regex will result
#
# ["listA", "", "", "billg#microsoft.com"]
#
# Hence, your example will fail since
# line[0] == "listA" and line[1] == ""
# also note that your regex mean
#
# any character except:
# letters, '|', '#', '|', '\.'
#
# If you want to split over one or more
# whitespace characters use \s+ instead.
# Hence we could replace it with:
line = line.strip.split(/\s+/)
puts "LIST-> #{line[0]} SUB-> #{line[1]} "
# OK, Is this supposed to debug the line?
# Tips: the simplest way to debug is:
#
# p line
#
# that's all,
listData[line[0]] = ("#{line[1]}")
# why? using (), then "", then #{}
# I suggest:
listData[line[0]] = line[1]
# But to make more simple, actually you could do this instead
key, value = line.strip.split(/\s+/)
listData[key] = value
# Outside the block:
puts '====================================='
# OK, that's too loooooooooong...
puts '=' * 30
# or better assign it to a variable since you use it twice
a = '=' * 30
puts a
p listData # better way to debug
puts a
# next:
print listData.reduce('') { |s, (k, v)|
s << "The key is #{k} and the value is #{v}.\n"
}
# why using reduce?
# for debugging you could use `p listData` instead.
# but since you are printing it, why not iterate for
# each element then print each of that.
listData.each do |k, v|
puts "The key is #{k} and the value is #{v}."
end
OK, sorry for blabbering so much, Hope it help.

Ruby thinks element of hash is Integer

I have a function in Ruby:
def find_item(keyword)
potential = []
$items.each do |item|
puts item # <-- for debugging purposes
if item["name"].downcase== keyword
potential << item["name"].downcase
elsif item["keywords"].index(keyword) != nil
potential << item["name"].downcase
end
end
return potential
end
(The global variable $items is a Hash object that maps a few strings to some values that determine the properties of the item.)
When I puts the current item it is iterating over (the line with the comment does just that), it gives me:
{"name"=>"Thing1", "keywords"=>["thing", "green"], ...}
but when I try item["name"] on the next line (which should definitely return Thing1), it gives me:
C:/somepath/someprogram.rb:125:in '[]': can't convert String into Integer (TypeError)
if $items is a Hash, then $items.each do |item| will yield [key, value] pairs (Arrays) to the block. If you only want the values, use each_value.

How does one populate an array in Ruby?

Here is the code I'm working with:
class Trader
def initialize(ticker ="GLD")
#ticker = ticker
end
def yahoo_data(days=12)
require 'yahoofinance'
YahooFinance::get_historical_quotes_days( #ticker, days ) do |row|
puts "#{row.join(',')}" # this is where a solution is required
end
end
end
The yahoo_data method gets data from Yahoo Finance and puts the price history on the console. But instead of a simple puts that evaporates into the ether, how would you use the preceding code to populate an array that can be later manipulated as object.
Something along the lines of :
do |row| populate_an_array_method(row.join(',') end
If you don't give a block to get_historical_quotes_days, you'll get an array back. You can then use map on that to get an array of the results of join.
In general since ruby 1.8.7 most iterator methods will return an enumerable when they're called without a block. So if foo.bar {|x| puts x} would print the values 1,2,3 then enum = foo.bar will return an enumerable containing the values 1,2,3. And if you do arr = foo.bar.to_a, you'll get the array [1,2,3].
If have an iterator method, which does not do this (from some library perhaps, which does not adhere to this convention), you can use foo.enum_for(:bar) to get an enumerable which contains all the values yielded by bar.
So hypothetically, if get_historical_quotes_days did not already return an array, you could use YahooFinance.enum_for(:get_historical_quotes_days).map {|row| row.join(",") } to get what you want.

Resources