Learning Ruby here and this is my first endeavor into OOP and below is my complete code which makes a hash class. I'm having trouble understanding what is happening behind the scenes in the union method. When I change self.to_a.each { |key| joined_set.insert(key) } to #store.to_a.each { |key| joined_set.insert(key) } the hash joined_set becomes an array of arrays containing the keys and values of #store while it just contains the keys if I use just self and not #store. How does this discrepancy arise? Is self not equal to the instance variable?
class MyHashSet
def initialize
#store = {}
end
def insert(el)
#store[el] = true
end
def include?(el)
return true if #store[el]
false
end
def delete(el)
if #store[el]
#store.delete(el)
return true
else
return false
end
end
def to_a
#store.keys
end
def union(set2)
joined_set = self.class.new
self.to_a.each { |key| joined_set.insert(key) }
set2.to_a.each { |key| joined_set.insert(key) }
joined_set
end
end
The more specific reason you're getting different results is that self.to_a is equal to #store.keys. Why? because that's how you defined to_a:
def to_a
#store.keys
end
#store.keys and #store.to_a are very different from each other; #store is a ruby Hash, and Hash#to_a returns an array of arrays, with each subarray being a key-value pair, like [[key1, value1], [key2, value2]]; Hash#keys, on the other hand, just returns an array of keys.
self is not equal to the instance variable.
self is equal to the current object, which, in this case, would be the current instance of the MyHashSet class.
so #store is an attribute of self in this case.
if you had an attr_accessor for #store, then #store would be equal to self.store
Related
I have a 2D hash table of a class type that I made. I'm trying to pop a value out of that hash table and assign it to a variable:
foo = #bar[0].pop()
However, foo is not being populated with the object. I've made sure #bar[0].pop actually pops the right object as my original implementation is in a Class method with this code:
#test.each {|x| return #bar[x].pop() if something == x }
and the method returns the correct object. However, I'm trying to do more actions within the method which requires me to hold the popped object in a variable, and I'm not sure how to do that by assignment or if my syntax is just incorrect.
EDIT: Here's my actual code (stuff that's pertinent)
class Candy
attr_accessor :name, :shelved
def initialize
#name = ""
#shelved = 0
end
end
class Shelf
attr_accessor :candy_hash, :total_candies, :shelf_full, :num_candies,
:name_array
def initialize()
#candy_hash = Hash.new{ |h, k| h[k] = []}
#total_candies = 0
#shelf_full = 0
#name_array = Array.new
end
end
class Shop
attr_accessor :shelves, :unshelved_hash, :unshelved_names
def initialize
#shelves = []
#unshelved_hash = Hash.new{ |h, k| h[k] = []}
#unshelved_names = []
end
def push_new_unshelved(candy)
#unshelved_names << candy.name
#unshelved_hash[#unshelved_names.last].push(candy)
end
def push_existing_unshelved(candy)
#unshelved_names.each{|x| #unshelved_hash[x].push(candy) && break if x == candy.name}
end
def receive_candy(candy)
check = self.check_if_exists(candy)
if check == 0
self.push_new_unshelved(candy)
else
self.push_existing_unshelved(candy)
end
end
def get_hash_key(candy_name)
#unshelved_names.each{|x| return x if candy_name == x}
end
def get_unshelved_candy(candy_name)
candy = #unshelved_hash[self.get_hash_key(candy_name)].pop()
return candy
# problem is here, candy is never populated
# I'm gonna do more in this method but this is the important part
# working solution right now: return #unshelved_hash[self.get_hash_key(candy_name)].pop()
end
def shelve_single(candy_name)
candy = self.get_unshelved_candy(candy_name)
#shelves[self.find_empty_shelf].add_candy(candy)
end
end
shop1.receive_candy(Candy.new("snickers"))
shop1.shelve_single("snickers")
foo is not being populated with the object.
Assignments to local variables cannot silently fail. foo = #bar[0].pop() will assign the result from #bar[0].pop() to foo. (or raise an exception if #bar[0].pop() fails)
If by "not being populated" you mean a value of nil then it's because #bar[0].pop() returned nil. Most likely this is because #bar[0] is an empty array. (maybe because it wasn't populated in the first place)
Here's my actual code (stuff that's pertinent)
At first glance, your code seems to be unnecessarily complex. When working with defaults-based hashes like #candy_hash = Hash.new { |h, k| h[k] = [] } you can access the underlying arrays simply via their key, e.g. #candy_hash["snickers"]. And since the hash automatically creates an empty array as needed, there's no need to check whether it exists. You can just push elements:
candies = Hash.new { |h, k| h[k] = [] }
candies["snickers"].push(Candy.new("snickers"))
candies["snickers"].push(Candy.new("snickers"))
candies["kit kat"].push(Candy.new("kit kat"))
candies
#=> {
# "snickers"=>[
# #<Candy:0x00007fad03046818 #name="snickers">,
# #<Candy:0x00007fad030467c8 #name="snickers">
# ],
# "kit kat"=>[
# #<Candy:0x00007f981303a658 #name="kit kat">
# ]
# }
candies.pop("snickers")
#=> #<Candy:0x00007fad030467c8 #name="snickers">
And to get the candy names, you could simply inspect the hash's keys: (instead of maintaining a separate array)
candies.keys
#=> ["snickers", "kit kat"]
Regarding your problem, let's start by using an incomplete but working test implementation for Candy and Shelf:
class Candy
attr_reader :name
def initialize(name)
#name = name
end
end
class Shelf
attr_reader :candy_hash
def initialize
#candy_hash = Hash.new { |h, k| h[k] = [] }
end
def add_candy(candy)
candy_hash[candy.name].push(candy)
end
def remove_candy(candy_name)
candy_hash[candy_name].pop
end
end
Based on this, we can implement Shop as follows:
class Shop
attr_reader :shelves, :unshelved_hash
def initialize
#shelves = []
#unshelved_hash = Hash.new { |h, k| h[k] = [] }
end
def receive_candy(candy)
#unshelved_hash[candy.name].push(candy)
end
def get_unshelved_candy(candy_name)
#unshelved_hash[candy_name].pop
end
def find_empty_shelf
#shelves.first # replace with actual logic
end
def shelve_single(candy_name)
candy = get_unshelved_candy(candy_name)
empty_shelf = find_empty_shelf
empty_shelf.add_candy(candy)
end
end
Example: (I've omitted the object ids)
shop = Shop.new
shop.shelves << Shelf.new
shop.receive_candy(Candy.new("snickers"))
shop.unshelved_hash #=> {"snickers"=>[#<Candy #name="snickers">]}
shop.shelves #=> [#<Shelf #candy_hash={}>]
shop.shelve_single("snickers")
shop.unshelved_hash #=> {"snickers"=>[]}
shop.shelves #=> [#<Shelf #candy_hash={"snickers"=>[#<Candy #name="snickers">]}>]
You could also replace unshelved_hash by an instance of Shelf in order to reuse its code:
class Shop
attr_reader :shelves, :unshelved
def initialize
#shelves = []
#unshelved = Shelf.new
end
def receive_candy(candy)
#unshelved.add_candy(candy)
end
def get_unshelved_candy(candy_name)
#unshelved.remove_candy(candy_name)
end
# ...
end
The Shop implementation(s) above aren't complete but they should get you started.
A common pitfall with hashes that assign values lazily is that they don't handle deletion automatically. You might have noticed that after shelving our test Snickers, the unshelved_hash still contains "snickers"=>[]. To remove it completely, you can delete the corresponding key after pop-ing off the array's last item:
def remove_candy(candy_name)
candy = candy_hash[candy_name].pop
candy_hash.delete(candy_name) if candy_hash[candy_name].empty?
candy
end
Note: This answer is based on quite a few assumptions to fill-up the gaps in your question.
You need to use break instead of return if you want to assign the value to a variable within a method. return exits the whole method while break only interrupts the current iterator (each in your case):
def my_method
my_value = #test.each {|x| break #bar[x].pop() if something == x }
...
end
I use blocks to create values like so
some_block = BlockClass.new {|b|
b.one = 1
b.two = 2
b.three = 3
}
Here is BlockClass
class BlockClass
attr_accessor :one
attr_accessor :two
attr_accessor :three
def initialize
yield self if block_given?
end
end
I need a way to iterate over some_block, and print all the value in the block without having to do
puts some_block.one
puts some_block.two
puts some_block.three
Is there a way to iterate over the values in the block?
First of all, the b parameter in the block is nil, so you will get a
NoMethodError: undefined method `one=' for nil:NilClass`
To fix this, you can change yield if block_given? to yield(self) if block_given?, which will pass self as the first parameter to the block.
If you want the b.one = ..., b.two = ... syntax, you should use an OpenStruct:
require 'ostruct'
class BlockClass < OpenStruct
def initialize
super
yield(self) if block_given?
end
end
You can get a dump of the internal Hash by calling marshal_dump:
some_block = BlockClass.new {|b|
b.one = 1
b.two = 2
b.three = 3
}
some_block.marshal_dump # => {:one=>1, :two=>2, :three=>3}
You can then iterate over the values:
some_block.marshal_dump.each_pair do |k, v|
puts "the value of #{k} is #{v}"
end
Your block takes 1 parameter, b, but your yield statement doesn't pass anything in. Perhaps you mean, yield self if block_given??
Also, if you want to "iterate", you'll need an enumerable collection of something, like an Array or Hash. As is, one, two, and three are totally unrelated accessors to your BlockClass.
You could iterate over all methods of BlockClass:
(some_block.methods).each do |method_name|
puts some_block.send(method_name)
end
But that doesn't sound like what you're looking for. Perhaps Initialize a Ruby class from an arbitrary hash, but only keys with matching accessors might help?
class New_Class
def initialize
#array = ['1A','2A','1B','2C']
end
def remove_letters
#array.map do |rmv|
rmv.sub /([0-9])([A-Z])/, '\1'
end
end
def show_array
#array.each do |element|
puts element
end
end
end
array = New_Class.new
new_array = array.remove_letters
puts new_array.class #=> Array
new_array.each do |element|
puts element #=>1,2,1,2
end
array.show_array #=>1A,2A,1B,2C
new_array.show_array #=>Undefined method error since it's an object of type Array not New_Class
How would I make new_array an object of type New_Class instead of Array?
This doesn't work:
new_array = New_Class.new
new_array = array.remove_letters
Still says new_array is of type Array.
I played around with clone and dup but couldn't get it to work that way either. Thanks!
Your remove_letters method returns an array, which you then assign to new_array. If you want new_array to be another instance of New_Class instead, then you need to return another instance of NewClass from your remove_letters method, not an array:
def initialize(values = ['1A','2A','1B','2C'])
#array = values
end
def remove_letters
New_Class.new(#array.map { |rmv| rmv.sub(/([0-9])([A-Z])/, '\1') })
end
Above, I modify initialize to use your array as a default, if an initial array value isn't passed to New_Class.new, then it uses ['1A','2A','1B','2C'] as a default.
I have an array and I need to create a class method named "each" to yield or return (not sure what the difference is of those or which I need to use if any) each item in the array when the method is called.
Do I need to use return instead of yield or neither?
class Sum
def initialize
#sum = Array.new
end
def each
#sum.each do |item|
yield item
end
end
You could include Enumerable and implement each and get a lot of functionality for free.
class Sum
def initialize
#sum = []
end
def each &block
#sum.each &block
end
end
This will yield each item of the collection or if you do not provide a block it will return you an enumerator just like a normal Array would
Are you attempting to write a class that acts like an iterator? That provides it's own each form? If so, then this is the pattern for doing so with a ruby class:
class MyIterator
include Enumerable
def initialize data=[]
#data = data
end
def each
#data.each do |item|
yield item
end
end
end
m = MyIterator.new [1,2,3,4]
m.each do |item|
puts "item=#{item}"
end
puts m.map(&:next)
The for_each method iterates over the array (#data) and yields each of the values to the block.
As #jan-dvorak pointed out, including Enumerable and naming the method each gives additional benefits, such as being able to call map directly on the object.
Assuming that this is homework, and using the existing each method is not allowed:
class Sum
def initialize
#values = []
end
def homework_each
0.upto(#values.length-1){ |i| yield #values[i] }
end
end
However, your code as written works, modulo the fact that you're missing an end, and also you're missing any way to populate your array with values.
I'm working on a class that reads some sensor information and returns it as a hash. I would like to use the hash keys as accessors, but I'm not having much luck getting it work. Here are the relevant parts of my code so far:
I've tried it both with method_missing and by using the :define_method method.
attr_reader :sensor_hash
def method_missing(name, *args, &blk)
if args.empty? && blk.nil? && #sensor_hash.has_key?(name.to_s)
#sensor_hash[name.to_s]
else
super
end
end
def sensor(*sensor_to_return)
sensor_output = run_command(this_method_name)
sensor_output = sensor_output.split("\n")
sensor_output.map! { |line| line.downcase! }
unless sensor_to_return.empty?
sensor_to_return = sensor_to_return.to_s.downcase
sensor_output = sensor_output.grep(/^#{sensor_to_return}\s/)
end
#sensor_hash = Hash.new
sensor_output.each { |stat| #sensor_hash[stat.split(/\s+\|\s?/)[0].gsub(' ','_').to_sym] = stat.split(/\s?\|\s?/)[1..-1].each { |v| v.strip! } }
#sensor_hash.each do |k,v|
puts v.join("\t")
self.class.send :define_method, k { v.join("\t") }
end
return #sensor_hash
The data returned is a hash with the sensor name as the key and and the value is an array of everything else returned. My goal is to be able to call Class.sensor.sensor_name and get the output of Class.sensor[:sensor_name]. Currently, all I'm able to get is an undefined method error. Anybody have any idea what I'm doing wrong here?
Maybe OpenStruct does what you want. From the doc :"It is like a hash with a different way to access the data. In fact, it is implemented with a hash, and you can initialize it with one."
require 'ostruct'
s=OpenStruct.new({:sensor_name=>'sensor1',:data=>['something',1,[1,2,3]]})
p s.sensor_name
#=> "sensor1"
Just a quick example. Do you have any reasons to not monkey-patch your Hash?
irb(main):001:0> class Hash
irb(main):002:1> def method_missing(name, *args, &blk)
irb(main):003:2> if self.keys.map(&:to_sym).include? name.to_sym
irb(main):004:3> return self[name.to_sym]
irb(main):005:3> else
irb(main):006:3* super
irb(main):007:3> end
irb(main):008:2> end
irb(main):009:1> end
=> nil
irb(main):012:0> h = {:hello => 'world'}
=> {:hello=>"world"}
irb(main):013:0> h.hello
=> "world"
You could use a wrapper class with method missing so you don't have to monkey patch Hash.
class AccessibleHash
def initialize(hash)
#hash = hash
end
def method_missing(name, *args, &block)
sname = name.to_sym
if #hash.keys.include? sname
return #hash[sname]
else
super
end
end
end
Or, if you are working with Rails it has some nice built in object delegation with SimpleDelegator. That would allow you to define accessors on your hash as well as any nested hashes within it.
class AccessibleHash < SimpleDelegator
def initialize
define_accessors(self.keys)
end
def define_accessors(keys)
keys.each do |key|
defind_accessors(body[key].keys)
self.define_singleton_method(key) { self[key] }
end
end
end
ah = AccessibleHash.new({ some: 'hash', with: { recursive: 'accessors' })
ah.with.recursive == 'accessors'
=> true
This would be less performant at instantiation than method_missing, because it has to run recursively over your delegatee object as soon as it's created. However, it's definitely safer than method_missing, and certainly way safer than monkey patching your Hash class. Of course, safety is relative to your goals, If it's all your application does then monkey patch away.
And if you want the recursive, nested accessors without rails you could do something like this with a combination of the above...
class AccessibleHash
def initialize(hash)
#hash = hash
define_accessors(#hash.keys)
end
def define_accessors(keys)
keys.each do |key|
#hash[key] = self.class.new(#hash[key]) if #hash.keys.present?
self.define_singleton_method(key) { self[key] }
end
end
end
But at that point you're getting pretty crazy and it's probably worth reevaluating your solution in favor of something more Object Oriented. If I saw any of these in a code review it would definitely throw up a red flag. ;)