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
Related
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
I need to have add_student add multiple students to the array grade. The method should add multiple students to the array and assign them to grade or the key in the hash.
class School
def initialize(name)
#name = name
end
def roster
#roster ||= {}
end
def add_student(student, grade)
roster[grade] = []
roster[grade] << student
end
def student_grade(grade)
return students
end
end
I do not understand why add_student does not add multiple arguments. I get an error that it returns only one argument or nil.
Vutran's answer correctly identifies the problem, but a better solution would be to use a default proc to automatically initialize any missing value in the hash.
class School
attr_reader :roster
def initialize(name)
#name = name
#roster = Hash.new {|h,k| h[k] = [] }
end
def add_student(student, grade)
roster[grade] << student
end
# ...
end
Every time you add a student, you reinitialize your roster[grade] to [] which discards all of previous added students. To fix this, you might change:
roster[grade] = []
to
roster[grade] ||= []
This line of code does the following work: it initializes roster[grade] to [] if roster[grade] is nil.
I have a has like this:
reminder_hash = {"bot_client_id"=>"test-client-id", "recurring"=>true, "recurring_natural_language"=>"everyday", "time_string"=>"10AM", "time_zone"=>"America/Los_Angeles", "via"=>"slack", "keyword"=>"test-keyword", "status"=>"active", "created_time"=>1444366166000}
I would like to pass to method like this (from Rspec):
let(:reminder) { Reminder.new( reminder_hash ) }
I would like the instance variables for Reminder to be based on the hash.
expect(reminder.bot_client_id).to eq('test-client-id')
I can't get it to work. I tried the following:
class Reminder
attr_accessor :bot_client_id
def initiate(hash)
# http://stackoverflow.com/questions/1615190/declaring-instance-variables-iterating-over-a-hash
hash.each do |k,v|
instance_variable_set("##{k}",v)
# if you want accessors:
eigenclass = class<<self; self; end
eigenclass.class_eval do
attr_accessor k
end
end
end
Rspec gives the following error:
Failure/Error: let(:reminder) { Reminder.new( reminder_hash ) }
ArgumentError:
wrong number of arguments (1 for 0)
Question: How do I pass a hash to an instance method for an object, so that the hash values are the instance variables for that newly created object?
Sure you can do something like this in your initialize method:
hash.each do |k, v|
instance_variable_set("##{k}", v)
self.class.send(:attr_reader, k)
end
Here's an example using your input hash:
class Reminder
def initialize(hash)
hash.each do |k, v|
instance_variable_set("##{k}", v)
self.class.send(:attr_reader, k)
end
end
end
reminder_hash = {"bot_client_id"=>"test-client-id", "recurring"=>true, "recurring_natural_language"=>"everyday", "time_string"=>"10AM", "time_zone"=>"America/Los_Angeles", "via"=>"slack", "keyword"=>"test-keyword", "status"=>"active", "created_time"=>1444366166000}
reminder = Reminder.new(reminder_hash)
puts reminder
puts reminder.bot_client_id
Output:
#<Reminder:0x007f8a48831498>
test-client-id
Ruby has OpenStruct to do that.
require 'ostruct'
reminder_hash = {"bot_client_id"=>"test-client-id", "recurring"=>true, "recurring_natural_language"=>"everyday", "time_string"=>"10AM", "time_zone"=>"America/Los_Angeles", "via"=>"slack", "keyword"=>"test-keyword", "status"=>"active", "created_time"=>1444366166000}
reminder = OpenStruct.new(reminder_hash)
p reminder.bot_client_id # => "test-client-id"
Use a Struct for a full blown Class:
Reminder = Struct.new(*reminder_hash.keys.map(&:to_sym))
r = Reminder.new(*reminder_hash.values)
p r.bot_client_id # => "test-client-id"
When I call first_array | second_array on two arrays that contain custom objects:
first_array = [co1, co2, co3]
second_array =[co2, co3, co4]
it returns [co1, co2, co3, co2, co3, co4]. It doesn't remove the duplicates. I tried to call uniq on the result, but it didn't work either. What should I do?
Update:
This is the custom object:
class Task
attr_accessor :status, :description, :priority, :tags
def initiate_task task_line
#status = task_line.split("|")[0]
#description = task_line.split("|")[1]
#priority = task_line.split("|")[2]
#tags = task_line.split("|")[3].split(",")
return self
end
def <=>(another_task)
stat_comp = (#status == another_task.status)
desc_comp = (#description == another_task.description)
prio_comp = (#priority == another_task.priority)
tags_comp = (#tags == another_task.tags)
if(stat_comp&desc_comp&prio_comp&tags_comp) then return 0 end
end
end
and when I create few instances of Task type and drop them into two different arrays and when I try to call '|' on them nothing happens it just returns array including both first and second array's elements without the duplicates removed.
No programming language for itself can be aware if two objects are different if you don't implement the correct equality methods.
In the case of ruby you need to implement eql? and hash in your class definition, as these are the methods that the Array class uses to check for equality as stated on Ruby's Array docs:
def eql?(other_obj)
# Your comparing code goes here
end
def hash
#Generates an unique integer based on instance variables
end
For example:
class A
attr_accessor :name
def initialize(name)
#name = name
end
def eql?(other)
#name.eql?(other.name)
end
def hash
#name.hash
end
end
a = A.new('Peter')
b = A.new('Peter')
arr = [a,b]
puts arr.uniq
Removes b from Array leaving only one object
Hope this helps!
The uniq method can take a block that defines what to compare the objects on. For example:
class Task
attr_accessor :n
def initialize(n)
#n = n
end
end
t1 = Task.new(1)
t2 = Task.new(2)
t3 = Task.new(2)
a = [t1, t2, t3]
a.uniq
#=> [t1, t2, t3] # because all 3 objects are unique
a.uniq { |t| t.n }
#=> [t1, t2] # as it's comparing on the value of n in the object
I tried the solution from fsaravia above and it didn't work out for me. I tried in both Ruby 2.3.1 and Ruby 2.4.0.
The solution I've found is very similar to what fsaravia posted though, with a small tweak. So here it is:
class A
attr_accessor :name
def initialize(name)
#name = name
end
def eql?(other)
hash.eql?(other.hash)
end
def hash
name.hash
end
end
a = A.new('Peter')
b = A.new('Peter')
arr = [a,b]
puts arr.uniq
Please, don't mind that I've removed the # in my example. It won't affect the solution per se. It's just that, IMO, there wasn't any reason to access the instance variable directly, given a reader method was set for that reason.
So...what I really changed is found inside the eql? method, where I used hash instead name. That's it!
If you look at the Array#| operator it says that it uses the eql?-method, which on Object is the same as the == method. You can define that by mixin in the Comparable-module, and then implement the <=>-method, then you'll get lots of comparison-methods for free.
The <=> operator is very easy to implement:
def <=>(obj)
return -1 if this < obj
return 0 if this == obj
return 1 if this > obj
end
Regarding your 'update', is this what you are doing:
a = Task.new # => #<Task:0x007f8d988f1b78>
b = Task.new # => #<Task:0x007f8d992ea300>
c = [a,b] # => [#<Task:0x007f8d988f1b78>, #<Task:0x007f8d992ea300>]
a = Task.new # => #<Task:0x007f8d992d3e48>
d = [a] # => [#<Task:0x007f8d992d3e48>]
e = c|d # => [#<Task:0x007f8d988f1b78>, #<Task:0x007f8d992ea300>, \
#<Task:0x007f8d992d3e48>]
and then suggesting that e = [a, b, a]? If so, that's the problem, because a no longer points to #<Task:0x007f8d988f1b78>. All you can say is e => [#<Task:0x007f8d988f1b78>, b, a]
I took the liberty to rewrite your class and add the methods that needs to be overwritten in order to use uniq (hash and eql?).
class Task
METHODS = [:status, :description, :priority, :tags]
attr_accessor *METHODS
def initialize task_line
#status, #description, #priority, #tags = *task_line.split("|")
#tags = #tags.split(",")
end
def eql? another_task
METHODS.all?{|m| self.send(m)==another_task.send(m)}
end
alias_method :==, :eql? #Strictly not needed for array.uniq
def hash
[#status, #description, #priority, #tags].hash
end
end
x = [Task.new('1|2|3|4'), Task.new('1|2|3|4')]
p x.size #=> 2
p x.uniq.size #=> 1
How would I create an attr_accessor to array?
for example
class MyClass
attr_accessor :my_attr_accessor
def initialize()
end
def add_new_value(new_array)
#my_attr_accessor += new_array
return #my_attr_accessor
end
end
my_class = MyClass.new
my_class.my_attr_accessor = 1
my_class.my_attr_accessor[1] = 2
my_class.my_attr_accessor.push = 3
my_class.add_new_value(5)
my_class.my_attr_accessor
=> [1, 2, 3, 5]
Just use an instance variable that points to an array and make an accessor from that instance variable.
Inside your class include something like this:
attr_accessor :my_attr_accessor
def initialize
#my_attr_accessor = []
end
Note that usingattr_accessor will allow you to change the value of the variable. If you want to ensure that the array stays, use attr_reader in place of attr_accessor. You will still be able to access and set array elements and perform operations on the array but you won't be able to replace it with a new value and using += for concatenation will not work.
If you are OK with the Array always existing, #david4dev's answer is good. If you only want the array to pop into existence on the first usage, and never want the user to be able to replace it with a new array (via assignment):
class MyClass
def my_attr_accessor
#my_attr_accessor ||= []
end
def add_new_value( value )
my_attr_accessor << value
end
def add_new_values( values_array )
my_attr_accessor.concat values_array
end
end
The user could still call my_class.my_attr_accessor.replace( [] ) to wipe it out.