Package sim.field

Class SparseField

java.lang.Object
sim.field.SparseField
All Implemented Interfaces:
Serializable
Direct Known Subclasses:
Continuous2D, Continuous3D, SparseGrid2D, SparseGrid3D

public abstract class SparseField extends Object implements Serializable
While it has no abstract members, SparseField is explicitly an abstract superclass of various sparse field objects. It specifies a many-to-one relationship between objects and locations. It contains two hash tables and a bag, resulting in fairly speedy, O(1) searches for all objects at a location, all objects in the entire field, changes in location for an object, and addition or removal of an object.

There are two flags you can set to trade memory for speed. These flags are here because often in a Sparse Field, you'd load a lot of things into one location, then delete all of them, but the large Bag which held them is still hanging there. Thus: removeEmptyBags will garbage-collect any bags in the field which have been completely emptied (except for the allObjects bag). And replaceLargeBags will replace large bags (over 32 in size) with smaller ones when the bag's contents drop below 1/4 the bag size. The smaller bags will be twice the size of the contents (rather than 4 times or more).

To create a SparseField, you need to specify exactly what type a "location" object is. You do this by overriding the setObjectLocation() method and creating a method called getObjectLocation(), which returns objects of that class (by calling getRawObjectLocation() and casting the result into the proper type). See the Example Usage below.

Warning About Hashing. Java's hashing method is broken in an important way. One can override the hashCode() and equals() methods of an object so that they hash by the value of an object rather than just the pointer to it. But if this is done, then if you use this object as a key in a hash table, then change those values in the object, it will break the hash table -- the key and the object hashed by it will both be lost in the hashtable, unable to be accessed or removed from it. The moral of the story is: do not override hashCode() and equals() to hash by value unless your object is immutable -- its values cannot be changed. This is the case, for example, with Strings, which hash by value but cannot be modified. It's also the case with Int2D, Int3D, Double2D, and Double3D, as well as Double, Integer, etc. Some of Sun's own objects are broken in this respect: Point, Point2D, etc. are both mutable and hashed by value.

This affects you in the following way. While SparseField can specify any Object as a "location", generally speaking, subclasses should only permit immutable objects as locations (Int2D, Integer, String, etc.), or warn the user to never modify the location object he provides as a key.

Computational Complexity. Adding a new object to a location is O(1). Changing an object's location, or removing the object, is O(M), where M is the number of objects currently located at the object's old location. Scanning through all objects is O(N) and fast, where N is the number of objects total -- just scan through the allObjects Bag. Scanning through all stored locations is potentially slow, probably O(H), where H is the number of hash table buckets in the objectHash hash table -- you'd have to get a hash table iterator and iterate through it. Removing all objects at a given location is O(O), where O is the number of objects at that location. Clearing the hash table is O(1) discounting GC.

Example Usage. Here is an example of a simple subclass which allows locations to be positive, non-zero integers:


    public class PositiveIntegerSparseField extends SparseField
        {
        // note we return an Integer, not an Object.  If the user wants
        // an abstract supermethod for all SparseFields, he can use
        // getRawObjectLocation instead.  Java's a pain sometimes
            
        public Integer getObjectLocation(Object obj)
            {
            return (Integer) super.getRawObjectLocation(obj);
            }

        // note we explicitly state that the location has to be an integer

        public boolean setObjectLocation(Object obj, final Integer location)
            {
            if (location.intValue() > 0)  // it's a valid location
            return super.setObjectLocation(obj, location);
            else return false;
            }
        }
    
    
See Also:
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static class 
    Objects stored in SparseField's locationAndIndexHash table.
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    All the objects in the sparse field.
    static final int
    Pass this into buildMap to indicate that it should make a map of any size it likes.
    static final int
    The size of an initial bag
    static final int
    A bag must be larger than its contents by this ratio to be replaced replaceLargeBags is true
    LocationAndIndex objects (locations and indexes into the allObjects array) hashed by Object.
    static final int
    No bags smaller than this size will be replaced regardless of the setting of replaceLargeBags
    Bags of objects hashed by location.
    boolean
    Should we remove bags in the field if they have been emptied, and let them GC, or should we keep them around? This doesn't include the allObjects bag.
    boolean
    When a bag drops to one quarter capacity, should we replace it with a new bag? This doesn't include the allObjects bag.
    static final int
    A bag to be replaced will be shrunk to this ratio if replaceLargeBags is true
  • Constructor Summary

    Constructors
    Modifier
    Constructor
    Description
    protected
     
    protected
     
  • Method Summary

    Modifier and Type
    Method
    Description
    buildMap(int size)
    Creates a map of the provided size (or any size it likes if ANY_SIZE is passed in).
    buildMap(Map other)
    Creates a Map which is a copy of another.
    Deletes everything, returning all the objects as a Bag (which you can freely use and modify).
    boolean
    Returns true if the object is in the field.
    final Bag
    Returns all the objects in the Sparse Field.
    int
    Returns the index of the object in the allObjects Bag, if the object exists, else returns -1.
    Returns a bag containing all the objects at a given location, or null when there are no objects at the location.
    Returns a bag containing all the objects at the same location as a given object, including the object itself, or null if the object is not in the Field.
    getObjectsAtLocations(Bag locations, Bag result)
    For each location, puts all object at that location into the result bag.
    protected final Object
    Get the location of the provided object, or null if the object does not exist.
    protected final Bag
    This method is called by getObjectsAtLocation(location) so you can override getObjectsAtLocation() to customize it in certain ways (which is rare).
    Iterates over all objects.
    Iterates [somewhat inefficiently] over all bags of objects grouped by location.
    final int
    Returns the number of objects at a given location.
    int
    Returns the number of objects at the same location as a given object, including the object itself, or 0 if the object is not in the SparseField.
    Removes an object if it exists.
    Removes objects at the given location, and returns a bag of them, or null of no objects are at that location.
    protected boolean
    Changes the location of an object, or adds if it doesn't exist yet.
    int
    Returns the number of elements in the field

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • removeEmptyBags

      public boolean removeEmptyBags
      Should we remove bags in the field if they have been emptied, and let them GC, or should we keep them around? This doesn't include the allObjects bag.
    • replaceLargeBags

      public boolean replaceLargeBags
      When a bag drops to one quarter capacity, should we replace it with a new bag? This doesn't include the allObjects bag.
    • INITIAL_BAG_SIZE

      public static final int INITIAL_BAG_SIZE
      The size of an initial bag
      See Also:
    • MIN_BAG_SIZE

      public static final int MIN_BAG_SIZE
      No bags smaller than this size will be replaced regardless of the setting of replaceLargeBags
      See Also:
    • LARGE_BAG_RATIO

      public static final int LARGE_BAG_RATIO
      A bag must be larger than its contents by this ratio to be replaced replaceLargeBags is true
      See Also:
    • REPLACEMENT_BAG_RATIO

      public static final int REPLACEMENT_BAG_RATIO
      A bag to be replaced will be shrunk to this ratio if replaceLargeBags is true
      See Also:
    • locationAndIndexHash

      public Map locationAndIndexHash
      LocationAndIndex objects (locations and indexes into the allObjects array) hashed by Object. Ideally you would store only immutable or hash-by-pointer objects, el se they'll get lost in the HashMap.
    • objectHash

      public Map objectHash
      Bags of objects hashed by location. Do not rely on these bags always being the same objects.
    • allObjects

      public Bag allObjects
      All the objects in the sparse field. For fast scans. Do not rely on this bag always being the same object.
    • ANY_SIZE

      public static final int ANY_SIZE
      Pass this into buildMap to indicate that it should make a map of any size it likes.
      See Also:
  • Constructor Details

    • SparseField

      protected SparseField()
    • SparseField

      protected SparseField(SparseField other)
  • Method Details

    • buildMap

      public Map buildMap(Map other)
      Creates a Map which is a copy of another. By default, HashMap is used.
    • buildMap

      public Map buildMap(int size)
      Creates a map of the provided size (or any size it likes if ANY_SIZE is passed in). By default, HashMap is used.
    • getObjectIndex

      public int getObjectIndex(Object obj)
      Returns the index of the object in the allObjects Bag, if the object exists, else returns -1.
    • exists

      public boolean exists(Object obj)
      Returns true if the object is in the field.
    • size

      public int size()
      Returns the number of elements in the field
    • getRawObjectLocation

      protected final Object getRawObjectLocation(Object obj)
      Get the location of the provided object, or null if the object does not exist. Subclasses should create a getObjectLocation(Object obj) method which returns a location type appropriate for your kind of Sparse Field.
    • numObjectsAtLocation

      public final int numObjectsAtLocation(Object location)
      Returns the number of objects at a given location.
    • getObjectsAtLocation

      public Bag getObjectsAtLocation(Object location)
      Returns a bag containing all the objects at a given location, or null when there are no objects at the location. You should NOT MODIFY THIS BAG. This is the actual container bag, and modifying it will almost certainly break the Sparse Field object. If you want to modify the bag, make a copy and modify the copy instead, using something along the lines of new Bag(foo.getObjectsAtLocation(location)) . Furthermore, changing values in the Sparse Field may result in a different bag being used -- so you should not rely on this bag staying valid. The default implementation of this method simply calls getRawObjectsAtLocation(), but you may need to override it for more custom functionality (which is rare).
    • getRawObjectsAtLocation

      protected final Bag getRawObjectsAtLocation(Object location)
      This method is called by getObjectsAtLocation(location) so you can override getObjectsAtLocation() to customize it in certain ways (which is rare). All internal methods in SparseField instead call getRawObjectsAtLocation(). Returns a bag containing all the objects at a given location, or null when there are no objects at the location. You should NOT MODIFY THIS BAG. This is the actual container bag, and modifying it will almost certainly break the Sparse Field object. If you want to modify the bag, make a copy and modify the copy instead, using something along the lines of new Bag(foo.getObjectsAtLocation(location)) . Furthermore, changing values in the Sparse Field may result in a different bag being used -- so you should not rely on this bag staying valid.
    • getObjectsAtLocationOfObject

      public Bag getObjectsAtLocationOfObject(Object obj)
      Returns a bag containing all the objects at the same location as a given object, including the object itself, or null if the object is not in the Field. You should NOT MODIFY THIS BAG. This is the actual container bag, and modifying it will almost certainly break the Sparse Field object. If you want to modify the bag, make a copy and modify the copy instead, using something along the lines of new Bag(foo.getObjectsAtLocation(location)) . Furthermore, changing values in the Sparse Field may result in a different bag being used -- so you should not rely on this bag staying valid.
    • numObjectsAtLocationOfObject

      public int numObjectsAtLocationOfObject(Object obj)
      Returns the number of objects at the same location as a given object, including the object itself, or 0 if the object is not in the SparseField.
    • removeObjectsAtLocation

      public Bag removeObjectsAtLocation(Object location)
      Removes objects at the given location, and returns a bag of them, or null of no objects are at that location. The Bag may be empty, or null, if there were no objects at that location. You can freely modify this bag.
    • clear

      public Bag clear()
      Deletes everything, returning all the objects as a Bag (which you can freely use and modify). If you need the Bag, then this is a useful method -- otherwise it might in fact be faster to just make a brand new Sparse Field and let the garbage collector do its magic.
    • remove

      public Object remove(Object obj)
      Removes an object if it exists. Returns its location, or null if the object didn't exist.
    • setObjectLocation

      protected boolean setObjectLocation(Object obj, Object location)
      Changes the location of an object, or adds if it doesn't exist yet. Returns false if the object could not be set to that location. For example: you cannot put null objects or null locations into a Sparse Field. Subclasses may have further restrictions on locations.
    • getAllObjects

      public final Bag getAllObjects()
      Returns all the objects in the Sparse Field. Do NOT modify the bag that you receive from this method -- it is used internally. If you wish to modify the Bag you receive, make a copy of the Bag first, using something like new Bag(foo.getAllObjects()).
    • getObjectsAtLocations

      public Bag getObjectsAtLocations(Bag locations, Bag result)
      For each location, puts all object at that location into the result bag. Returns the result bag. If the provided result bag is null, one will be created and returned.
    • iterator

      public Iterator iterator()
      Iterates over all objects. NOT fail-fast, and remove() not supported. Use this method only if you're woozy about accessing allObject.numObjs and allObject.objs directly. For the fastest scan, you can do:

      for(int x=0;xinvalid input: '<'field.allObjects.numObjs;x++) ... field.allObjects.objs[x] ...

      ... but do NOT modify the allObjects.objs array.

    • locationBagIterator

      public Iterator locationBagIterator()
      Iterates [somewhat inefficiently] over all bags of objects grouped by location. Only used by SparseFieldPortrayal -- generally this should not be interesting to you.