//
// $Id: HashSet.h,v 1.23 2007/03/06 20:42:19 will_mason Exp $
//
// vi: set ft=objc:

/*
 * ObjectiveLib - a library of containers and algorithms for Objective-C
 *
 * Copyright (c) 2004-2007
 * Will Mason
 *
 * Portions:
 *
 * Copyright (c) 1994
 * Hewlett-Packard Company
 *
 * Copyright (c) 1996,1997
 * Silicon Graphics Computer Systems, Inc.
 *
 * Copyright (c) 1997
 * Moscow Center for SPARC Technology
 *
 * Copyright (c) 1999 
 * Boris Fomitchev
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * You may contact the author at will_mason@users.sourceforge.net.
 */

#if !defined(HASHSET_OL_GUARD)
#define HASHSET_OL_GUARD

#include <ObjectiveLib/Iterator.h>
#include <ObjectiveLib/Functional.h>

/**
 * @internal The hash table that provides the underlying data structure
 * for OLHashSet and OLHashMultiSet
 */
@class OLHashTable;

/**
 * @class OLHashSet HashSet.h ObjectiveLib/HashSet.h
 *
 * A hash table based set. The class provides very similar functionality to OLSet,
 * but it uses a hash table as the underlying data structure. Like OLSet it is a
 * container of unique objects, and attempting to insert an object that is already
 * in the set will fail. However, there are important differences to OLSet due to
 * the use of the hash table as the data structure. Searching for an object in a
 * hash set is inherently very fast, which is the main reason to choose to use
 * OLHashSet over OLSet. Another difference is that, unlike OLSet, items in a
 * hash set do not appear in a predictable order. Of course, the items are not
 * ordered randomly, but the order will depend on the result of the hash function
 * used for elements in the hash set.
 *
 * OLHashSet requires a @ref Functors "function object" to test elements for
 * equality, as equal elements are grouped together. If no function is
 * explicitly given, then OLEqualTo is used.
 *
 * @note It is important only to place objects of the same type in a hash set. If
 * a mixture of types is used then different hash functions will also be used,
 * resulting in corruption of the distribution of elements in the hash table.
 *
 * @sa OLHashIterator, OLSet
 *
 * @ingroup Containers
 */
@interface OLHashSet :
#if defined(OL_NO_OPENSTEP)
    Object <OLStreamable>
#else
    NSObject <OLStreamable, NSCopying, NSCoding>
#endif
{
@protected
    /**
     * The hash table that provides the underlying data structure
     */
    OLHashTable* table;
}

/**
 * Create and return a new hash set.
 *
 * @note If OpenStep is present the returned object will be autoreleased
 * before being returned.
 *
 * @return a new hash set
 */
+ (id) hashSet;

/**
 * Create and return a new hash set. The hash set is initialized
 * with the contents of the range <tt>[first, last)</tt>.
 *
 * @pre All objects in the range <tt>[first, last)</tt> must respond to the message @c hash.
 *
 * @note If OpenStep is present the returned object will be autoreleased
 * before being returned.
 *
 * @param first the first in the range of elements to insert
 * @param last one beyond the last in the range of elements to insert
 * @return a new hash set
 */
+ (id) hashSetFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last;

/**
 * Create and return a new hash set. The hash set is initialized
 * with the contents of @a right.
 *
 * @note If OpenStep is present the returned object will be autoreleased
 * before being returned.
 *
 * @param right the hash set to copy
 * @return a new hash set
 */
+ (id) hashSetWithHashSet: (OLHashSet*)right;

/**
 * @name Initializers and Deallocators
 */
/* @{ */
/**
 * Initialize the set. The set is empty and uses OLEqualTo to test for equality.
 *
 * @return a reference to this set
 */
- (id) init;

/**
 * Initialize the set. The set uses the comparison
 * function OLEqualTo to test for equality, and inserts all elements in
 * the range <tt>[first, last)</tt>.
 *
 * @pre All objects in the range <tt>[first, last)</tt> must respond to the message @c hash.
 *
 * @param first the first in the range of elements to insert
 * @param last one beyond the last in the range of elements to insert
 * @return a reference to this set
 */
- (id) initFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last;

/**
 * Initialize the set. The set uses the comparison
 * function OLEqualTo to test for equality, and inserts all elements in
 * the range <tt>[first, last)</tt>. The parameter @a size is used as a hint for
 * the desired hash table size. The table size will grow as needed, so it is not crucial
 * that this parameter carry any particular meaning. However, providing a size can
 * reduce the number of memory allocations required to build the table.
 *
 * @pre All objects in the range <tt>[first, last)</tt> must respond to the message @c hash.
 *
 * @param first the first in the range of elements to insert
 * @param last one beyond the last in the range of elements to insert
 * @param size a hint as to the desired hash table size
 * @return a reference to this set
 */
- (id) initFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last tableSize: (unsigned)size;

/**
 * Initialize the set. The set uses the comparison
 * function @a eq to test for equality, and inserts all elements in
 * the range <tt>[first, last)</tt>. The parameter @a size is used as a hint for
 * the desired hash table size. The table size will grow as needed, so it is not crucial
 * that this parameter carry any particular meaning. However, providing a size can
 * reduce the number of memory allocations required to build the table.
 *
 * @pre All objects in the range <tt>[first, last)</tt> must respond to the message @c hash.
 *
 * @param first the first in the range of elements to insert
 * @param last one beyond the last in the range of elements to insert
 * @param size a hint as to the desired hash table size
 * @param eq the @ref Functors "function object" used to test elements for equality
 * @return a reference to this set
 */
#if defined(OL_NO_OPENSTEP)
- (id) initFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last tableSize: (unsigned)size keyEqual: (Object<OLBoolBinaryFunction>*)eq;
#else
- (id) initFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last tableSize: (unsigned)size keyEqual: (NSObject<OLBoolBinaryFunction>*)eq;
#endif

#if !defined(OL_NO_OPENSTEP)
/**
 * Initialize the set. This initializer creates a new set
 * from an archive and returns it.
 *
 * @post The set returned will be identical to the set
 * saved to the archive using the #encodeWithCoder: message.
 *
 * @param decoder the coder which will decode the archived set
 * @return a reference to this set
 */
- (id) initWithCoder: (NSCoder*)decoder;
#endif

/**
 * Initialize the set. The set copies the comparison function
 * and all elements from @a right.
 *
 * @param right the hash set to copy
 * @return a reference to this set
 */
- (id) initWithHashSet: (OLHashSet*)right;

- (id) initWithObjectInStream: (OLObjectInStream*)stream;

/**
 * Initialize the set. The set is initially empty and uses 
 * OLEqualTo to test elements for equality. The parameter @a size is a hint as to the
 * desired capacity for the hash table. The table will grow as needed, so it isn't
 * strictly necessary to specify a table size, but providing a valid hint can reduce the
 * number of memory allocations performed.
 *
 * @param size a hint as to the desired hash table size
 * @return a reference to this set
 */
- (id) initWithTableSize: (unsigned)size;

/**
 * Initialize the set. The set is initially empty and uses 
 * @a eq to test elements for equality. The parameter @a size is a hint as to the
 * desired capacity for the hash table. The table will grow as needed, so it isn't
 * strictly necessary to specify a table size, but providing a valid hint can reduce the
 * number of memory allocations performed.
 *
 * @param size a hint as to the desired hash table size
 * @param eq the @ref Functors "function object" used to test elements for equality
 * @return a reference to this set
 */
#if defined(OL_NO_OPENSTEP)
- (id) initWithTableSize: (unsigned)size keyEqual: (Object<OLBoolBinaryFunction>*)eq;
#else
- (id) initWithTableSize: (unsigned)size keyEqual: (NSObject<OLBoolBinaryFunction>*)eq;
#endif

/**
 * Finalize the set and deallocate any allocated memory.
 */
#if defined(OL_NO_OPENSTEP)
- (id) free;
#else
- (void) dealloc;
#endif
/* @} */

/**
 * Return an iterator pointing to the beginning of the sequence.
 *
 * @note If OpenStep is present the returned object will be autoreleased
 * before being returned.
 *
 * @return an iterator pointing to the first element
 */
- (OLHashIterator*) begin;

/**
 * Remove all elements.
 */
- (void) clear;

/**
 * Compare this hash set to another object. If the other object is of type OLHashSet, each
 * of the contained objects is compared to the corresponding object in @a other by
 * calling the @c compare: method.
 *
 * @param other the object with which to compare this one
 * @return a value greater than, equal to, or less than zero accoringly as this object
 * is greater than, equal to, or less than @a other
 */
- (int) compare: (id)other;

#if defined(OL_NO_OPENSTEP)
/**
 * Make a copy of this hash set.
 *
 * @return the copy
 */
- (id) copy;
#else
/**
 * Make a copy of this hash set allocating memory from the given zone.
 *
 * @param zone the zone from which to allocate memory
 * @return the copy
 */
- (id) copyWithZone: (NSZone*)zone;
#endif

/**
 * Return the number of elements equal to @a value.
 *
 * @pre @a value must respond to the message @c hash.
 *
 * @param value the value to search for in the set
 * @return the number of instances of @a value
 */
- (unsigned) count: (id)value;

/**
 * Test whether the set is empty.
 *
 * @return YES if the set is empty, NO otherwise
 */
- (BOOL) empty;

#if !defined(OL_NO_OPENSTEP)
/**
 * Encode the map. The map is saved to an archive using @a encoder. The map
 * will be retrieved from the archive using the initializer #initWithCoder:.
 *
 * @param encoder the coder which will save the bit set to the archive
 */
- (void) encodeWithCoder: (NSCoder*)encoder;
#endif

/**
 * Return an iterator pointing to one position beyond the end of the sequence.
 *
 * @note If OpenStep is present the returned object will be autoreleased
 * before being returned.
 *
 * @return an iterator pointing to one position beyond the last element
 */
- (OLHashIterator*) end;

/**
 * Return a range whose elements are equal to @a value. The returned pair contains two instances
 * of OLHashIterator delimiting the range of elements.
 *
 * @pre @a value must respond to the message @c hash.
 *
 * @note If OpenStep is present the returned object will be autoreleased
 * before being returned.
 *
 * @param value the value for which to search
 * @return a pair of OLHashIterator instances that define the range of elements
 * in the set equal to @a value
 */
- (OLPair*) equalRange: (id)value;

/**
 * Remove the element designated by @a where.
 *
 * @pre @a where must point to an element in this set.
 *
 * @param where an iterator designating the element to remove
 */
- (void) erase: (OLHashIterator*)where;

/**
 * Remove a range of elements. All elements in the range <tt>[first, last)</tt> will
 * be removed from the set.
 *
 * @pre @a first and @a last must refer to elements in this set.
 *
 * @param first the first in the range of elements to remove
 * @param last one position beyond the last in the range of elements to remove
 */
- (void) eraseFrom: (OLHashIterator*)first to: (OLHashIterator*)last;

/**
 * Erase all instances of @a value. The set is searched for @a value, all instances are removed
 * from the set and the number removed is returned.
 *
 * @pre @a value must respond to the message @c hash.
 *
 * @param value the value to remove from the set
 * @return the number of elements removed
 */
- (unsigned) eraseValue: (id)value;

/**
 * Find an element. The element @a object is searched for in the set and an iterator to its
 * location is returned. If the element does not exist in the set, then the returned
 * iterator will be equal to the iterator returned by #end.
 *
 * @pre @a object must respond to the message @c hash.
 *
 * @note If OpenStep is present the returned object will be autoreleased
 * before being returned.
 *
 * @param object the value for which to search
 * @return an iterator pointing to the location of @a object, or an iterator equal to that
 * returned by #end if @a object does not exist
 */
- (OLHashIterator*) find: (id)object;

/**
 * Insert an object into the set. An attempt is made to insert @a object into the set,
 * and an instance of OLPair is returned indicating the state of the insertion. The first
 * element in the returned pair is an instance of OLHashIterator indicating the
 * position of @a object in the set, and the second element of the returned pair is
 * an object that responds to the message @c boolValue. The message @c boolValue will
 * return YES if the insertion was successful, or NO if not.
 *
 * @pre The @a object to insert must respond to the message @c hash.
 *
 * @note If OpenStep is present the returned object will be autoreleased
 * before being returned.
 *
 * @param object the element to insert
 * @return an instance of OLPair indicating the position of @a object in the set and
 * whether the insertion succeeded or failed
 */
- (id) insert: (id)object;

/**
 * Insert a range of objects into the set. An attempt is made to insert all objects
 * in the range <tt>[first, last)</tt>, however there is no guarantee that any of
 * the elements in the range will actually be inserted if they already exist in the
 * set.
 *
 * @pre All objects in the range <tt>[first, last)</tt> must respond to the message @c hash.
 *
 * @param first the first in the range of objects to insert
 * @param last one position beyond the last in the range of objects to insert
 */
- (void) insertFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last;

/**
 * Test whether another set is equal to this one. Two sets are considered equal if they
 * contain the same number of objects and the objects are in the same order and each
 * object is equal to the corresponding object in the other set.
 *
 * @param object the object to test
 * @return YES if @a object is equal to this set
 */
- (BOOL) isEqual: (id)object;

/**
 * Return the function used to compare keys in the set.
 *
 * @return the @ref Functors "function object" that is used to compare keys
 */
#if defined(OL_NO_OPENSTEP)
- (Object<OLBoolBinaryFunction>*) keyEqual;
#else
- (NSObject<OLBoolBinaryFunction>*) keyEqual;
#endif

/**
 * Return the maxiumum number of objects that can be stored in a set. This limit is
 * theoretical, meaning that there is no guarantee that you can insert this many
 * objects into any given set. The memory conditions of the run-time system play an
 * important role.
 *
 * @return the maximum number of objects for a set
 */
- (unsigned) maxSize;

/**
 * Return the number of elements in the set.
 *
 * @return the number of elements
 */
- (unsigned) size;

/**
 * Swap this set with another one. All elements in @a right will be placed into
 * this set, and all elements in this set will be placed in @a right.
 *
 * @param right the set with which to swap this one
 */
- (void) swap: (OLHashSet*)right;

- (void) writeSelfToStream: (OLObjectOutStream*)stream;

@end

/**
 * @class OLHashMultiSet HashSet.h ObjectiveLib/HashSet.h
 *
 * A hash set that allows multiple instances of objects. Hash multiset is identical to
 * hash set expect that as many instances of a given object may be inserted as desired.
 *
 * @sa OLHashSet, OLHashIterator
 *
 * @ingroup Containers
 */
@interface OLHashMultiSet : OLHashSet
{
}

/**
 * Create and return a new hash set.
 *
 * @note If OpenStep is present the returned object will be autoreleased
 * before being returned.
 *
 * @return a new hash set
 */
+ (id) hashMultiSet;

/**
 * Create and return a new hash set. The hash set is initialized
 * with the contents of the range <tt>[first, last)</tt>.
 *
 * @pre All objects in the range <tt>[first, last)</tt> must respond to the message @c hash.
 *
 * @note If OpenStep is present the returned object will be autoreleased
 * before being returned.
 *
 * @param first the first in the range of elements to insert
 * @param last one beyond the last in the range of elements to insert
 * @return a new hash set
 */
+ (id) hashMultiSetFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last;

/**
 * Create and return a new hash set. The hash set is initialized
 * with the contents of @a right.
 *
 * @note If OpenStep is present the returned object will be autoreleased
 * before being returned.
 *
 * @note The argument @a right may be an instance of OLHashSet or of OLHashMultiSet.
 *
 * @param right the hash set to copy
 * @return a new hash set
 */
+ (id) hashMultiSetWithHashSet: (OLHashSet*)right;

/**
 * Insert an object into the set. The object is inserted and an instance of
 * OLHashIterator is returned indicating the position of @a object
 * in the set.
 *
 * @pre The @a object to insert must respond to the message @c hash.
 *
 * @note If OpenStep is present the returned object will be autoreleased
 * before being returned.
 *
 * @param object the element to insert
 * @return an instance of OLHashIterator pointing to the newly inserted object
 */
- (id) insert: (id)object;
- (void) insertFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last;

- (BOOL) isEqual: (id)object;

@end

#endif
