Chapter 6 

The CONTAINER Cluster 
=====================


   The CONTAINER Cluster actually contains two different abstractions: 
   the  containers  and  the  iterators.  These  live  in  a  sort of 
   symbiosis with each other. 


   A container is an abstract data type in which one may store objects 
   of a given type  and expect to find them  again at some later time. 
   In roughly 90 % of all cases in which one uses arrays, linked lists 
   and the like, all one really  wants is this simple functionality of 
   a container. 


   We  must,  however,  distinguish  three  important  and  orthogonal 
   properties of containers: 

 * Unique. Can the same object occur more than once in the container? 

 * Ordered. Are  the objects  in the  container kept  in sorted order? 
   (This, of  course, only  makes sense if  the type  of the contained 
   objects has a  total order --- i.e.  conforms to COMPARABLE). This 
   can have an influence on the  performance of the search routine and 
   also on the behavior of the iterators. 

 * Keyed. Does one search for an object in the container by asking for 
   the object itself? Or does one search for an object by giving a key 
   , with which the object was presumably stored in the first place? 
     The  cluster  CONTAINER  deals  with  these  three  properties of 
   containers as follows: 

 * A container object is initialized with  a make routine that takes a 
   BOOLEAN argument only_once. If only_once is set to true, then 
   the container  will make  sure that  it never  contains duplicates. 
   Otherwise duplicates are allowed. 

 * For  each  kind of  container  there  is a  sorted  version  and an 
   unsorted  version. The  sorted version  has  a name  beginning with 
   SORTED_. 

 * The classes in  which objects are searched  for directly are called 
   collections; those  in which  one searches  using a  key are called 
   tables. Thus  there are  altogether four  basic types  of container 
   classes: collections, sorted collections, tables and sorted tables. 



Important remark 
================

   In  Eiffel  as  in every  language  using  reference  semantics the 
   question of  when two  entities are  `equal' is  complicated. Do we 
   mean that they refer to the same object (then we use the operator = 
   )? Or do we mean that the  objects they refer to are equal field by 
   field (then  we use  the function  equal resp.  is_equal from the 
   class GENERAL)? Or maybe deep_equal? This question is of course 
   important  in  connection with  the  uniqueness  property described 
   above. 


   In this cluster  (and in several others  such as GRAPH) `equality' 
   will always mean  value equality ---  i.e. it is  tested using is_ 
   equal. We shall not  mention this fact at  every point where it is 
   relevant, so the reader should always bear it in mind. 


   In  the sorted  containers, however,  the  question of  equality is 
   actually decided by the  order relation on the  items. In that case 
   `equal' is the same as not < and not >. 


   Because of  this fact the  collections are provided  with a feature 
   found_item and the tables with a feature found_key. If after a 
   search found is true,  then found_item is bound  to the item that 
   was found. Similarly after a table search that was successful found 
   _key is bound  to the key that  was found. At  first this may seem 
   ridiculous: don't we know what item (key) was found? We had to give 
   it as argument  to search. The  answer may be  `No', since found_ 
   item is only guaranteed to satisfy 

 x.is_equal (found_item) 

   if  x  was  the  argument  to  search.  This  does  not  of course 
   necessarily mean that x and found_item are the same object. 


   The basic  operations of the  abstract data type  container are the 
   following. 


   COLLECTION [  G ]                        TABLE  [ K, G  ] 

        add  (x : G)                       add (t, G,  k : K) 
      remove (x :  G)                       remove (k  : K) 
      search (x  : G)                      search  (k : K)
           ---                           replace (x : G, k : K)
           ---                               found_key : K 
           ---                          key (it : ITERATOR) : K 


             COLLECTION [ G ] and TABLE [ G ] 

                   count : INTEGER  
                   empty : BOOLEAN
                   found : BOOLEAN
                   found_item : G 
                   protected :  BOOLEAN 
                   iterator :  ITERATOR 
                   inside (it  : ITERATOR) : BOOLEAN 
                   item (it : ITERATOR) : G 

   The sematics of the  operations add and remove  do of course depend 
   on  the unique  property  of the  container.  If the  container was 
   created in unique mode, thenadd  will change nothing if the object 
   x is already  in the collection or  if the key  k already occurs in 
   the table. It is, however, not  an error to call add repeatedly for 
   the same object; add simply ignores the second and all further such 
   calls. 


   Similarly in unique mode remove removes  the one and only copy of x 
   if x occurs in the container  at all. In non-unique mode, however, 
   just one copy of x is removed; there is no way to control which one 
   (assuming the question `which one?' even makes sense). If the given 
   element was not in the container, then remove simply does nothing. 


   All containers in the  cluster are dynamic. That  is, they grow and 
   shrink  as  needed.  Hence  there is  no  attribute  full  in these 
   classes. 


   An iterator is a data structure  that allows one to visit one after 
   the other all the objects in a container --- each one exactly once. 
   An  iterator  ob belongs  to  one particular  container  object and 
   possesses  some  intimate knowledge  about  `its'  container, which 
   enables it to carry  out this traversal. A  container is also aware 
   of any  iterators associated  with it  (there may  be several). The 
   reason for  this will become  clear shortly.  This mutual knowledge 
   between containers and iterators is  the reason for the `symbiosis' 
   referred to above. 


   The basic  operations of  the abstract  data type  iterator are the 
   following. 


 first -- go  to "first" object  in container 

 forth  -- go to "next"  object in container 

 stop --  break off traversal 

 finished : BOOLEAN -- have we seen them all or stopped? 



   In addition  we have  an extended kind  of iterator  for the sorted 
   containers: it can traverse the container in the reverse direction. 
   These two--way iterators have two additional operations. 

 last -- go to  "last" object in container  
 back -- go to "previous" object in container 


   A  typical  use of  an  iterator for  a  container c  will  look as 
   follows. 

   c : COLLECTION [ G ] it : ITERATOR 
     ... 
     from 
        it := c.iterator 
     until 
        it.finished 
     loop 
        if c.item (it) = ... then 
           it.stop 
        else 
           .. do something with c.item (it) 
           it.forth 
        end 
     end 

   Here we did not need to  call it.first explicitly since the call to 
   c.iterator does this automatically. 


   Please  note  the  analogy  to  iterating  over  arrays.  There the 
   corresponding   program  segment  would  look  something  like  the 
   following. 

   a : ARRAY [ G ] 
   i : INTEGER 
   stop : BOOLEAN 
     ... 
   from 
      i := 1 
   until 
      stop or else  i > a.size
   loop  
      if a.item (i) =  ... then 
         stop := true 
      else 
         .. do something  with a.item (i) 
         i := i + 1 
      end
   end 

   Here the iterator takes over the role of the iteration index i. 


   The reader may have encountered Eiffel libraries in which container 
   classes had a  so called `cursor'  that could be  positioned on the 
   different objects in the container  using routines of the container 
   class like first and forth.  Such cursors are problematical for at 
   least two reasons. 

 * It happens  occasionally that  two or  more routines  are iterating 
   over the  same container  simultaneously. Since  there is  only one 
   cursor, these  routines are sure  to hinder each  other unless they 
   are  aware  of  their  competition  and  take  special  measures to 
   cooperate. 

 * When one adds objects to or  removes objects from the container one 
   is always faced  with the question  of what to  do with the cursor. 
   Suppose the cursor was standing on the object one has just removed; 
   should one now position it on the successor --- supposing there was 
   a successor, and if  not on the predecessor?  There doesn't seem to 
   be an answer that is optimal in all situations. 

Example
     
   Suppose a  routine f  iterates over  all elements  of a container 
   object c. For each element of c  it calls a routine g. Suppose it 
   happens that g also  iterates over all elements  in c. Then g will 
   move the cursor that f was counting on to remember where it was. 


   If f and g are aware of  this competition, then one of them can try 
   to remember  the position of  the cursor  and put it  back where it 
   found it when finished. (The  container class needs to support this 
   kind of thing  with routines called something  like mark and return 
   .) This  situation of  one routine  `knowing' about  another is not 
   exactly in the spirit of sound software design. 


   With the iterators on  the other hand we  do not have this problem. 
   There can be arbitrarily many iterators running over a container at 
   the same time. Since all  iterators are independent of one another, 
   they cannot interfere with each other. 


   What  about the  problem  with add  and  remove in  connection with 
   iterators? An  iterator could  get quite  confused if  someone went 
   about  removing objects  from  its container  while  it was  in the 
   process of traversing the container.  For this reason each iterator 
   `logs in' to its container when it begins a traversal with first or 
   last  and it  `logs out'  again when  it has  reached the  last (or 
   first) element  or been stopped  with stop. A  container will then 
   decline to  do an  add or  remove as  long as  some iterator  is in 
   progress.  More  precisely: one  of  the preconditions  of  add and 
   remove is that no iterator is active. The attribute protected gives 
   information about whether there is an active iterator. 


   In some  cases it is  important to  know in what  order an iterator 
   visits the elements  of its container. The  answer to this question 
   is simple. 

 * If  the container  is sorted  then the  iterator always  visits the 
   elements  in  non-decreasing  order  (  first  and  forth)  resp. 
   non-increasing order (last and back). 

 * If the  container is a  collection then the  iterator always visits 
   the elements in the order in which they were added to the container 
   --- that is, the oldest element first, the youngest last. 

 * If the container is a table  then no guarantee about visiting order 
   is made. 
     At first the designers of this  cluster were inclined to say that 
   for both unsorted  collections and unsorted  tables no guarantee is 
   made  about the  visiting  order of  an  iterator. But  that seemed 
   unnecessarily dogmatic. Since it is  not difficult to implement the 
   rule given above and since it  may be useful in many situations, it 
   was decided to implement collections this way. 


   The container  classes described so  far were  designed to optimize 
   speed. They  can be comparatively  wasteful of  space in situations 
   where one has a  large number of containers  with few elements. For 
   this purpose there is a  second category of containers: the `short' 
   containers. They have names  beginning with SHORT_such as SHORT_ 
   LIST.  They have  the same  syntax and  semantics as  their `long' 
   cousins but  are implemented in  such a  way as to  optimize use of 
   memory.  The  price  to pay  is  of  course in  the  speed  of many 
   operations.  As  a  rough  guide  one  can  say  that  the  `short' 
   containers should be used when they are only to contain a few dozen 
   objects. There  the loss in  efficiency is not  very noticeable and 
   the saving in space can be considerable. 


   In addition to the four basic container types mentioned above there 
   are some containers with somewhat deviant or restricted semantics. 


   An array can be used to  achieve a one--one mapping of the integers 
   into some set of objects. The object associated with integer i is 

   a.item (i) . 

   Often, though, we need to  represent a one--one mapping between two 
   sets neither  if which  is of type  INTEGER. For  this purpose one 
   uses  the abstract  data type  `dictionary'  --- often  also called 
   `associative array'. Its `indices' can be of almost any type. 


   The container cluster offers for  this purpose the class DICTIONARY 
   [ G, K  ]. The `index' or  `key' has to have  a type conforming to 
   HASHABLE but can otherwise be arbitrary --- for example STRING. If 
   d is of type DICTIONARY, then the object associated with a `key' k 
   is 

   d.at (k) . 

   Here   the   function  could   not  be   called  item   because  by 
   CONTAINER--convention item  is the  function taking  an argument of 
   type ITERATOR and  answering with the object  on which the iterator 
   is currently `standing'. 


   If an  array has an  index range  lower ... upper,  then one knows 
   that there  is some  object associated  with every  index i between 
   lower  and  upper  ---  it could  be  the  `object'  void.  With a 
   dictionary or associative array one does not know this; in fact, if 
   the type of the  `keys' is not totally  ordered this statement does 
   not even make sense. So the class DICTIONARY has a BOOLEAN function 
   has (k : K)  which will tell us whether  there is already an object 
   associated with key k. 


   In  all  other  respects  a  DICTIONARY  is  like  other  kinds  of 
   container. In particular  we have the procedures  add and remove as 
   well as iterators for visiting all elements in the dictionary. 


   For situations where one needs to represent a one--many mapping the 
   CONTAINER cluster offers  the class CATALOG.  A CATALOG is similar 
   to a DICTIONARY but  it associates with each  key an entire list of 
   objects. That is, the function call 

   c.at (k) 

   returns  not just  a single  object  but rather  an entire  list of 
   objects. The procedure call 

   c.add (x, k) 

   adds the object x  to the list associated  with key k (creating the 
   list,  if there  was previously  no list  associated with  k). And 
   correspondingly the call 

   c.remove (x, k) 

   removes the object x from the list  associated with key k (if x was 
   there; otherwise it does nothing). 


   In  reality  CATALOG  is  an  abstract  class  (deferred)  and  has 
   effective descendents  for each  of the kinds  of list  that can be 
   associated with keys. LIST_CATALOG, where  the lists are of type 
   LIST and SORTED_CATALOG  whose lists are  of type SORTED_LIST. 
   There are also  two `short' variants,  where the lists  are of type 
   SHORT_LIST and SHORT_SORTED_LIST. 


   Further container classes whose semantics are more restrictive than 
   those of the general containers are the following. 

 * Stack. Stacks of course  operate on the `last  in first out' (LIFO) 
   principle. This means that remove  has no argument; it removes that 
   object which was most recently  added using add. The function item 
   returns that  element which was  most recently added  (and would be 
   removed  by  a succeeding  call  to  remove). There  is  no search 
   routine. 

 * Queue. Queues operate on the `first in first out' (FIFO) principle. 
   Thus remove removes  that element which  has been in  the queue the 
   longest. This is the same element  that is returned by the function 
   item. Queues also have no routine search. 

 * Priority Queue.  A priority queue  is a  self organizing container. 
   The element  `at the front'  of the  queue is the  element with the 
   highest priority. Actually  there are two  kinds of priority queue. 
   One of  them accepts  elements conforming  to COMPARABLE  and sorts 
   them  according  to  their inherent  order.  The  other  accepts an 
   element  and  a  priority  (conforming  to  COMPARABLE)  and sorts 
   according to the priority member of the pair. 


   Thus  the  function item  for  a  priority queue  will  return that 
   element with the  highest priority currently in  the queue. This is 
   also  the element  removed  by a  succeeding  call to  remove. The 
   procedure add always  inserts an element  at a position appropriate 
   to its priority. 


   These special  container classes  are not  provided with iterators, 
   since the semantics of these abstract data types would not normally 
   involve traversing the containers.  Moreover their make routines do 
   not   have  the  argument   only_once,  since  the  question  of 
   uniqueness is irrelevant here. 


   The reader who  may be wondering  why the operations  on stacks are 
   called add and  remove instead of  push and pop  or those on queues 
   are not called enqueue and dequeue  is requested to see the remarks 
   in the Introduction about naming conventions. 


   We close this general introduction  to the CONTAINER Cluster with a 
   remark about the implementations of these classes. 

 * Collections are implemented by LIST. 

 * Sorted collections are implemented by SORTED_LIST. 

 * Tables are implemented by SIMPLE_TABLE and HASH_TABLE. 

 * Sorted tables are implemented by SORTED_TABLE. 

     With the  exception of SIMPLE_TABLE  these implementations were 
   chosen   because  they  should  be  the  most  efficient  for  most 
   applications. Normally  one should  never need  to replace  them by 
   others. The  HASH_TABLE is much  more efficient  for large tables 
   but unfortunately  does not fit  smoothly into  our general scheme, 
   because its creation procedure make  takes two arguments instead of 
   one. The  reader is  referred to  the section  on the  class HASH_ 
   TABLE below. In many  situations in which one  might at first think 
   of using HASH_TABLE one should also consider using DICTIONARY. 


   In addition there are the four classes 

   SHORT_LIST 
   SHORT_TABLE 
   SHORT_SORTED_LIST 
   SHORT_SORTED_TABLE 

   These are implemented to optimize space requirements at the cost of 
   speed.  Otherwise  their  behavior  is  identical  to  that  of the 
   corresponding `long' versions. 




6.1 Dynamic arrays 
==================


   Many of the classes in this cluster implement their data structures 
   using dynamic arrays --- that is, arrays which expand and shrink as 
   needed. The reader may be  concerned that the `expand' and `shrink' 
   operations needed occasionally  in the array  implementation may be 
   too expensive. In fact one can  readily show using the technique of 
   amortized analysis that  an arbitrary sequence of  n add and remove 
   operations beginning with an empty array  requires at most K n time 
   units for a  suitable constant K (see  the literature cited below). 
   Thus each such operation costs on the average K time units --- i.e. 
   a constant amount independent of n  . (Here we have not counted the 
   searching time  needed to  find an element  to be  removed; we have 
   considered only the time for  the actual removal. An implementation 
   using linked  lists or other  similar structures  also cannot avoid 
   the search time.) 


   Indeed the  interested reader may  wish to try  implementing his or 
   her own version of  our classes using list  nodes created on demand 
   and compare the performance of  such an implementation with that of 
   the Eiffel/S  classes. With  large containers  the Eiffel/S classes 
   will win. The reason is easy to understand: they benefit from `bulk 
   rates' on storage  acquisition because they  `buy' their storage in 
   big  chunks  instead  of piecemeal.  They  also  produce  much less 
   `garbage' and  so burden the  Eiffel/S garbage  collector less than 
   the list node implementation. 


   In the discussion  of time complexity  accompanying such classes we 
   shall often see specifications like the following. 


   Time: 
    add : O(count), amort. O(1) 


   This is  to be understood  as follows. An  individual add operation 
   could --- if it happens to cause  an `expand' --- cost on the order 
   of count  time units. This  is a  worst case value.  However, as we 
   remarked above a sequence  of n add and  remove operations costs no 
   more than  K n time  units for a  constant K .  Thus each operation 
   costs on  the average  at most  K time  units. This  upper bound is 
   independent of the  data being stored  and also independent  of n . 
   This fact is expressed by the second specification amort. O(1) . 




6.2 The routines is_equal, copy 
===============================


   We   treat  these  routines  in  a  section  before  beginning  the 
   discussion of the  individual classes, because  the general remarks 
   we can make about them apply for all container classes. 

   In all the container classes these routines that are inherited from 
   the  class  GENERAL  are redefined.  This  is  because  the default 
   behavior provided by  the standard versions  does not correspond to 
   what one  would expect  for container classes.  Let us  look at the 
   function is_equal for example.  Its standard version compares two 
   objects attribute by  attribute and returns  true if the attributes 
   are all equal  (in the sense of  =). But an  object of a container 
   type does not  have much to do  with the things  contained; it is a 
   sort of bookkeeper. What one would  expect of is_equal is that it 
   returns  true  if  the  two  containers  contain  exactly  the same 
   elements. 


   Please note that  what we have  just required of  is_equal is not 
   the same as what is_deep_equal provides. The function is_deep 
   _equal  returns true  if the  elements of  the two  containers are 
   pairwise deep_equal to  each other. What we  want is something in 
   between the standard is_equal and is_deep_equal. 


   Now what  we have  just described  could be  interpreted as meaning 
   that  some permutation  of  the elements  of  the one  container is 
   element  for element  equal to  the  set of  elements in  the other 
   container. That may very  well be what the  user really would like. 
   However, that is nearly impossible  to implement. What the function 
   is_equal implemented here  provides is weaker:  it yields true if 
   the elements in their  given order are pairwise  equal. This can be 
   precisely formulated as follows: is_equal is logically equivalent 
   to the following. 

   is_equal (other : like current) : BOOLEAN is 

     local 
        i1, i2 : ITERATOR 
     do 
        from 
           i1 := iterator 
           i2 := other.iterator 
        until 
           i1.finished or else i2.finished 
        loop 
           if item  (i1) /= other.item (i2) then 
              i1.stop 
           else 
              i1.forth 
              i2.forth
           end
        end 

     i1.stop 
     i2.stop  
     result := (not inside (i1) and then  not other.inside (i2)) 
   end 

   In fact is_equal  is in every  case implemented in  a way that is 
   somewhat more efficient. We give the implementation above merely to 
   clarify the semantics of is_equal in these classes. 


   The  routine copy  is also  somewhere  between the  (weak) standard 
   version  and deep_copy.  That is,  it  produces a  new container 
   which  contains the  same elements  in  the same  order as  the old 
   container. In particular the postcondition 

   is_equal (other) 

   which has to hold for this routine is of course preserved. 


   

6.3 LIST 
========


   We come  now to  the classes  that actually  implement the abstract 
   data types collection  and table. The  first of these  is the class 
   LIST, which implements an unsorted collection. 


   This class  inherits all  its features  from its  parent COLLECTION 
   without altering the semantics of any of them. 


   The lists are  implemented as linked lists  by using dynamic arrays 
   (one for the references to the stored items, one for the chaining). 
   These  arrays expand  and  shrink as  needed.  For a  discussion of 
   dynamic arrays see the appropriate section at the beginning of this 
   chapter. 

 class LIST [ G ] 

     
 make (only_once  : BOOLEAN) 
    --  If `only_once' is  true, then the  new collection  will
    --  not allow  duplicates. Otherwise duplicate entries 
    -- are allowed. 

 add (x : G) 
    -- If  the collection was created in non-unique mode, then 
    -- the element `x' is unconditionally added to the 
    -- contents of the collection. If  the list was created  
    -- in unique mode, `x' is only added if  it was not yet in 
    -- the collection. Otherwise a call `add (x)' is 
    -- without effect. 
    require 
       clash : not protected 

 count : INTEGER -- That's how many there are 

 empty : BOOLEAN 

 found  : BOOLEAN 
    -- True  if and  only  if the  last call  to
    -- `search' was successful. 

 found_item : G 
    -- The item  found by the  last call to `search'. The value 
    -- is valid only if `found' = true. 

 inside (it : ITERATOR)  : BOOLEAN
    -- Is  the given iterator still inside the container? 
     require 
        not_void : it /= void 

 item (it : ITERATOR) : G 
    --  The element at the position on which the iterator 
    -- `it' is currently standing. 
    require 
       not_void : it /= void is_inside : inside (it) 

 iterator  :  ITERATOR
    -- Returns  an  iterator  object  which is  prepared to 
    -- traverse the  current container. The `first' routine 
    -- of the iterator has already been  called and does not
    -- need to be called explicitly unless one wants to 
    -- restart the traversal. 

 protected : BOOLEAN 
    -- True if and  only if there is at least one 
    -- iterator traversing the container at the moment. 

 remove (x : G) 
    -- If the element `x' is present in the list, then one 
    -- reference to `x' is removed. 
    require 
       clash : not protected 

 search (x : G) 
    -- Look for  the element `x' in the collection. If at
    -- least one reference to `x'  is found there, set `found' 
    -- to true and `found_item'  to the item found; 
    -- otherwise set `found' to false. 


Remarks

  1. One should note that found really means precisely what was stated 
     above. In particular found  is false if there  has not yet been a 
     call to search. Furthermore found =  true need not mean that the 
     element x that was being sought is still in the list; there could 
     have been a  call remove (x)  since the call  to search (x). The 
     list could even be empty in the meantime! 

  2. One can request  arbitrarily many iterators for  one and the same 
     list. They are all independent of one another. 

  3. In containers  (which inherit  from TRAVERSABLE)  as long  as at 
     least one iterator  is active on  the list (i.e.  from the moment 
     the iterator is started until it leaves the end of the list or is 
     stopped by  calling its routine  stop), one is  not permitted to 
     execute  either add  or  remove on  the  list. One  can determine 
     whether an iterator is active by checking the attribute protected. 

  4. If  the  list was  created  in  unique mode,  then  the reference 
     removed  by remove  (x)  will be  the  only reference  so  that a 
     succeeding call to  search (x) will  produce not found. However, 
     if the list was created in  non-unique mode, then there could be 
     further references to x in the list. 

     This  is  the place  to  speak  of the  complexity  (or  cost) of 
   operations on collections as they are implemented in this class. 


   Space: O(count) 

   Time: 

    search : O(count) 
    add : O(count), amort. O(1) 
    remove : O(count) 


   We also need  to specify the complexity  of the iterator operations 
   on lists. 


   Space: O(1) 

   Time: 
    first : O(1) 
    forth : O(1) 
    stop : O(1) 


   


6.4 SORTED_LIST 
===============


   This class  implements the  sorted collections  by using red--black 
   trees. These are a kind of  balanced binary tree and thus guarantee 
   an optimal  performance for the  three operations  search, add and 
   remove. 


   The class  SORTED_LIST inherits  from SORTED_COLLECTION without 
   adding any features  or modifying the syntax  of any feature. Since 
   the interface  of the  class SORTED_LIST  is almost  identical to 
   that of  LIST we  refer the  reader to  the section  on LIST  for a 
   description  of  most  of  the features  of  this  class.  The only 
   features that are new in sorted lists are the two following ones. 

 class SORTED_LIST [ G -> COMPARABLE ] 

 iter_after (x  : G) :  TWOWAY_ITER 
   -- Returns  an iterator object which  is  prepared  to
   -- traverse  the  current  collection  in increasing order 
   -- beginning at the first element >= `x'. 
   ensure 
      right_item : inside (result) implies item (result) >= x 

 iter_before (x :  G) : TWOWAY_ITER
   --  Returns an iterator object which  is  prepared  to  
   --  traverse  the  current  collection  in decreasing order 
   -- beginning at the first element <= `x'. 
   ensure 
      right_item : inside (result) implies item (result) <= x 



   We now discuss the complexity of the routines. 


   Space: O(count) 

   Time: 
     search : O(lg (count)) 
     add : O(lg (count)) 
     remove : O(lg (count)) 



   We also need to specify the complexity of the iterator operations. 


   Space: O(1) 
   Time: 
     first : O(lg (count)) 
     forth : O(lg (count)) 
     last : O(lg (count)) 
     back : O(lg (count)) 
     stop : O(1) 

   The times  given here are,  however, worst case  times. The average 
   times are in fact much better. As one can readily show (cf. clr Ex. 
   13.2-5) a sequence of k operations ( 1 first and k-1 times forth or 
   k times  forth starting  at some  inner position)  requires at most 
   O(lg(count) + k) time units. Thus the average time per operation is 
   of the order O(1 + lg(count)/k) . 


   Or to put it another way, the time required for the loop 

   from 
      it := iterator 
   until 
      it.finished 
   loop 
      it.forth 
   end 

   is of the  order of 3  * count time  units, since each  node of the 
   tree is visited at  most three times: once  for each child and once 
   for itself. 





6.5 SIMPLE_TABLE 
================


   As the  name suggests this  is an elementary  implementation of the 
   class TABLE.  Like the  class LIST  it implements  the table  as a 
   linked list using dynamic arrays  --- here with one addtional array 
   for the keys. 


   In  situations where  the table  is expected  to remain  small this 
   implementation  may  be  perfectly adequate.  For  tables  that are 
   likely to be large the implementation using hash tables (see below) 
   should be used instead. 

 class SIMPLE_TABLE [ K, G ] 

 make (only_once : BOOLEAN) 
   -- If `only_once' is true, then the 
   -- new table  will not  allow duplicate  keys. 
   --  Otherwise duplicate entries are allowed. 

 add (x : G, k : K) 
   --  If the table was created in non-unique 
   -- mode then  the pair  `x', `k'  is unconditionally  
   -- added  to the contents of the table. If the  table 
   -- was created in unique mode, then `x', `k' is 
   -- only added if  `k' was not yet in the table. 
   -- Otherwise a call `add (x, k)' is without effect. 
   require 
      clash : not protected 

 count : INTEGER -- that's how many there are 

 empty : BOOLEAN 

 found  : BOOLEAN  
   -- True  if and  only  if the  last call  
   -- to `search' was successful. 

 found_item : G 
   -- The item found by the last call to 
   -- `search'. The value is valid only if 
   -- `found' = true. 

 found_key : K 
   --  The key found by the  last call to 
   -- `search'.  The value is valid only if 
   -- `found' = true. 

 inside (it : ITERATOR)  : BOOLEAN 
   -- Is  the given iterator still inside the container? 
     require not_void : it /= void 

 item (it : ITERATOR) : G 
   --  The element at the position on which the 
   -- iterator `it' is currently standing. 
   require 
      not_void : it /= void is_inside : inside (it) 

 iterator  :  ITERATOR 
   -- Returns  an  iterator  object  which is prepared to
   -- traverse the  current container. The `first' routine 
   -- of the iterator has already been  called and does not 
   -- need to be called explicitly unless one wants to 
   -- restart the traversal. 

 key (it : ITERATOR) :  K
   -- The key at  the position on which the 
   -- iterator `it' is currently standing. 
   require 
      not_void : it /= void is_inside : inside (it) 

 protected : BOOLEAN
   -- True if and  only if there is at least one 
   -- iterator traversing the container at the moment. 

 remove (k : K) 
   -- If the  key `k' is present in the container, 
   -- then one reference to `k' is removed. 
   require 
      clash : not protected 

 replace (x :  G, k :  K) 
   -- If  an entry for  key k exists, replace item with x; 
   -- otherwise make an entry for k with item x. 
   require 
      clash : not protected 

 search (k : K) 
   -- Look for the  key `k' in the table. If at least one
   -- reference to `k'  is found  there, set `found'  to true; 
   -- otherwise set `found' to false.  
   -- If `found' = true, `found_item' and `found_key' are 
   -- set correspondingly. 



   Space: O(count) 
   Time: 
     search : O(count) 
     add : O(count), amort. O(1) 
     remove : O(count) 
     replace : O(count) 


   The iterator operations have the following complexity. 


   Space: O(1) 
   Time: 
     first : O(1) 
     forth : O(1) 
     stop : O(1) 





6.6 HASH_TABLE
==============
 

   For tables that are expected to be large but whose maximum size can 
   be  roughly  estimated, the  hash  table is  the  implementation of 
   choice. 


   Unfortunately the hash  table does not fit  neatly into our general 
   scheme for two reasons.  One is that we must  require the type K of 
   the key to conform to HASHABLE,  whereas tables in general make no 
   restriction upon the type K. 


   The other reason is that the creation procedure make we use for all 
   other containers has only one  argument. The hash table distributes 
   the elements  over a  set of  m `slots'  or `buckets'.  If the hash 
   function  defined  for the  type  HASHABLE achieves  a  good random 
   distribution of the elements then there will be an average of count 
   / m elements in each slot,  which results in good search times. But 
   this means that the creation  routine for HASH_TABLE must specify 
   the number m of `slots' --- i.e. it requires one argument more than 
   heretofore. 


   The  class  HASH_TABLE  inherits from  TABLE  without  adding any 
   features or modifying the syntax of any feature except make. Since 
   the interface of the class HASH_TABLE is almost identical to that 
   of SIMPLE_TABLE  we refer the  reader to the  section on SIMPLE_ 
   TABLE for a description of most  of the features of this class. The 
   only feature that is different is make. 


 class HASH_TABLE [ K -> HASHABLE, G ] 

 make (only_once  : BOOLEAN,  slots :  INTEGER) 
   -- The meaning of `only_once' is the  
   -- same as  in all other  container classes.
   -- The argument `slots' specifies the
   --  number of buckets to be used in the hash table. 



   One  should  choose slots  to  be  roughly the  expected  number of 
   elements to be put into the table. 


   Space: O(count + slots) 
   Time: 
     add : O(1) (not unique) 
         : O(count) (unique) 
     remove : O(count) 
     search : O(count) 
     replace : O(count) 

     The times given  for remove and  search are worst  case times and 
   are based on  the assumption that  the hash function  is very badly 
   chosen, so that all  the elements end up  in one slot. If, however, 
   the hash  function is  optimal for the  given data  and achieves so 
   called simple uniform hashing then the average times are in fact 


     add : O(1 + a) unique 
     remove : O(1 + a)
     search : O(1 + a)
     replace : O(1 + a)


   where a = count/slots is the load factor. The often heard claim that 
   hash tables achieve searches in constant time is manifest nonsense. 


   The complexity of the iterator operations is as follows. 


   Space: O(1) 
   Time: 
     first : O(slots) 
     forth : O(slots) 
     stop : O(1) 
  

   The time required by the loop 

   from 
      it := iterator 
   until 
      it.finished 
   loop 
      it.forth 
   end 

   is  O(count +  slots)  . Thus  the average  time  for one  of these 
   operations is of the order O(1 + 1/a). 


Literature:
   [clr] Ch. 12 




6.7 SORTED_TABLE 
================


   The class SORTED_TABLE inherits  from SORT_TABLE without adding 
   any  features or  modifying the  syntax of  any feature.  Since the 
   interface of the  class SORTED_TABLE is  almost identical to that 
   of SIMPLE_TABLE  we refer the  reader to the  section on SIMPLE_ 
   TABLE for a description of most  of the features of this class. The 
   only features that are  new in sorted tables  are the two following 
   ones. 

 class SORTED_TABLE [ K -> COMPARABLE, G ] 

 iter_after (k  : K) :  TWOWAY_ITER
   -- Returns  an iterator object which is  prepared to 
   --  traverse the current  table in increasing order 
   -- beginning at the first key >= `k'. 
   ensure 
      right_item : inside (result) implies key (result) >= k 

 iter_before (k :  K) : TWOWAY_ITER 
   -- Returns an iterator object which is  prepared to 
   -- traverse the current  table in decreasing order 
   -- beginning at the first key <= `k'. 
   ensure 
      right_item : inside (result) implies key (result) <= k 

   This class implements  SORT_TABLE by using  red--black trees. Its 
   time complexity is  identical to that  of SORTED_LIST. Its space 
   complexity is as follows. 


   Space: O(count) 


   The space complexity for an iterator is the following. 


   Space: O(1) 


   
6.8 The short containers
======================== 


   It is unnecessary to list the features of these classes. The syntax 
   and semantics of  a `short' container  SHORT_XXX are identical to 
   those of its `long' relative  XXX. The complexity of these classes 
   does differ from that of the `long' versions. So in this section we 
   content ourselves with giving the  complexity for each of the short 
   containers. 



6.8.1 SHORT_LIST
================ 


 class SHORT_LIST [ G ] 

   Space: O(count) ; between 50 % and  60 % of the space required by a 
          long list. 
 
   Time: 
     add : O(count) 
     remove : O(count) 
     search : O(count) 
     first : O(1) 
     forth : O(1) 
     stop : O(1) 


   
6.8.2 SHORT_TABLE
================= 


   class SHORT_TABLE [ K, G ] 

   Space: O(count) ; between 60 % and  70 % of the space required by a 
         simple (long) table. 

   Time: 
     add : O(count) 
     remove : O(count) 
     search : O(count) 
     replace : O(count) 
     first : O(1) 
     forth : O(1) 
     stop : O(1) 




6.8.3 SHORT_SORTED_LIST
=======================
 
   class SHORT_SORTED_LIST [ G -> COMPARABLE ] 

   Space: O(count) ; between 25 % and  30 % of the space required by a 
          long sorted list. 
   Time: 
     add : O(count) 
     remove : O(count) 
     search : O(lg(count)) 
     first : O(1) 
     forth : O(1) 
     stop : O(1) 




6.8.4 SHORT_SORTED_TABLE
========================
 

   class SHORT_SORTED_TABLE [ K -> COMPARABLE, G ] 

   Space: O(count) ; between 35 % and  40 % of the space required by a 
          long sorted table. 

   Time: 
     add : O(count) 
     remove : O(count) 
     search : O(lg (count)) 
     replace : O(lg (count)) if key already there 
             : O(count) otherwise 
     first : O(1) 
     forth : O(1) 
     last : O(1) 
     previous : O(1) 
     stop : O(1) 
