Determining if two objects are "equal"

● ARCHIVED · READ-ONLY
Started by Shaz 20 posts View original ↗
  1. I'm trying to figure out an easy way to determine if two objects are "equal" - that is, if all of their properties have the same value, without needing to know what the individual properties are.

    For instance, if I do this:

    a = $data_actors[1]b = $data_actors[2]p a == bI want to know what I could use in place if == to make this evaluate to true.But if I then modify a.features I want the above to evaluate to false.

    I need to be able to apply this to ANY of the default data structures, as well as any others that I create via scripts.

    I imagine internally, it would need to be something that could automatically list and go through the individual properties of the object and compare them, and if it's a simple data type would just do an == comparison, and if it's a complex data type, it would break it down and compare element by element (for example, a hash or an array). If it inherits from parent classes, it would need to compare all the inherited properties as well.

    If they are not equal, I do not need to know which one is greater than or less than the other - just whether they are "the same" or not.
  2. Now I'm not sure, but have you tried looking at ===? I believe its implicitly called in case statements for equality. I'm not sure if it'll suit your purposes.
  3. nope. That only returns true if they are the same object. They are not.
  4. Is it safe to assume that all properties are instance variables, and all instance variables are properties?
  5. You could alias the == method to include your logic so that you could pick and choose exactly which properties you want to check.


    That's usually how it's done when you want to use an object as a hash key for example and you want to hash on specific properties: that would basically amount to re-writing the hash method as well as the equality checking method.


    I remember reading a lengthy article on this but don't remember the page or what I used to search.
  6. I want ALL properties without having to pick and choose. I am trying to avoid having to write one method for each class I want to compare.
  7. shhhh ... it's a secret. I'll tell you if/when I pull it off ;)

    This seems useful:

    a = $data_actors[1].cloneb = $data_actors[1].clonesame = truea.instance_variables.map { |ivar| same = false if a.instance_variable_get(ivar) != b.instance_variable_get(ivar) }p 'same: ' + (same ? 'true' : 'false')a and b both have a features array - I'm not sure if it's comparing the contents or checking to see if that array is the same (ie - occupies the same memory space) for each of them.But it definitely looks like a good starting point.

    I should include a test to see that a and b are the same class.

    Then a way to see if it's a simple data type (and can just use the != comparison) or something more complex which may need to be handled differently.
  8. Excuse my lack of tags, on my phone using the regular desktop site (I don't really like the mobile version, so everything is broken, of course)

    I suppose you could do something like this:

    class Object

    def all_properties_equal?(other)

    list = instance_variables.map { |Ivar|

    if respond_to?(ivar[1..ivar.size]) # If we have a method the same name as the instance variable (usually set by attr_accessor)

    true

    end

    }

    list.all? { |Ivar|

    other.instance_variable_get(Ivar) == instance_variable_get(Ivar)

    end

    end

    Though while writing this, I did think of the issue that if both variables are not the same type always, something might go amiss.
  9. Yes, I was just starting to think along those lines.

    Why do you populate list and then use list.all?

    Why do you use respond_to? These won't always be attr_accessors - they'll sometimes be just attr_readers.

    Would something like this work ...

    Code:
    class Object  def all_properties_equal?(other)    return false if self.class != other.class    instance_variables.map { |ivar|      a = self.instance_variable_get(ivar)      b = other.instance_variable_get(ivar)      return false if a.class != b.class || a != b    }  return trueend
    but then if a and b are complex data types, a != b will always return false unless they're pointing to the same memory area, which they won't be. So again, a need to handle simple/complex types differently (by calling a.all_properties_equal?( b ) if it's anything whose values can't be compared with ==).
  10. I simply used after_access or as an example. If its an attr_reader would it not still be a property of the class? If you want accessor only, check for the getter and setter.

    That code was just a concept, not something you should use. It isn't easy writing good code on your phone :p . I used respond_to to make sure the method existed in the other class, though I suppose I never checked for its existance in the calling object did I...

    As I said, not an example of good code, just concept :p
  11. For sake of completion, I'd remind that in ruby two objects having the same class does not necessarily mean they have the same instance variables, since those can be defined lazily during runtime. For example instances of Game_Interpreter will define a @comments variable when they first execute command_108.

    In addition, some of the default classes do not use instance variables at all to store their values (like numerics, arrays, structs or RGSSs' graphical objects) and even normal objects have hidden attributes (e.g. freeze, taint and untrusted state).

    I don't know what you will use the code for, but you might need to check your properties recursively.

    However, I can't make up a scenario right know where I'd prefer this kind of equality check, so I don't know if anything of what I said would be important in your case.
  12. Why check properties recursively? I'm not sure how that relates to the previous statements.


    I don't think the first example (lazily defining instance variables) will be a problem in my case. I do know that some of them will have arrays and hashes, which cannot be compared easily, and whose contents may be other classes which could have similar problems.
  13. It doesn't really relate to the previous statements, sorry.

    Flat checking would be only a problem for more complex objects. For example, two instances of RPG::Weapon created in the editor will not be equal since they store a list of unique feature objects. Those features obviously won't be equal according to ==, even when they have the same values, so you'd have to check them attribute-wise too.
  14. Okay, I was just looking through all the built-in classes in the help file, and I think the only two that I really need to worry about are Array and Hash. Anything else that would be in the default data classes I think could be simply compared with ==. I think strings and numerics would be the only other things used.


    Can anyone think of anything in the default classes (Game_Actor, Game_System, etc) that is NOT a string, a number, an array or a hash?
  15. For just the Game_ prefix objects, you also have Tone and Color, and other data classes defined within the scripts (which I am sure you have already accounted for). In addition, you have Proc ($game_message.choice_proc) and Fiber (@fiber for each Game_Interpreter), however, neither make it into the save file. Assuming you do need the check during runtime, in the case of the choice_proc, the only relevant data is which interpreter it points back (which you can get with eval("self", $game_message.choice_proc.binding))to and @fiber for Game_Interpreter is irrelevant because its methods are static and the current state of the fiber is entirely dependent on the other instance variables within the interpreter (namely, @index). You also have one instance of RPG::Map in @map in Game_Map, which actually does get saved with the map, but is discarded if a new version is found when the game is loaded. And within RPG::Map is @data, which is a Table object. Here, it may be enough to just check the map_id (any maps loaded within the same instance from the same map_id should be the same map unless the fundamental way maps are loaded is altered). Unfortunately, though, RPG::Map doesn't have this method by default. RPG::Actor also uses a Table, while we are on the subject of the RPG module objects.

    If you do need the check during runtime and for all classes used within the default scripts, not just the ones responsible for storing persistent data, you also have to worry about Rect, Bitmap, Sprite, Tilemap, Viewport, Window, Plane, and Font.

    For objects that are compatible with Marshal, the easiest check I can think of is to simply dump both objects and compare the results:

    Code:
    class Object  def all_properties_equal?(other)    Marshal.dump(other) == Marshal.dump(self)  endend
    Works off the same principle as the using Marshal to quickly deep clone an object - Marshal does not care about object ids, only the data, which consists of the the instance variables (and their class and instance variables, recursively) for almost every data type. Most the stuff that I didn't benchmark it, so I'm not sure cost wise how it stacks up to performing strict iterations and comparisons, but it should work for anything compatible with Marshal. For those that aren't, you'd probably need to define a specific comparison method for these cases anyway. The main weakness to this method is that stuff for some data that would normally evaluate as true might not in this case, eg., Marshal.dump(2.0) == Marshal.dump(2) is false, because 2.0 is not 2. However, this is also a failing of the method posted above. You can add a "return true if self == other" to the beginning of the method, but that would only check the normal equality method for original object passed in and not recursively throughout the entire object. You could combine using recursion, a pre check with the base equality method and the method posted above but then you are back to having a complex solution to a simple problem. This would probably only be an issue in very specific circumstances (eg., specifically using a script call to add, say, 1.0 to atk to an actor in one instance and simply 1 on another), but still something to be aware of.
  16. Ah - my mistake. I am NOT running this over the $game_* objects. I am running it over the $data_* objects, as they are at the time of being loaded.


    So no worries with procs and fibers, and I think the instance_variable method will handle Tone and Color objects, though I will test them.


    I had not considered Table, and that will be an interesting one - though if it's defined as an array and has instance variables, the above may take care of that too.


    Are you sure RPG::Actor uses a table? I just took a look and couldn't find one.


    The Marshal.dump method is very interesting and I hadn't considered it at all. If what I have becomes troublesome, it's certainly an avenue to consider.


    Thanks for the list. I will refer back to it as I go on :)
  17. My mistake, the Table is actually in RPG::Class in Ace, not RPG::Actor (where it was in VX). It is the parameter table.


    Tone and Color objects do not have standard Ruby instance variables, and Tables don't use any implementation of Array and don't have Enumerable included (you'd have to define your own 'each' method to support Enumerable, if you were so inclined.)
  18. eww, well THAT'S not what I wanted you to say! lol!


    I'll think more about it on the weekend, when I can spend the time going through it more thoroughly.
  19. Not sure if I got you correctly.

    The database item objects actually use a bunch of other classes that lumb together certain attributes that are not responding fine to == by default:

    Spoiler
    RPG::BaseItem::FeatureRPG::Class::Learning

    RPG::UsableItem::Damage

    RPG::UsableItem::Effect

    RPG::Enemy::DropItem

    RPG::Enemy::Action

    RPG::Troop::Member

    RPG::Troop::page

    RPG::Troop::page::Condition

    RPG::EventCommand

    RPG::MoveRoute

    RPG::MoveCommand

    RPG::BGM

    RPG::BGS

    RPG::ME

    RPG::SE

    RPG::Animation::Frame

    RPG::Animation::Timing

    RPG::System::Terms
    RPG::System::TestBattler

    @Mithran:

    Out of my own interest: Is it somehow defined in which order an objects attributes are stored when serializing it through Marshal? Or - more specific - is it somehow guaranteed that the order is the same when two objects are created in the same way?

    If that order follows specific rules I'm sure that would be really useful sometimes. :)