ftp.nice.ch/Attic/openStep/implementation/gnustep/sources/gstep-base-0.2.7.tgz#/gstep-base-0.2.7/src/o_hash.m

This is o_hash.m in view mode; [Download] [Up]

/* A hash table.
 * Copyright (C) 1993, 1994, 1995, 1996  Free Software Foundation, Inc.
 * 
 * Author: Albin L. Jones <Albin.L.Jones@Dartmouth.EDU>
 * Created: ??? ??? ?? ??:??:?? ??? 1993
 * Updated: Tue Mar 19 00:25:18 EST 1996
 * Serial: 96.03.19.33
 * 
 * This file is part of the GNUstep Base Library.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */

/**** Included Headers *******************************************************/

#include <Foundation/NSZone.h>
#include <gnustep/base/o_cbs.h>
#include <gnustep/base/numbers.h>
#include <gnustep/base/o_hash.h>

/**** Function Implementations ***********************************************/

/** Behind-the-Scenes functions **/

static inline o_hash_bucket_t *
_o_hash_pick_bucket_for_element(o_hash_t *hash,
				      o_hash_bucket_t *buckets,
				      size_t bucket_count,
				      const void *element)
{
  return buckets + (o_hash(o_hash_element_callbacks(hash),
				 element, hash)
		    % bucket_count);
}

static inline o_hash_bucket_t *
_o_hash_pick_bucket_for_node(o_hash_t *hash,
				   o_hash_bucket_t *buckets,
				   size_t bucket_count,
				   o_hash_node_t *node)
{
  return buckets + (o_hash(o_hash_element_callbacks(hash),
				 node->element, hash)
		    % bucket_count);
}

static inline o_hash_bucket_t *
_o_hash_bucket_for_element(o_hash_t *hash, const void *element)
{
  return _o_hash_pick_bucket_for_element(hash, hash->buckets,
					    hash->bucket_count, element);
}

static inline o_hash_bucket_t *
_o_hash_bucket_for_node(o_hash_t *hash, o_hash_node_t *node)
{
  return _o_hash_pick_bucket_for_node(hash, hash->buckets,
					    hash->bucket_count, node);
}

static inline void
_o_hash_link_node_into_bucket(o_hash_bucket_t *bucket,
				    o_hash_node_t *node)
{
  if (bucket->first_node != 0)
    bucket->first_node->prev_in_bucket = node;

  node->next_in_bucket = bucket->first_node;

  bucket->first_node = node;

  return;
}

static inline void
_o_hash_unlink_node_from_its_bucket(o_hash_node_t *node)
{
  if (node == node->bucket->first_node)
    node->bucket->first_node = node->next_in_bucket;

  if (node->prev_in_bucket != 0)
    node->prev_in_bucket->next_in_bucket = node->next_in_bucket;
  if (node->next_in_bucket != 0)
    node->next_in_bucket->prev_in_bucket = node->prev_in_bucket;

  node->prev_in_bucket = node->next_in_bucket = 0;

  return;
}

static inline void
_o_hash_link_node_into_hash(o_hash_t *hash,
				  o_hash_node_t *node)
{
  if (hash->first_node != 0)
    hash->first_node->prev_in_hash = node;
  node->next_in_hash = hash->first_node;
  hash->first_node = node;

  return;
}

static inline void
_o_hash_unlink_node_from_its_hash(o_hash_node_t *node)
{
  if (node == node->hash->first_node)
    node->hash->first_node = node->next_in_hash;

  if (node->prev_in_hash != 0)
    node->prev_in_hash->next_in_hash = node->next_in_hash;
  if (node->next_in_hash != 0)
    node->next_in_hash->prev_in_hash = node->prev_in_hash;

  node->prev_in_hash = node->next_in_hash = 0;

  return;
}

static inline void
_o_hash_add_node_to_bucket(o_hash_bucket_t *bucket,
				 o_hash_node_t *node)
{
  if (bucket != 0)
  {
    _o_hash_link_node_into_bucket(bucket, node);

    node->bucket = bucket;

    bucket->node_count += 1;
    bucket->element_count += 1;
  }

  return;
}

static inline void
_o_hash_add_node_to_its_bucket(o_hash_t *hash,
				     o_hash_node_t *node)
{
  _o_hash_add_node_to_bucket(_o_hash_bucket_for_node(hash, node),
				   node);
  return;
}

static inline void
_o_hash_add_node_to_hash(o_hash_t *hash, o_hash_node_t *node)
{
  if (hash != 0)
  {
    _o_hash_add_node_to_its_bucket(hash, node);

    _o_hash_link_node_into_hash(hash, node);

    node->hash = hash;

    hash->node_count += 1;
    hash->element_count += 1;
  }

  return;
}

static inline void
_o_hash_remove_node_from_its_bucket(o_hash_node_t *node)
{
  if (node->bucket != 0)
  {
    node->bucket->node_count -= 1;
    node->bucket->element_count -= 1;

    _o_hash_unlink_node_from_its_bucket(node);
  }

  return;
}

static inline void
_o_hash_remove_node_from_its_hash(o_hash_node_t *node)
{
  if (node->hash != 0)
  {
    node->hash->node_count -= 1;
    node->hash->element_count -= 1;

    _o_hash_unlink_node_from_its_hash(node);
  }

  _o_hash_remove_node_from_its_bucket(node);

  return;
}

static inline o_hash_bucket_t *
_o_hash_new_buckets(o_hash_t *hash, size_t bucket_count)
{
  return (o_hash_bucket_t *)NSZoneCalloc(o_hash_zone(hash),
					       bucket_count,
					  sizeof(o_hash_bucket_t));
}

static inline void
_o_hash_free_buckets(o_hash_t *hash, o_hash_bucket_t *buckets)
{
  if (buckets != 0)
    NSZoneFree(o_hash_zone(hash), buckets);
  return;
}

static inline void
_o_hash_remangle_buckets(o_hash_t *hash,
			       o_hash_bucket_t *old_buckets,
			       size_t old_bucket_count,
			       o_hash_bucket_t *new_buckets,
			       size_t new_bucket_count)
{
  size_t i;
  o_hash_node_t *node;
  for (i = 0; i < old_bucket_count; i++)
  {
    while ((node = old_buckets[i].first_node) != 0)
    {
      _o_hash_remove_node_from_its_bucket(node);
      _o_hash_add_node_to_bucket(_o_hash_pick_bucket_for_node(hash,
							     new_buckets,
							new_bucket_count,
								   node),
				       node);
    }
  }

  /* And that's that. */
  return;
}

static inline o_hash_node_t *
_o_hash_new_node(o_hash_t *hash, const void *element)
{
  o_hash_node_t *node;
  /* Allocate the space for a new node. */
  node = (o_hash_node_t *)NSZoneMalloc(o_hash_zone(hash),
					     sizeof(o_hash_node_t));

  if (node != 0)
  {
    /* Retain ELEMENT.  (It's released in `_o_hash_free_node()'.) */
    o_retain(o_hash_element_callbacks(hash), element, hash);

    /* Remember ELEMENT. */
    node->element = element;

    /* Associate NODE with HASH. */
    node->hash = hash;

    /* Zero out the various pointers. */
    node->bucket = 0;
    node->next_in_bucket = 0;
    node->next_in_hash = 0;
    node->prev_in_bucket = 0;
    node->prev_in_hash = 0;
  }

  return node;
}

static inline void
_o_hash_free_node(o_hash_node_t *node)
{
  if (node != 0)
  {
    o_hash_t *hash;
    /* Remember NODE's hash. */
    hash = node->hash;

    /* Release ELEMENT.  (It's retained in `_o_hash_new_node()'.) */
    o_release(o_hash_element_callbacks(hash),
		    (void *)node->element,
		    hash);

    /* Actually free the space hash aside for NODE. */
    NSZoneFree(o_hash_zone(hash), node);
  }

  /* And just return. */
  return;
}

static inline o_hash_node_t *
_o_hash_node_for_element(o_hash_t *hash, const void *element)
{
  o_hash_node_t *node = 0;

  if (element != o_hash_not_an_element_marker(hash))
  {
    o_hash_bucket_t *bucket = 0;

    /* Find the bucket in which the node for ELEMENT would be. */
    bucket = _o_hash_bucket_for_element(hash, element);

    /* Run through the nodes in BUCKET until we find one whose element
     * matches ELEMENT. */
    for (node = bucket->first_node;
         (node != 0) && !o_is_equal(o_hash_element_callbacks(hash),
                                      element,
                                      node->element,
                                      hash);
         node = node->next_in_bucket);
  }

  /* Note that if ELEMENT is bogus or if none of the nodes'
   * elements matches ELEMENT, then we naturally return 0. */
  return node;
}

static inline o_hash_node_t *
_o_hash_enumerator_next_node(o_hash_enumerator_t *enumerator)
{
  o_hash_node_t *node;
  /* Remember ENUMERATOR's current node. */
  node = enumerator->node;

  /* If NODE is a real node, then we need to increment ENUMERATOR's
   * current node to the next node in ENUMERATOR's hash. */
  if (node != 0)
    enumerator->node = enumerator->node->next_in_hash;

  /* Send back NODE. */
  return node;
}

/** Callbacks... **/

/* Return a hash index for HASH.  Needed for the callbacks below. */
size_t
_o_hash_hash(o_hash_t *hash)
{
  /* One might be tempted to do something simple here, but remember:
   * If two hash tables are equal they *must* hash to the same value! */

  /* FIXME: Code this. */
  return 0;
}

/* An (inefficient, but necessary) "retaining" function for hash tables. */
o_hash_t *
_o_hash_retain(o_hash_t *hash, o_hash_t *in_hash)
{
  /* Note that this works only because all the structures (hash, map
   * list, array) look alike at first...so we can get the zone of
   * one just like we can get the zone of any of them. */
  return o_hash_copy_with_zone(hash, o_hash_zone(in_hash));
}

/* Returns a collection of callbacks for use with hash tables. */
o_callbacks_t
o_callbacks_for_hash(void)
{
  o_callbacks_t hash_callbacks =
  {
    (o_hash_func_t) _o_hash_hash,
    (o_compare_func_t) 0,
    (o_is_equal_func_t) o_hash_is_equal_to_hash,
    (o_retain_func_t) _o_hash_retain,
    (o_release_func_t) o_hash_dealloc,
    (o_describe_func_t) o_hash_description,
    0
  };

  return hash_callbacks;
}

/** Resizing **/

size_t
o_hash_resize(o_hash_t *hash, size_t new_capacity)
{
  o_hash_bucket_t *new_buckets;
  /* Round NEW_CAPACITY up to the next power of two. */
  new_capacity = _o_next_power_of_two(new_capacity);

  /* Make a new hash of buckets. */
  new_buckets = _o_hash_new_buckets(hash, new_capacity);

  if (new_buckets != 0)
  {
    _o_hash_remangle_buckets(hash,
				   hash->buckets,
				   hash->bucket_count,
				   new_buckets,
				   new_capacity);

    _o_hash_free_buckets(hash, hash->buckets);

    hash->buckets = new_buckets;
    hash->bucket_count = new_capacity;

  }

  /* Return the new capacity. */
  return hash->bucket_count;
}

size_t
o_hash_rightsize(o_hash_t *hash)
{
  /* FIXME: Now, this is a guess, based solely on my intuition.  If anyone
   * knows of a better ratio (or other test, for that matter) and can
   * provide evidence of its goodness, please get in touch with me, Albin
   * L. Jones <albin.l.jones@dartmouth.edu>. */

  if (3 * hash->node_count > 4 * hash->bucket_count)
  {
    return o_hash_resize(hash, hash->bucket_count + 1);
  }
  else
  {
    return hash->bucket_count;
  }
}
/** Statistics **/

size_t
o_hash_count(o_hash_t *hash)
{
  return hash->element_count;
}

size_t
o_hash_capacity(o_hash_t *hash)
{
  return hash->bucket_count;
}

int
o_hash_check(o_hash_t *hash)
{
  /* FIXME: Code this. */
  return 0;
}
/** Searching **/

int
o_hash_contains_element(o_hash_t *hash, const void *element)
{
  o_hash_node_t *node;
  node = _o_hash_node_for_element(hash, element);

  return node != 0;
}

const void *
o_hash_element(o_hash_t *hash, const void *element)
{
  o_hash_node_t *node;
  /* Try and find the node for ELEMENT. */
  node = _o_hash_node_for_element(hash, element);

  if (node != 0)
    return node->element;
  else
    return o_hash_not_an_element_marker(hash);
}

const void **
o_hash_all_elements(o_hash_t *hash)
{
  size_t j;
  const void **array;
  o_hash_enumerator_t enumerator;

  /* FIXME: It probably shouldn't be the programmer's responsibility to
   * worry about freeing ARRAY.  Maybe we should be returning an NSArray? */

  /* Allocate space for ARRAY.  Remember that it is the programmer's
   * responsibility to free this by calling
   * `NSZoneFree(o_hash_zone(HASH), ARRAY)' */
  array = (const void **)NSZoneCalloc(o_hash_zone(hash),
				      hash->node_count + 1,
				      sizeof(void *));

  /* ENUMERATOR is an enumerator for HASH. */
  enumerator = o_hash_enumerator_for_hash(hash);

  /* Now we enumerate through the elements of HASH, adding them one-by-one
   * to ARRAY.  */
  for (j = 0; j < hash->node_count; j++)
    o_hash_enumerator_next_element(&enumerator, array + j);

  /* We terminate ARRAY with the `not_an_element_marker' for HASH. */
  array[j] = o_hash_not_an_element_marker(hash);

  /* And we're done. */
  return array;
}

int
o_hash_is_empty(o_hash_t *hash)
{
  return o_hash_count(hash) == 0;
}
/** Enumerating **/

/* WARNING: You should not alter a hash while an enumeration is
 * in progress.  The results of doing so are reasonably unpredictable.
 * With that in mind, read the following warnings carefully.  But
 * remember, DON'T MESS WITH A HASH WHILE YOU'RE ENUMERATING IT. */

/* IMPORTANT WARNING: Hash enumerators, as I have hash them up, have a
 * wonderous property.  Namely, that, while enumerating, one may add
 * new elements (i.e., new nodes) to the hash while an enumeration is
 * in progress (i.e., after `o_hash_enumerator_for_hash()' has been called), and
 * the enumeration remains the same. */

/* WARNING: The above warning should not, in any way, be taken as
 * assurance that this property of hash enumerators will be preserved
 * in future editions of the library.  I'm still thinking about
 * this. */

/* IMPORTANT WARNING: Enumerators have yet another wonderous property.
 * Once a node has been returned by
 * `_o_hash_enumerator_next_node()', it may be removed from the hash
 * without effecting the rest of the current enumeration.  For
 * example, to clean all of the nodes out of a hash, the following code
 * would work:
 * 
 * void
 * empty_my_hash(o_hash_t *hash)
 * {
 *   o_hash_enumerator_t enumerator = o_hash_enumerator_for_hash(hash);
 *   o_hash_node_t *node;
 * 
 *   while ((node = _o_hash_enumerator_next_node(&enumerator)) != 0)
 *   {
 *     _o_hash_remove_node_from_its_hash(node);
 *     _o_hash_free_node(node);
 *   }
 * 
 *   return;
 * }
 * 
 * (In fact, this is the code currently being used below in the
 * function `o_hash_empty()'.)  But again, this is not to be taken
 * as an assurance that this behaviour will persist in future versions
 * of the library. */

/* EXTREMELY IMPORTANT WARNING: The purpose of this warning is point
 * out that, at this time, various (i.e., many) functions depend on
 * the behaviours outlined above.  So be prepared for some serious
 * breakage when you go fudging around with these things. */

o_hash_enumerator_t
o_hash_enumerator_for_hash(o_hash_t *hash)
{
  o_hash_enumerator_t enumerator;
  /* Make sure ENUMERATOR knows its hash. */
  enumerator.hash = hash;

  /* Start ENUMERATOR at HASH's first node. */
  enumerator.node = hash->first_node;

  return enumerator;
}

int
o_hash_enumerator_next_element(o_hash_enumerator_t *enumerator,
				     const void **element)
{
  o_hash_node_t *node;
  /* Get the next node in the enumeration represented by ENUMERATOR. */
  node = _o_hash_enumerator_next_node(enumerator);

  if (node != 0)
  {
    /* NODE is real, so we pull the information out of it that we need. 
     * Note that we check to see whether ELEMENT and COUNT are non-`0'. */
    if (element != 0)
      *element = node->element;

    /* Since we weren't at the end of our enumeration, we return ``true''. */
    return 1;
  }
  else /* (node == 0) */
  {
    /* If NODE isn't real, then we return the bogus element indicator and
     * a zero count. */
    if (element != 0)
      *element = o_hash_not_an_element_marker(enumerator->hash);

    /* Since we're at the end of the enumeration, we return ``false''. */
    return 0;
  }
}
/** Adding... **/

inline const void *
o_hash_add_element_known_absent(o_hash_t *hash,
				      const void *element)
{
  /* Note that we *do not* use the callback functions to test for
   * the presence of the bogus element.  Is is perfectly permissible for
   * elements which are "equal" (but not equal) to the "not an
   * element marker" to be added to HASH. */
  if (element == o_hash_not_an_element_marker(hash))
  {
     /* FIXME: We should do something useful here,
      * like raise an exception. */
     abort();
  }
  else if ((_o_hash_node_for_element(hash, element)) != 0)
  {
    /* FIXME: We should do something useful here,
     * like raise an exception. */
    abort();
  }
  else /* (element != bogus && !(element in hash)) */
  {
    o_hash_node_t *node;
    node = _o_hash_new_node(hash, element);

    if (node != 0)
    {
      /* Actually add NODE to HASH. */
      _o_hash_add_node_to_hash(hash, node);

      return node->element;
    }
    else /* (node == 0) */
      return o_hash_not_an_element_marker(hash);
  }
}

const void *
o_hash_add_element(o_hash_t *hash, const void *element)
{
  o_hash_node_t *node;

  /* First, we check for ELEMENT in HASH. */
  node = _o_hash_node_for_element(hash, element);

  if (node == 0)
  {
    /* ELEMENT isn't in HASH, so we can add it with impunity. */
    return o_hash_add_element_known_absent(hash, element);
  }
  else /* (node != 0) */
  {
    /* Remember: First retain, then release. */
    o_retain(o_hash_element_callbacks(hash), element, hash);
    o_release(o_hash_element_callbacks(hash),
		    (void *)(node->element),
		    hash);
    return node->element = element;
  }
}

/* If (any item "equal" to) ELEMENT is in HASH, then that member of HASH is
 * returned.  Otherwise, the "not an element marker" for HASH is returned
 * and ELEMENT is added to HASH. */
const void *
o_hash_add_element_if_absent(o_hash_t *hash, const void *element)
{
  o_hash_node_t *node;

  /* First, we check for ELEMENT in HASH. */
  node = _o_hash_node_for_element(hash, element);

  if (node == 0)
  {
    /* ELEMENT isn't in HASH, so we can add it with impunity. */
    o_hash_add_element_known_absent(hash, element);

    /* To indicate that ELEMENT was not in HASH, we return the bogus
     * element indicator. */
    return o_hash_not_an_element_marker(hash);
  }
  else /* (node != 0) */
    return node->element;
}
/** Removing **/

void
o_hash_remove_element(o_hash_t *hash, const void *element)
{
  o_hash_node_t *node;
  node = _o_hash_node_for_element(hash, element);

  if (node != 0)
  {
    /* Pull NODE out of HASH. */
    _o_hash_remove_node_from_its_hash(node);

    /* Free up NODE. */
    _o_hash_free_node(node);
  }

  return;
}
/** Emptying **/

void
o_hash_empty(o_hash_t *hash)
{
  o_hash_enumerator_t enumerator;
  o_hash_node_t *node;
  /* Get an element enumerator for HASH. */
  enumerator = o_hash_enumerator_for_hash(hash);

  /* Just step through the nodes of HASH and wipe them out, one after
   * another.  Don't try this at home, kids!  Note that, under ordinary
   * circumstances, this would be a verboten use of hash enumerators.  See
   * the warnings with the enumerator functions for more details. */
  while ((node = _o_hash_enumerator_next_node(&enumerator)) != 0)
  {
    _o_hash_remove_node_from_its_hash(node);
    _o_hash_free_node(node);
  }

  /* And return. */
  return;
}
/** Creating **/

o_hash_t *
o_hash_alloc_with_zone(NSZone * zone)
{
  o_hash_t *hash;
  /* Get a new hash, using basic methods. */
  hash = _o_hash_alloc_with_zone(zone);

  return hash;
}

o_hash_t *
o_hash_alloc(void)
{
  return o_hash_alloc_with_zone(0);
}

o_hash_t *
o_hash_with_callbacks(o_callbacks_t callbacks)
{
  return o_hash_init_with_callbacks(o_hash_alloc(), callbacks);
}

o_hash_t *
o_hash_with_zone_with_callbacks(NSZone * zone,
				      o_callbacks_t callbacks)
{
  return o_hash_init_with_callbacks(o_hash_alloc_with_zone(zone),
					  callbacks);
}

o_hash_t *
o_hash_with_zone(NSZone * zone)
{
  return o_hash_init(o_hash_alloc_with_zone(zone));
}

o_hash_t *
o_hash_of_char_p(void)
{
  return o_hash_with_callbacks(o_callbacks_for_char_p);
}

o_hash_t *
o_hash_of_non_owned_void_p(void)
{
  return o_hash_with_callbacks(o_callbacks_for_non_owned_void_p);
}

o_hash_t *
o_hash_of_owned_void_p(void)
{
  return o_hash_with_callbacks(o_callbacks_for_owned_void_p);
}

o_hash_t *
o_hash_of_int(void)
{
  return o_hash_with_callbacks(o_callbacks_for_int);
}

o_hash_t *
o_hash_of_int_p(void)
{
  return o_hash_with_callbacks(o_callbacks_for_int_p);
}

o_hash_t *
o_hash_of_id(void)
{
  return o_hash_with_callbacks(o_callbacks_for_id);
}
/** Initializing **/

o_hash_t *
o_hash_init_with_callbacks(o_hash_t *hash,
				 o_callbacks_t callbacks)
{
  if (hash != 0)
  {
    size_t capacity = 10;
    /* Make a note of the callbacks for HASH. */
    hash->callbacks = o_callbacks_standardize(callbacks);

    /* Zero out the various counts. */
    hash->node_count = 0;
    hash->bucket_count = 0;
    hash->element_count = 0;

    /* Zero out the pointers. */
    hash->first_node = 0;
    hash->buckets = 0;

    /* Resize HASH to the given CAPACITY. */
    o_hash_resize(hash, capacity);
  }

  /* Return the newly initialized HASH. */
  return hash;
}

o_hash_t *
o_hash_init(o_hash_t *hash)
{
  return o_hash_init_with_callbacks(hash, o_callbacks_standard());
}

o_hash_t *
o_hash_init_from_hash(o_hash_t *hash, o_hash_t *old_hash)
{
  if (hash != 0)
  {
    o_hash_enumerator_t enumerator;
    const void *element;
    /* Make a note of the callbacks for HASH. */
    hash->callbacks = o_hash_element_callbacks(hash);

    /* Zero out the various counts. */
    hash->node_count = 0;
    hash->bucket_count = 0;
    hash->element_count = 0;

    /* Zero out the pointers. */
    hash->first_node = 0;
    hash->buckets = 0;

    /* Resize HASH to the given CAPACITY. */
    o_hash_resize(hash, o_hash_capacity(old_hash));

    /* Get an element enumerator for OLD_HASH. */
    enumerator = o_hash_enumerator_for_hash(old_hash);

    /* Add OLD_HASH's elements to HASH, one at a time. */
    while (o_hash_enumerator_next_element(&enumerator, &element))
      o_hash_add_element_known_absent(hash, element);
  }

  /* Return the newly initialized HASH. */
  return hash;
}

/** Destroying... **/

void
o_hash_dealloc(o_hash_t *hash)
{
  /* Remove all of HASH's elements. */
  o_hash_empty(hash);

  /* Free up the bucket array. */
  _o_hash_free_buckets(hash, hash->buckets);

  /* And finally, perform the ultimate sacrifice. */
  _o_hash_dealloc(hash);

  return;
}

/** Replacing... **/

/* If (some item "equal" to) ELEMENT is an element of HASH, then ELEMENT is
 * substituted for it.  (This is rather like the non-existant but perfectly
 * reasonable 'o_hash_add_element_if_present()'.) */
void
o_hash_replace_element(o_hash_t *hash, const void *element)
{
  o_hash_node_t *node;

  /* Lookup the node for ELEMENT. */
  node = _o_hash_node_for_element(hash, element);

  if (node != 0)
  {
    /* Remember: First retain the new element, then release the old
     * element, just in case they're the same. */
    o_retain(o_hash_element_callbacks(hash), element, hash);
    o_release(o_hash_element_callbacks(hash),
		    (void *)(node->element),
		    hash);
    node->element = element;
  }

  return;
}

/** Comparing... **/

/* Returns true if HASH1 is a superset of HASH2. */
int
o_hash_contains_hash(o_hash_t *hash1, o_hash_t *hash2)
{
  o_hash_enumerator_t enumerator;
  const void *element;
  enumerator = o_hash_enumerator_for_hash(hash2);

  while (o_hash_enumerator_next_element(&enumerator, &element))
    if (!o_hash_contains_element(hash1, element))
      return 0;

  return 1;
}

/* Returns true if HASH1 is both a superset and a subset of HASH2.
 * Checks to make sure HASH1 and HASH2 have the same number of
 * elements first. */
int
o_hash_is_equal_to_hash(o_hash_t *hash1, o_hash_t *hash2)
{
  size_t a,
         b;
  /* Count HASH1 and HASH2. */
  a = o_hash_count(hash1);
  b = o_hash_count(hash2);

  /* Check the counts. */
  if (a != b)
    return 0;

  /* If the counts match, then we do an element by element check. */
  if (!o_hash_contains_hash(hash1, hash2)
      || !o_hash_contains_hash(hash2, hash1))
    return 0;

  /* If we made it this far, HASH1 and HASH2 are the same. */
  return 1;
}

/* Returns true if HASH and OTHER_HASH have at least one element in
 * common. */
int
o_hash_intersects_hash(o_hash_t *hash, o_hash_t *other_hash)
{
  o_hash_enumerator_t enumerator;
  const void *element;
  /* Get an element enumerator for OTHER_HASH. */
  enumerator = o_hash_enumerator_for_hash(other_hash);

  while (o_hash_enumerator_next_element(&enumerator, &element))
    if (o_hash_contains_element(hash, element))
      return 1;

  return 0;
}

/** Copying... **/

/* Returns a new copy of OLD_HASH in ZONE. */
o_hash_t *
o_hash_copy_with_zone(o_hash_t *old_hash, NSZone * zone)
{
  o_hash_t *new_hash;
  /* Alloc the NEW_HASH, copying over the low-level stuff. */
  new_hash = _o_hash_copy_with_zone(old_hash, zone);

  /* Initialize the NEW_HASH. */
  o_hash_init_from_hash(new_hash, old_hash);

  /* Return the NEW_HASH. */
  return new_hash;
}

/* Returns a new copy of OLD_HASH, using the default zone. */
o_hash_t *
o_hash_copy(o_hash_t *old_hash)
{
  return o_hash_copy_with_zone(old_hash, 0);
}

/** Mapping... **/

/* WARNING: The mapping function FCN must be one-to-one on elements of
 * HASH.  I.e., for reasons of efficiency, `o_hash_map_elements()'
 * makes no provision for the possibility that FCN maps two unequal
 * elements of HASH to the same (or equal) elements.  The better way
 * to handle functions that aren't one-to-one is to create a new hash
 * and transform the elements of the first to create the elements of
 * the second. */
o_hash_t *
o_hash_map_elements(o_hash_t *hash,
			  const void *(*fcn)(const void *, const void *),
			  const void *user_data)
{
  o_hash_enumerator_t enumerator;
  o_hash_node_t *node;
  enumerator = o_hash_enumerator_for_hash(hash);

  while ((node = _o_hash_enumerator_next_node(&enumerator)) != 0)
  {
    const void *element;
    element = (*fcn)(node->element, user_data);

    /* Remember: First retain the new element, then release the old
     * element. */
    o_retain(o_hash_element_callbacks(hash), element, hash);
    o_release(o_hash_element_callbacks(hash),
		    (void *)(node->element),
		    hash);
    node->element = element;
  }

  return hash;
}

/** Miscellaneous **/

/* Removes the elements of HASH which do not occur in OTHER_HASH. */
o_hash_t *
o_hash_intersect_hash(o_hash_t *hash, o_hash_t *other_hash)
{
  o_hash_enumerator_t enumerator;
  o_hash_node_t *node;
  enumerator = o_hash_enumerator_for_hash(hash);

  while ((node = _o_hash_enumerator_next_node(&enumerator)) != 0)
    if (!o_hash_contains_element(other_hash, node->element))
    {
      _o_hash_remove_node_from_its_hash(node);
      _o_hash_free_node(node);
    }

  return hash;
}

/* Removes the elements of HASH which occur in OTHER_HASH. */
o_hash_t *
o_hash_minus_hash(o_hash_t *hash, o_hash_t *other_hash)
{
  o_hash_enumerator_t enumerator;
  o_hash_node_t *node;
  enumerator = o_hash_enumerator_for_hash(hash);

  /* FIXME: Make this more efficient by enumerating
   * over the smaller of the two hashes only. */
  while ((node = _o_hash_enumerator_next_node(&enumerator)) != 0)
    if (o_hash_contains_element(other_hash, node->element))
    {
      _o_hash_remove_node_from_its_hash(node);
      _o_hash_free_node(node);
    }

  return hash;
}

/* Adds to HASH those elements of OTHER_HASH not occurring in HASH. */
o_hash_t *
o_hash_union_hash(o_hash_t *hash, o_hash_t *other_hash)
{
  o_hash_enumerator_t enumerator;
  const void *element;

  enumerator = o_hash_enumerator_for_hash(other_hash);

  while (o_hash_enumerator_next_element(&enumerator, &element))
    o_hash_add_element_if_absent(hash, element);

  return hash;
}

/** Describing a hash table... **/

NSString *
o_hash_description(o_hash_t *hash)
{
/* FIXME: Fix this. 
  NSMutableString *string;
  NSString *gnirts;
  o_callbacks_t callbacks;
  o_hash_enumerator_t enumerator;
  const void *element;

  callbacks = o_hash_element_callbacks(hash);
  enumerator = o_hash_enumerator_for_hash(hash);
  string = [_o_hash_description(hash) mutableCopy];

  [[string retain] autorelease];

#define DESCRIBE(E) o_describe(callbacks, (E), hash)

  [string appendFormat:@"element_count = %d;\n", o_hash_count(hash)];
  [string appendFormat:@"not_an_element_marker = %s;\n",
          [DESCRIBE(o_hash_not_an_element_marker(hash)) cStringNoCopy]];
  [string appendString:@"elements = {\n"];

  while (o_hash_enumerator_next_element(&enumerator, &element))
    [string appendFormat:@"%s,\n", [DESCRIBE(element) cStringNoCopy]];

  [string appendFormat:@"%s};\n",
          [DESCRIBE(o_hash_not_an_element_marker(hash)) cStringNoCopy]];

#undef DESCRIBE

  gnirts = [[[string copy] retain] autorelease];

  [string release];

  return gnirts;
*/
  return nil;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.