I have a class roughly like this:
class C
attr_accessor :board # board is a multidimensional array (represents a matrix)
def initialize
#board = ... # initialize board
end
def ==(other)
#board == other.board
end
end
Still, when I do:
s = Set.new
s.add(C.new)
s.include?(C.new) # => false
Why?
Set uses eql? and hash, not ==, to test two objects for equality. See, for example, this documentation of Set: "The equality of each couple of elements is determined according to Object#eql? and Object#hash, since Set uses Hash as storage."
If you want two different C objects to be the same for set membership, you'll have to override those two methods.
class C
attr_accessor :board
def initialize
#board = 12
end
def eql?(other)
#board == other.board
end
def hash
#board.hash
end
end
s = Set.new
s.add C.new
s.include? C.new # => true
You need to do something below :
require 'set'
class C
attr_accessor :board
def initialize
#board = 12
end
def ==(other)
#board == other.board
end
end
s = Set.new
c = C.new
s.add(c)
s.include? c # => true
The reason below will not work:
s.add(C.new)
s.include?(C.new) # => false
Using C.new you create 2 different objects. If you do run C.new thrice then you will get 3 different objects:
C.new.object_id # => 74070710
C.new.object_id # => 74070360
C.new.object_id # => 74070030
Summary : The instance of C you added to s,using Set#add and the instance of C you are checking using Set#include? are 2 different objects. So the result you got is more obvious.
class C
attr_accessor :board # board is a multidimensional array (represents a matrix)
def initialize
board = [[1],[2]] # initialize board
p #board #=> nil !!
end
def ==(other)
#board == other.board
end
def eql?(other) # not used
puts "eql? called"
#board == other.board
end
def ==(other) # not used
puts "== called"
#board == other.board
end
def hash
puts "hash called"
board.hash
end
end
require 'set'
s = Set.new
s.add(c = C.new)
p s.include?(c)
Set uses a Hash as storage underneath. Output:
nil
hash called
hash called
true
Related
I have the following code where I set attr_reader and attr_writer manually.
class Pairs
attr_reader :pair, :asks, :bids, :isFrozen, :seq, :main_currency, :sub_currency
attr_writer :pair, :asks, :bids, :isFrozen, :seq
def initialize (key, args)
#pair = key
#main_currency, #sub_currency = key.split('_')
args.each {|k,v|
if numeric?(v) then v=v.to_f end
self.instance_variable_set("##{k}".to_sym, v)
}
end
private
def numeric?(string)
Float(string) != nil rescue false
end
end
Is there a way to automatically set them based on the keys of the arguments, like I'm automatically filling #k with v? Can I set attr_reader for each #k?
I suppose something like:
self.attr_reader("##{k}")
or even better for all objects of the class, something like:
Pairs << attr_reader("##{k}")
I am going to assume that you may be creating this with many keys specific to different Hash if this is the case then rather than clutter the individual instances with unneeded readers for non existent keys let's use the singleton_class for this.
So your final Pairs class could look something like
class Pairs
attr_reader :main_currency, :sub_currency
attr_accessor :pair, :asks, :bids, :isFrozen, :seq
def initialize (key, args)
#pair = key
#main_currency, #sub_currency = key.split('_')
args.each do |k,v|
singleton_class.send(:attr_reader,k)
instance_variable_set("##{k}", convert_numeric(v))
end
# Alternatively:
# args.each do |k,v|
# val = convert_numeric(v)
# define_singleton_method(k) {val}
# end
end
private
def convert_numeric(val)
Float(Rational(val)) rescue val
end
end
TL;DR
For Example: (using #mudasobwa's approach)
class C
def extend_self_with_reader name
self.class.send :attr_reader, name
end
def initialize *keys
keys.each(&method(:extend_self_with_reader))
end
end
This causes subsequent readers to clutter the instance and bleed across instances:
a = C.new(:a,:b)
a.a #=> nil
b = C.new
b.a #=> nil
c = C.new(:r)
c.a #=> nil
c.r #=> nil
a.methods.sort - Object.methods
#=> [:a, :b, :extend_self_with_reader, :r]
a.r #=> nil (hmmmmm)
Instead localize these readers buy using the singleton_class of the instance like:
class C
def initialize *keys
singleton_class.send(:attr_reader, *keys)
end
end
Then
a = C.new(:a,:b)
a.a #=> nil
b = C.new
b.a #=> NoMethodError: undefined method `a'
c = C.new(:r)
c.a #=> NoMethodError: undefined method `a'
c.r #=> nil
a.r #=> NoMethodError: undefined method `r'
a.methods.sort - Object.methods
#=> [:a,:b]
b.methods.sort - Object.methods
#=> []
Using the singleton_class localizes these readers to the instance of the object rather than bleeding them into the Class definition. If attr_reader is not a requirement then this would also be sufficient:
keys.each {|k| define_singleton_method(k) {}}
I doubt I understood the question, but from what I get you want to dynamically extend your class with attribute readers at runtime.
This method would do:
def extend_self_with_reader name
self.class.send :attr_reader, name
end
Test:
class C
def extend_self_with_reader name
self.class.send :attr_reader, name
end
def initialize *keys
puts keys.inspect
keys.each(&method(:extend_self_with_reader))
end
end
cc = C.new(*%i|a b c|)
cc.a #⇒ nil
Perhaps look into define_method.
I'm not 100% sure that I understand the problem, but check this out:
hash = { a: 1, b: 2, c: 3 }
hash.keys.each do |key|
define_method(key) do
hash[key]
end
end
There are now methods for a, b, and c:
a => 1
b => 2
c => 3
That essentially makes an attr_reader for all the keys in the hash. You could do something similar for an attr_writer.
The following code is resulting in nil and I can't figure out why. Is there something wrong with my initialization?
class Card
VALUES = %w(2 3 4 5 6 7 8 9 10 J Q K A)
SUITS = %w(S H D C)
def initialize(suit, value)
#suit = suit
#value = value
end
end
class Deck
attr_accessor :cards
def initialize
#cards = []
Card::SUITS.each do |suit|
Card::VALUES.each do |value|
#cards << Card.new(suit, value)
end
end
end
end
Deck.new
p #cards
#cards is not known outside the object. Outside the class Deck it is a instance variable of the top-level scope in Ruby.
You have to use the accessor method to get the content:
class Card
VALUES = %w(2 3 4 5 6 7 8 9 10 J Q K A)
SUITS = %w(S H D C)
def initialize(suit, value)
#suit = suit
#value = value
end
end
class Deck
attr_accessor :cards
def initialize
#cards = []
Card::SUITS.each do |suit|
Card::VALUES.each do |value|
#cards << Card.new(suit, value)
end
end
end
end
deck = Deck.new #<--- Store object in a variable
p deck.cards #<--- Use accessor
or just:
my_deck = Deck.new
p my_deck.cards
In short, instance variables can only be seen by other methods from within the same class. I believe what you're trying to do is:
new_deck = Deck.new
p new_deck.cards
Calling the cards method on new_deck returns #cards.
You create a new object Deck.new, but you don't print the value of its cards - you print a #cards instance variable which in this context is nil.
You probably wanted something like p Deck.new.cards.
Currently your output for #cards array is difficult to read and contains object info. I thought I'd offer an alternative I've just conjoured up, hope it helps:
class Deck
attr_writer :suits, :values
attr_accessor :deck
def initialize
suits
values
generate_deck
shuffle
end
def generate_deck
#deck = []
#suits.each do |suit|
#values.each { |value| #deck << [suit, value] }
end
end
def suits
#suits = ['Clubs', 'Diamonds', 'Hearts', 'Spades' ]
end
def values
#values = ('2'..'10').to_a + ['J','Q','K','A']
end
#just in case you want to shuffle your deck
def shuffle
#deck.shuffle!
end
end
require 'pp' #this makes the output prettier
new_deck = Deck.new
pp new_deck.deck #calls your current deck so you can see it
Output example:
$ ruby yourfilename.rb
[["Hearts", "K"],
["Spades", "5"],
["Clubs", "7"],
#code omitted... (the rest of your cards would be here)
["Clubs", "K"],
["Hearts", "5"],
["Diamonds", "J"],
["Hearts", "7"]]
For academic reasons, I'd like to make an instance of Ruby class act like a hash.
GOALS
Initialize MyClass instance with a hash # success
Request values from instance of myClass, like a hash # success
Then set properties as a hash # fail
Although some discussion exists, I tried what's out there (1, 2) with no success. Let me know what I'm doing wrong. Thanks!
class MyClass
attr_accessor :my_hash
def initialize(hash={})
#my_hash = hash
end
def [](key)
my_hash[key]
end
def set_prop(key, value)
myhash[key] = value
end
end
test = myClass.new({:a => 3}) #=> #<MyClass:0x007f96ca943898 #my_hash={:a=>3}>
test[:a] #=> 3
test[:b] = 4 #=> NameError: undefined local variable or method `myhash' for #<MyClass:0x007f96ca9d0ef0 #my_hash={:a=>3}>
You declared set_prop, but you're using []= in tests. Did you mean to get this?
class MyClass
attr_accessor :my_hash
def initialize(hash={})
#my_hash = hash
end
def [](key)
my_hash[key]
end
def []=(key, value)
my_hash[key] = value
end
end
test = MyClass.new({:a => 3}) # success
test[:a] # success
test[:b] = 4 # success
test.my_hash # => {:a=>3, :b=>4}
module HashizeModel
def [](key)
sym_key = to_sym_key(key)
self.instance_variable_get(sym_key)
end
def []=(key, value)
sym_key = to_sym_key(key)
self.instance_variable_set(sym_key, value)
end
private
def to_sym_key(key)
if key.is_a? Symbol
return ('#'+key.to_s).to_sym
else
return ('#'+key.to_s.delete('#')).to_sym
end
end
end
You should write it as test = MyClass.new({:a => 3}) and the below code should work.
class MyClass
attr_accessor :my_hash
def initialize(hash={})
#my_hash = hash
end
def [](key)
#my_hash[key]
end
def []=(key,val)
#my_hash[key]=val
end
def set_prop(key, value)
#myhash[key] = value
end
end
test = MyClass.new({:a => 3})
test[:a]
test[:b]= 4
test.my_hash # => {:a=>3, :b=>4}
not quite understanding factory method here...
here is the respec line:
Temperature.from_celsius(50).in_celsius.should == 50
Here is what I have now:
getting errors...not quite sure how to satisfy this. thanks
class Temperature
attr_accessor :f
attr_accessor :c
def initialize(args)
#f = args[:f]
#c = args[:c]
end
def in_fahrenheit
#f or
(#c*9.0/5.0)+32
end
def in_celsius
#c or
(#f-32)*(5.0/9.0)
end
def self.from_celsius(c)
new c
end
end
This should help
class Temperature
def initialize c
#c = c
end
def in_celsius
#c
end
def in_fahrenheit
#c *9.0 /5.0 +32
end
# factory pattern typically instantiates a new object
def self.from_celsius(c)
new c
end
end
puts Temperature.from_celsius(50).in_celsius #=> 50
puts Temperature.from_celsius(100).in_fahrenheit #=> 212
I would recommend against attr_accessor :c unless you want users to have public access to temp.c. Without it, users will be forced to use temp.in_celsius or temp.in_fahrenheit
You need to assign to :c in the initialize method. Then you need self.from_celsius to return a new instance of Temperature. You probably want something like this:
class Temperature
attr_accessor :c
def initialize c
#c = c
end
def in_celsius
#c
end
def in_fahrenheit
9/5 * #c + 32
end
def self.from_celsius(num)
Temperature.new(num)
end
def self.from_fahrenheit(num)
Temperature.new((num-32)*5/9)
end
end
Now rspec shows true
1.9.1p378 :047 > Temperature.from_celsius(50).in_celsius.should == 50
=> true
1.9.1p378 :131 > Temperature.from_fahrenheit(32).in_celsius.should == 0
=> true
The reason you're getting "error: Can't covert symbol to integer –" is because you're in your Temperature.from_celsius(50) you're passing it an integer when you're supposed to pass it a key & symbol for the options hash.
initialized
class Temperature
def initialize(opts = {})
#options = opts
end
class factory method
def self.from_celsius(x)
Temperature.new(:c => x)
end
instance method
def in_celsius
if #options[:c] == nil
return (#options[:f]-32) * (5/9.to_f)
else
return #options[:c]
end
end
I update this question to better reflect what I have problems to grasp. The example below kind of work but how can I access the Sub class then I have defined it inside the Base class? Should it not be better to do the call outside the class? If so how do I do that? The second question I have in this example is how to grab values so I can use them in another class. Here I store the values in an array that I later need to unpack in another class. Should I not be able to use a proc for this?
Basically what I want to do is to sort the methods into two different classes depending on if they are nested or not.
class Sub
def initialize(base_class_method)
#base_class_method = base_class_method
#sub_methods = []
end
# omitted code here
def base_class_method
#base_class_method
end
def sub_actions(method)
#sub_methods << method
end
def return_sub_methods
#sub_methods
end
def method_missing(sub_method, &block)
if sub_method
sub_method
else
super
end
end
end
class Base
def initialize
#base_methods = []
end
# omitted code here
def base_actions(method)
#base_methods << method
end
def return_base_methods
#base_methods
end
def method_missing(method, &block)
if block_given?
Sub.new(method).instance_eval(&block)
elsif method
base_actions(method)
else
super
end
end
end
base = Base.new
base.instance_eval do
something1
something_with_a_block do
something_inside_block1_1
something_inside_block1_2
end
something2
something_with_a_block2_2 do
something_inside_block2_1
end
end
p base.return_base_methods #=> [:something1, :something2] works!
You can do something like this.
class Test
# reserved method to instantiate object
def initialize(a,b,c)
#a = a
#b = b
#c = c
end
# getters
def a
#a
end
def b
#b
end
def c
#c
end
def abc
[#a, #b, #c] # returns an array
end
# setters
def a=(var)
#a = var
end
def b=(var)
#b = var
end
def c=(var)
#c = var
end
# set values all at once
def update(a, b, c)
#a = a
#b = b
#c = c
end
end
z = Test.new('something','something','something')
z.update('something!','nothing!',"a thing!")
z.a
z.b
z.c
z.a = 'wow, new value!'