Uniqueness of Ruby Instances - ruby

If I create two String instances with the same content separately they are identical. This is not the case with custom classes by default (see example below).
If I have my own class (Test below) and I have a variable (#v below) which is unique, ie. two Test instances with the same #v should be treated as identical, then how would I go about telling Ruby this is the case?
Consider this example:
class Test
def initialize(v)
#v = v
end
end
a = {Test.new('a') => 1, Test.new('b') => 2}
a.delete(Test.new('a'))
p a
# # Desired output:
# => {#<Test:0x100124ef8 #v="b">=>2}

You need to define an == method that defines what equality means for your class. In this case, you would want:
class Test
def initialize(v)
#v = v
end
def ==(other)
#v == other.instance_variable_get(:#v)
end
end

You are using objects of class Test as keys for the hash. In order for that to function properly (and consequently a.delete), you need to define two methods inside Test: Test#hash and Test#eql?
From: http://ruby-doc.org/core/classes/Hash.html
Hash uses key.eql? to test keys for
equality. If you need to use instances
of your own classes as keys in a Hash,
it is recommended that you define both
the eql? and hash methods. The hash
method must have the property that
a.eql?(b) implies a.hash == b.hash.

I found a different way to tackle this, by keeping track of all the instances of Test internally I can return the premade instance rather than making a new one and telling ruby they're equivalent:
class Test
def self.new(v)
begin
return ##instances[v] if ##instances[v]
rescue
end
new_test = self.allocate
new_test.instance_variable_set(:#v,v)
(##instances ||= {})[v] = new_test
end
end
Now Test.new('a') == Test.new('a') and Test.new('a') === Test.new('a') :)

Most of the time, an object you need to be comparable and/or hashable is composed of member variables which are either primitives (integers, strings, etc) or are themselves comparable/hashable. In those cases, this module:
module Hashable
include Comparable
def ==(other)
other.is_a?(self.class) && other.send(:parts) == parts
end
alias_method :eql?, :==
def hash
parts.hash
end
end
can simply be included in your class to take care of all of the busywork. All you have to do is define a "parts" method that returns all of the values that comprise the object's state:
class Foo
include Hashable
def initialize(a, b)
#a = a
#b = b
end
private
def parts
[#a, #b]
end
end
Objects built this way are comparable (they have <, <=, ==, >=, >, != and equ?) and they can be hash keys.

Related

How does Set in ruby compare elements?

I am trying to put custom objects in a set. I tried this:
require 'set'
class Person
include Comparable
def initialize(name, age)
#name = name
#age = age
end
attr_accessor :name, :age
def ==(other)
#name == other.name
end
alias eql? ==
end
a = Person.new("a", 18)
b = Person.new("a", 18)
people = Set[]
people << a
people << b
puts a == b # true
It seems that Set does not identify same objects with Object#eql? or == methods:
puts people # #<Set: {#<Person:0x00007f9e09843df8 #name="a", #age=18>, #<Person:0x00007f9e09843da8 #name="a", #age=18>}>
How does Set identify two same objects?
From the docs:
Set uses Hash as storage, so you must note the following points:
Equality of elements is determined according to Object#eql? and Object#hash. [...]
That said: If you want two people to be equal when they have the same name, then you must implement hash accordingly:
def hash
#name.hash
end
Ruby's built-in Set stores items in a Hash. So for your objects to be treated as the "same" by Set, you also need to define a custom hash method. Something like this would work:
def hash
#name.hash
end
Use gem which set.rb to see where the source code for Set is stored, and try reading through it. It's clear and well-written.

List all Ruby classes and methods

For a while now I've wanted a way to inspect all changes made to a given ruby environment when a particular model is loaded. Furthermore, the ability to compare and contrast the methods and classes available in separate versions of ruby, and different Ruby VMs.
I've created some code which uses meta-programming to generate such a list:
arr = []
arr << "Ruby version " + ::RUBY_VERSION
arr << ""
Module.constants.each do |const|
if ::Module.const_defined? const
#If for whatever reason const_get fails, then rescue.
begin
obj = Module.const_get(const)
if obj
if obj.is_a? Class
# Class methods
arr << (obj.singleton_methods).sort.map do |method_sym|
obj.to_s + "::" + method_sym.to_s
end
# Instance methods
arr << (obj.instance_methods - (obj.superclass ? obj.superclass.instance_methods : []) - Object.methods).sort.map do |method_sym|
"#<" + obj.to_s + ">." + method_sym.to_s
end
elsif obj.is_a? Module
arr << (obj.methods - Module.methods).sort.map do |method_sym|
obj.to_s + "::" + method_sym.to_s
end
else
# Methods
arr << "::" + const.to_s
end
end
rescue
end
end
end
File.new("D:\\methods_#{::RUBY_VERSION}.txt","w").write(arr.flatten.sort.join("\n"))
The criteria for the list is that it should list all non-inherited instance and class methods. Constants are indicated with :: prefix, Class methods are indicated as MyClass::someMethod and instance methods are indicated as follows: #<MyClass>.someMethod.
The script above works for the most part however, it misses out Object and BasicObject. I.E. in the lists created there are no lines prefixed with #<Object>. or Object::. Am I missing something obvious?
I suggest you create a hash for each Ruby version and then save that hash or its contents to a file whose name indicates the Ruby version (e.g., v_2-5-1.json or v_2-5-1.txt)
While I don't fully understand the question, you may find he method ObjectSpace::each_object helpful here. For example,
h = ObjectSpace.each_object(Class).with_object({}) do |c,h|
h[c] = { cm: c.methods(false).sort, im: c.instance_methods(false).sort,
con: c.constants.sort }
end
h.keys && [Object, BasicObject]
# [Object, BasicObject]
As you see Object and BasicObject are two keys (classes) in this hash. Let's look at the value of one of the keys: Dir.
h[Dir]
#=> {:cm=>[:[], :chdir, :children, :chroot, :delete, :each_child, :empty?,
# :entries, :exist?, :exists?, :foreach, :getwd, :glob, :home,
# :mkdir, :open, :pwd, :rmdir, :unlink],
# :im=>[:close, :each, :fileno, :inspect, :path, :pos, :pos=, :read,
# :rewind, :seek, :tell, :to_path],
# :con=>[]}
This is for
RUBY_VERSION
#=> "2.5.1"
Comparing the contents of this hash with the doc for Dir for v2.5.1 we see that the results are correct for the class Dir.
Obtaining constants defined within a class C is problematic. C.constants, which I've used above includes inherited constants. Were I to write C.constants - C.superclass.constants (mindful that BasicObject.superclass #=> nil) that would not report inherited constants the are redefined in C (and redefining an inherited constant does not produce a warning message).
Instead of just looking at classes you may wish to examine all modules (ObjectSpace.each_object(Module)) and add a key :is_class to the hash with value true or false):
There may be other keys you may wish to add to the hash. :superclass and :included_modules (for classes ) are two that comes to mind; :nesting (for non-class modules) is another. See Module::nesting. I suspect that these observations just scratch the surface, that a meaningful comparison of two Ruby versions will be highly complex.

What is the "other" object and how does it work?

I have seen other used often in class comparisons, such as
def ==(other)
...
end
or
def eql?(other)
self == other
end
but I still have found no explanation of what it actually is. What's going on here?
And perhaps this is for another question, but what does starting a method with == imply?
In ruby, operators are in fact method calls. If you have two variables a and b and want to check their equality, you generally write a == b, but you could write a.==(b). The last syntax shows what happens during an equality check : ruby calls a's method == and passes it b as an argument.
You can implement custom equality check in your classes by defining the == and/or the eql? methods. In your example, other is simply the name of the argument it receives.
class Person
attr_accessor :name
def initialize name
#name = name
end
end
a = Person.new("John")
b = Person.new("John")
a == b # --> false
class Person
def == other
name == other.name
end
end
a == b # --> true
For your second question, the only methods starting with == you're allowed to implement are == and ===. Check here for the full list of restrictions on method names in ruby: What are the restrictions for method names in Ruby?
other is a parameter to this method, the object, that is being passed.
For example:
class A
def ==(other)
:lala == other
end
end
obj = A.new
obj.==(:foo) # full syntax, just like any other method
# but there's also a shorthand for operators:
obj == :foo # => false
obj == :lala # => true
other is the parameter for == and it represents the object you are comparing with.
Example
x == y
The == method (yes, its just a method!), on your x object, gets called with y as a parameter.
Welcome to Ruby, you'll love it after a while :)
other, in this case, is the object you're comparing to, so this:
class SomeClass
def ==(other)
self == other
end
end
means:
SomeClass.new == nil # here "other" is nil, this returns false
Basically def ==(other) is the implementation of the == operator, for cases where you do something specific in your class regarding comparison using ==.
For numbers you can get the sum of 2 numbers using:
a= x + y
in Ruby everything is object, Right! so x and y are objects and for Number(Object) x it has a defined method + which accept 1 paramter which is y
same for what you are trying to understand, what if you have 2 classes and you want to check if they are equal or not and its your defined class and you want to specify how the are equal for example if their name attribute is equal then you can say:
class Student
def ==(other)
self.name == other.name
end
end
fi = Student.new(name: 'Mark')
sec = Student.new(name: 'Hany')
if (fi == sec)
# do something here

How can I make the set difference insensitive to case?

I have a class in which the data is stored as a set and I want to be able to compare objects of that class such that the letter case of the elements is of no matter. For example if the set contains elements that are strings there should be no difference of "a" and "A".
To do this I have tried to define the eql? method of the set members to be insensitive to case but this has no effect on the method - (alias difference) in Set. So, how should I go about to make - insensitive to case?
The following code illustrates the problem:
require 'set'
class SomeSet
include Enumerable
def initialize; #elements = Set.new; end
def add(o)
#elements.add(o)
self
end
def each(&block) # To enable +Enumerable+
#elements.each(&block)
end
def difference(compared_list)
#elements - compared_list
end
end
class Element
attr_reader :element
def initialize(element); #element = element; end
# This seems to have no effect on +difference+
def eql?(other_element)
element.casecmp(other_element.element) == 0
end
end
set1 = SomeSet.new
set2 = SomeSet.new
set1.add("a")
set2.add("A")
# The following turns out false but I want it to turn out true as case
# should not matter.
puts set1.difference(set2).empty?
Ok, firstly, you're just storing strings from SomeSet#add, you need to store an instance of Element, like so:
def add(o)
#elements.add(Element.new(o))
self
end
And you need to implement a hash method in your Element class.
You can convert Element##element to lowercase, and pass on its hash.
def hash
element.downcase.hash
end
Full code and demo: http://codepad.org/PffThml2
Edit: For my O(n) insertion comment, above:
Insertions are O(1). From what I can see, eql? is only used with the hash of 2 elements is same. As we're doing hash on the downcased version of the element, it will be fairly well distributed, and eql? shouldn't be called much (if it is called at all).
From the docs:
The equality of each couple of elements is determined according to Object#eql? and Object#hash, since Set uses Hash as storage.
Perhaps you need to implement Object#hash as well.
require 'set'
class String2
attr_reader :value
def initialize v
#value = v
end
def eql? v
value.casecmp(v.value) == 0
end
def hash
value.downcase.hash
end
end
set1 = Set.new
set2 = Set.new
set1.add(String2.new "a")
set2.add(String2.new "A")
puts set1.difference(set2).empty?

a set of strings and reopening String

In an attempt to answer this question: How can I make the set difference insensitive to case?, I was experimenting with sets and strings, trying to have a case-insensitive set of strings. But for some reason when I reopen String class, none of my custom methods are invoked when I add a string to a set. In the code below I see no output, but I expected at least one of the operators that I overloaded to be invoked. Why is this?
EDIT: If I create a custom class, say, String2, where I define a hash method, etc, these methods do get called when I add my object to a set. Why not String?
require 'set'
class String
alias :compare_orig :<=>
def <=> v
p '<=>'
downcase.compare_orig v.downcase
end
alias :eql_orig :eql?
def eql? v
p 'eql?'
eql_orig v
end
alias :hash_orig :hash
def hash
p 'hash'
downcase.hash_orig
end
end
Set.new << 'a'
Looking at the source code for Set, it uses a simple hash for storage:
def add(o)
#hash[o] = true
self
end
So it looks like what you need to do instead of opening String is open Set. I haven't tested this, but it should give you the right idea:
class MySet < Set
def add(o)
if o.is_a?(String)
#hash[o.downcase] = true
else
#hash[o] = true
end
self
end
end
Edit
As noted in the comments, this can be implemented in a much simpler way:
class MySet < Set
def add(o)
super(o.is_a?(String) ? o.downcase : o)
end
end

Resources