1 Container classes

1.1  Objectives

The first key idea is to keep everything extremely simple - simple to understand but 
also simple to use. 

The second key is that only the functionality is important - not the implementation. 
Nevertheless efficiency is important but the user should not be disturbed by possible 
implementations.

So the "design" is strictly on functionality - it is important what one can do with one 
class. This is definitively not a linear approach. If we apply our approach on plants it 
will tell us what we can do with a plant - not all the relations between plants. So we 
have plants we can eat, plants to feed animals, plants we can use to make clothes, 
plants looking nice and so on. Grouping plants means mainly answering questions 
like: "can I eat it?" or "is this plant useful for the chemical industry?" 

This does not mean that the linear approach is a bad one - we only use a different way 
which may be more useful for the programmer without to much interest into the "zoo" 
of data structuring.



We need some "things" to store and retrieve data in a convenient, efficient and safe 
way. We want the following functionality:



 store data with and without key ordered or not ordered

 retrieve data with random access or sequential one

 browse through sets of data

 perform some set operations (combine sets, produce subsets, etc.)

 and more

1.1.1 Nomenclature

We call something we can store data in a CONTAINER and something we can remove 
data from at exactly one place a DISPENSER. Contrary to this container which allows 
only to remove item after item (like a salt dispenser) we have other containers which 
allow us to browse through and to remove, extract and change elements randomly. We 
call these containers LIST if data are stored without key and TABLES if they are 
stored with key.

1.1.2 Ideas behind

To approach this target we finally distinguish 3 different types of containers we can 
read from (note that ARRAY and STRING are part of the Eiffel Kernel and will not be 
discussed here):



 LIST         						elements can be read without changing the list

 TABLE        						the same as LIST but with keys

 DISPENSER    						only one element can be read at the same time


in terms of writing we distinguish two types:



 CONTAINER            				 		 no keys 

 KEYED_CONTAINER 						 elements are stored with keys


To simplify the manipulation of the LIST and TABLE objects we introduce two 
groups of "expert" classes: TABLE_ITERATOR and LIST_ITERATOR. These iter-
ators will be explained separately. They are not really necessary, i.e. it is possible to 
live without them. Despite of this fact they may be useful for special applications and 
the objective of this class library is to be useful. So we include these classes.



1.1.3 Structure of the classes (overview)


.. see attached GIF file





1.1.4 LIST

allows storing objects. Retrieving is mainly by using iterators or cursors. The pro-
grammer will use containers mainly if he is not interested in retrieving objects by 
using a key or in a special sequence. A container just allows storing and performing 
some actions with the elements. Note that traversing a list is always done following the 
same rule, i.e. that two iterations through one list are performed in the same way - pro-
vided the fact that be-tween the two iterations no element has been removed or inserted.

1.1.5 TABLE

allows storing objects and retrieving them using keys. In addition one has the possi-
bility to traverse a table like a list. In this case a possible constraint (comparable or 
hashable) has consequences for the sequence of the elements are visited by a cursor. 
The programmer will use tables if he/she has a meaningful key. Please note that a table 
with a INTEGER key can be used like a ARRAY (this has no relation to the implemen-
tation).

Both containers can be traversed using special objects, cursors. Beside this it is pos-
sible to perform some actions with special classes - called iterators. Iterators and 
cursors will be discussed in a special chapter later on. 

1.1.6 DISPENSER

is a non-traversable container. Objects are stored and retrieved following special rules 
dependent on the dispenser type. The programmer will use dispensers if these rules are 
the most important criteria. 

There is some limited possibility to transform one container type into another type.

1.2  Support classes

1.2.1 SIMPLE_TRAVERSABLE

indexing

title: "The project-wide universal properties: facilities for being traversable in one 
direction - towards the last entry"

deferred class interface

SIMPLE_TRAVERSABLE

feature -- Operations

cursor: SIMPLE_CURSOR

-- delivers a new cursor to the beginning of the container

feature -- Queries

is_inside (cs: like cursor): BOOLEAN

-- is this cursor traversing the container ?

require

valid_argument: cs /= Void

is_protected: BOOLEAN

-- true if at least one cursor is traversing the container;

-- otherwise - false

feature {SIMPLE_CURSOR}

first (cs: SIMPLE_CURSOR)

-- positions on the first item

require

proper_cursor: is_inside (cs)

deferred

forth (cs: SIMPLE_CURSOR)

-- moves one item forward

require

proper_cursor: is_inside (cs)

deferred

end

1.2.2 TRAVERSABLE

indexing

title: "The project-wide universal properties: facilities for being traversable in the 
both directions"

deferred class interface

TRAVERSABLE

inherit

SIMPLE_TRAVERSABLE

redefine

cursor

feature -- Operations

cursor: CURSOR

-- delivers a new cursor to the beginning of the container

feature {CURSOR}

back (cs: CURSOR)

-- moves one item backwards

require

proper_cursor: is_inside (cs)

deferred

last (cs: CURSOR)

-- positions on the last item

require

proper_cursor: is_inside (cs)

deferred

end

1.2.3 Cursors

Each cursor is tied to exactly one container. We have two types of cursors - one starts 
at the beginning and stops at the end and the other can work in the both directions (we 
will not discuss here what "beginning" and "end" means). See also the chapter about 
iterators which gives a different possibility to do something with the content of a list 
or a table.

indexing

title: "Simple cursors: tools for traversing all the ancestors of class 
SIMPLE_TRAVERSABLE"

class interface

SIMPLE_CURSOR

creation {SIMPLE_TRAVERSABLE}

make

feature -- Operations

first

-- positions on the first item

forth

-- moves one item forward

require

still_inside: not is_finished

stop

-- stops traversal

feature -- Queries

is_finished: BOOLEAN

-- is the end of the structure reached ?

target: SIMPLE_TRAVERSABLE

-- the structure the cursor belongs to

end

____________________________________________________________

indexing

title: "Cursors: tools for traversing all the ancestors of class TRAVERSABLE"

class interface

CURSOR

inherit

SIMPLE_CURSOR

redefine

target

creation {TRAVERSABLE}

make

feature -- Operations

back

-- moves one item backwards

require

still_inside: not is_finished

last

-- positions on the last item

feature -- Queries

target: TRAVERSABLE

-- the structure the cursor belongs to

end

1.3  Containers

1.3.1 General comments

Any container is in fact a model of a box which can contain something. The only thing 
we can do with such container is to count the number of elements inside. Putting some-
thing in is more specialized - we have the possibility to attach a label to each item so 
that we can retrieve it later using this label, but we must not do so. In our design we 
have to attach labels to all elements of a container or to none. In the first case we call 
such container LIST, in the second one - TABLE. 

In contrast to the "real life" situation our containers are not limited in size, i.e. we have 
no "bounded containers". Instead all containers are extended dynamically and only 
limited by the total working space of the computer system. This follows our philoso-
phy to make life as easy as possible to our customers. But should you really want to get 
good control over the allocation of memory for new entries, you can do that.

As well lists and tables can be created in a unique or a non unique form. This means 
that for a list the same ele-ment can or can not be element more than once. In a table this 
is important for the key which must be unique or not. Groups LIST - TABLE are very 
similar. This is demonstrated by the following table.



Actions

TABLE

LIST

creation

make (is_unique: BOOLEAN)

search for a entry

search (x: G)

search (k: H)

add new entries

put (x: G)

put (x: G; k: H)

merging

merge (other: like Current)

remove (one or more) 
entry or entries

remove (x: G)

remove_all (x: G)

remove (x: G; k: H) 
remove_all (k: H)

remove all entries

wipe_out

uniqueness

unique_items

unique_keys

status indicators

empty, found

element included ?

 has_item (x: G)

has_key (k: H)

entries

count

duplicates

 count_of (x: G)

count_of (k: H)

navigation

 cursor_at (x: G)

cursor_at (k: H

item_at (cs: CURSOR): G

1.4  Containers of type LIST

Classes SIMPLE_LIST, LIST, HASH_LIST and SORTED_LIST describe the containers 
of type LIST - the data structures in which the elements are arranged in a linear order: 
we know how they follow each other. Unlike an array, in which the linear order is 
determined by the array indices, the order in lists is determined by either an order in 
which the elements (items) are put into (SIMPLE_LIST and LIST) or a total order rela-
tion based on the comparison of the elements (SORTED_LIST) or their hash values or 
hash codes (HASH_LIST).

All the lists differ only in how their elements follow each other and that is why all of 
them have the same interface - only SORTED_LIST has some more additional features 
and SIMPLE_LIST has not features for backward traversing.

The instances of class SIMPLE_LIST represent so called singly linked lists - we can 
traverse these objects only in one direction: from the first element to the last one. Any 
new element is added to the end of the container (and becomes the last one at that 
time). The first element is the one put into a list earlier than all the others currently 
stored in the list. So, SIMPLE_LIST is based on a first_in, first-out, or FIFO, policy.

The instances of class LIST represent so called doubly linked lists. They differ from 
singly linked lists (SIMPLE_LIST) only in one point: they can be traversed in the both 
directions: not only towards the last element but towards the first element also. In all 
other respects LIST and SIMPLE_LIST are the same (a new element is added to the end 
of the container also ...)

The instances of class HASH_LIST represent lists in which direct addressing into ordi-
nary array takes place. Instead of requesting an array index directly, the array index is 
computed from the element. It is evident that the elements with smaller indices are 
ordered before those with bigger ones. The following algorithm for computing these 
indices are used in HASH_LIST:

		hash_index := x.hash_code \\ capacity + 1

where x is a descendant of class HASHABLE (part of the Eiffel Kernel), capacity is a 
feature of all the containers and \\ - integer remainder in Eiffel.

Class HASH_LIST describes a data structure which can give the programmer 
extremely fast operations of type search, put or remove. The efficiency of these oper-
ations depends solely on how well  x.hash_code \\ capacity + 1  distributes the set of 
the elements to be stored among capacity slots. Formally the answer is very simple - 
hash_code of class HASHABLE should be proper defined but, unfortunately, how to 
do that is beyond our discussion here.

The instances of class SORTED_LIST represent data structures in which the elements 
are arranged according to the total order relation based on the comparing features - like 
`<` (less than) - defined or introduced in class COMPARABLE (part of the Eiffel Ker-
nal). The smallest element is the first element in the linear order of SORTED_LIST 
and the largest one is the last element - all the others are surely put between them.

A FIFO policy is used for duplicates if they are allowed to be stored in a container of 
type LIST.

While handling any list the programmer can get in return not only the exceptions 
issued or raised by the Eiffel Kernel classes but also the container ones. These excep-
tions will be explained separately later.

The rest of the chapter are some extracts from the short forms of the classes.

indexing

title: "The project-wide universal properties: the class is an ancestor to all the 
container classes"

deferred class interface

SIMPLE_CONTAINER

feature -- Operations

make (is_unigue: BOOLEAN)

-- tells the container if its entries must be unigue

deferred

feature -- Queries

empty: BOOLEAN

-- is the container empty ?

count: INTEGER

-- gives the number of entries in the container or equally

-- the number of the slots used for storing the entries

deferred

capacity: INTEGER

-- gives the number of all the slots allocated in the container

-- (all the containers may have not only slots occupied by the

-- entries already put into but unused ones also)

deferred

invariant

no_miracle: count >= 0 and then count <= capacity

end

____________________________________________________________

indexing

title: "The manner of adding entries into non-keyed containers"

deferred class interface

CONTAINER [G]

inherit

SIMPLE_CONTAINER

feature -- Operations

put (x: G)

-- adds the new entry into the structure

require

valid_item: x /= Void

deferred

end

1.4.1 SIMPLE_LIST

indexing

title: "Simple lists: containers with one element entries implementing a first-in, 
first-out policy and supporting only restricted traversal in one direction"

class intarface

SIMPLE_LIST [G]

inherit

CONTAINER [G]

SIMPLE_TRAVERSABLE

creation

make

feature -- Operations

allocate_automatically

-- switches the list to the mode of automatic memory allocation

-- for new slots; it can be necessary only if allocate_manually

-- was called before because, by default, all types of the lists

-- allocate memory automatically

allocate_manually (new_capacity: INTEGER)

-- forbids the list to allocate memory for new slots automatically

-- and sets the total amount of slots allocated in the list to be

-- equal to new_capacity - being negligent after that in manual

-- allocating extra slots for new entries to be put might lead to

-- the exception of type SCIF ("container is full")

require

no_obstacle: not is_protected;

proper_capacity: new_capacity >= count and then new_capacity > 0

make (is_unique: BOOLEAN)

-- tells the container if its entries must be unigue

merge (other: like Current)

-- puts all entries from other into the list

require

no_obstacle: not is_protected;

valid_argument: other /= Void

ensure

well_done: count = old count + other.count 

put (x: G)

-- adds the entry into the list

require else

valid_argument: x /= Void;

no_obstacle: not is_protected;

uniqueness: unique_items implies not has_item (x)

ensure then

well_done: not empty and then has_item (x)

remove (x: G)

-- deletes the entry from the list

require

no_obstacle: not is_protected;

valid_argument: x /= Void;

valid_target: has_item (x)

ensure

well_done: count = old count - 1

remove_all (x: G)

-- deletes the entry from the list together with all its

-- duplicates if more than one copy is currently stored

require

no_obstacle: not is_protected;

valid_argument: x /= Void;

valid_target: has_item (x)

ensure

well_done: old count < count and then not has_item (x)

search (x: G)

-- looks for the element: after every successful attempt found

-- is set to true and found_item - to the found element;

-- in case of failure found is set to false

require

valid_argument: x /= Void

wipe_out

-- removes all entries from the list

require

no_obstacle: not is_protected

ensure

well_done: empty

feature -- Queries

count: INTEGER

-- number of the entries currently stored in the list or

-- equally the number of slots used

found: BOOLEAN

-- was the last search successful ?

found_item: G

-- the entry found by the very last call to search;

-- the value is valid if and only if found = true

unique_items: BOOLEAN

-- must the entries be unique ?

capacity: INTEGER

-- gives the number of all slots allocated in the list (all types

-- of the lists may have not only slots occupied by the entries

-- already put into but unused ones also)

count_of (x: G): INTEGER

-- gives the number of the duplicates of the entry inclusive; if

-- unique_items = true then the number can be only zero or one

require

valid_argument: x /= Void

ensure

well_done: Result <= count

cursor_at (x: G): SIMPLE_CURSOR

-- delivers a new cursor positioned to the entry; if there is no

-- such entry currently stored in the list Void is delivered

require

valid_argument: x /= Void

ensure

well_done: Result /= Void implies is_protected

has_item (x: G): BOOLEAN

-- is the entry currently stored in the list ?

require

valid_argument: x /= Void

item_at (cs: SIMPLE_CURSOR): G

-- delivers the entry of the list the cursor is positioned to

require

proper_cursor: is_inside (cs)

feature {SIMPLE_CURSOR}

first (cs: SIMPLE_CURSOR)

-- positions on the first item

ensure then

well_done: not empty implies has_item (item_at (cs))

forth (cs: SIMPLE_CURSOR)

-- moves one item forward

ensure then

well_done: not cs.is_finished implies has_item (item_at (cs))

end

1.4.2 LIST

indexing

title: "Lists: containers with one element entries implementing a first-in, first-out 
policy and supporting traversal in the both directions"

class interface

LIST [G]

inherit

SIMPLE_LIST [G]

undefine

cursor

redefine

cursor_at, item_at, make

TRAVERSABLE

creation

make

feature -- Operations

make (is_unique: BOOLEAN)

-- tells the container if its entries must be unigue

feature -- Queries

item_at (cs: CURSOR): G

-- delivers the entry of the list the cursor is positioned to

cursor_at (x: G): CURSOR

-- delivers a new cursor positioned to the entry; if there is no

-- such entry currently stored in the list Void is delivered

feature {CURSOR}

last (cs: CURSOR)

-- positions on the last item

ensure then

well_done: not empty implies has_item (item_at (cs))

back (cs: CURSOR)

-- moves one item backwards

ensure then

well_done: not cs.is_finished implies has_item (item_at (cs))

end

1.4.3 HASH_LIST

indexing

title: "Hash lists: containers which store its one element entries widely using their 
ability to be associated with integers"

class interface

HASH_LIST [G -> HASHABLE]

inherit

LIST [G]

rename

make as list_make

export {NONE}

list_make

redefine

list_make

creation

make

feature -- Operations

make (is_unique: BOOLEAN; initial_capacity: INTEGER)

-- tells the container if its entries must be unigue and sets

-- the initial amount of slots to be allocated for storing entries

require

proper_initial_slots: initial_capacity > 0

end

1.4.4 SORTED_LIST

indexing

title: "Sorted lists: containers which store their one element entries in a total 
relation order (ascending) based on the operations of class COMPARABLE 
from the Eiffel Kernel"

class interface

SORTED_LIST [G -> COMPARABLE]

inherit

LIST [G]

redefine

make

creation

make

feature -- Operations

make (is_unique: BOOLEAN) is

-- -- tells the container if its entries must be unigue

feature -- Queries

cursor_after (x: G): CURSOR is

-- delivers a new cursor positioned to the entry closest to and

-- greater than x - the presence of entry x is not essential

-- but if currently there are at all no such entries in the list

-- then Void is delivered

require

valid_argument: x /= Void

ensure

well_done: Result /= Void implies is_protected

cursor_before (x: G): CURSOR is

-- delivers a new cursor positioned to the entry closest to and

-- less than x - the presence of entry x is not essential

-- but if currently there are at all no such entries in the list

-- then Void is delivered

require

valid_argument: x /= Void

ensure

well_done: Result /= Void implies is_protected

end

1.5  Containers of type TABLE

The classes TABLE, HASH_TABLE and SORTED_TABLE describe the containers of 
type TABLE - the data structures in which the elements are arranged in a linear order: 
we know how they follow each other - just like ones describing by the containers of 
type LIST. As was mentioned above these groups are very similar. The only difference 
is that a linear order of the elements is defined by their keys - not by the elements 
themselves. Every entry of the tables consists of two elements: an element or item 
itself and a key. The element is mainly only stored but the associated key is widely 
used for handling the entry.

Entries of an instance of class TABLE are handled basing on a FIFO policy. There is no 
much special in that - just like class LIST.

The instances of class HASH_TABLE represent data structures in which direct 
addressing into ordinary array takes place. Instead of computing array index from the 
element like in HASH_LIST it is computed in HASH_TABLE from the key - the same 
algorithm is used.

The instances of class SORTED_TABLE represent data structures in which the ele-
ments are arranged according to the total order relation of their keys. The same tech-
nique as for the containers of type LIST is used.

Creation proce-dure make should be told whether the keys (not the items like in lists) 
are unique or not. This way allows us to use TABLE for "one to one" and "one to 
many" relations just by changing a parameter in a creation procedure. The different 
approach to group the tables in SET, TABLE and UNIQUE_TABLE (and correspond-
ingly LIST and UNIQUE_LIST) seems also acceptable but introduces more classes 
and makes the system more difficult to use. So our approach seems to be better.

A FIFO policy is used for duplicates if they are allowed to be stored in a container of 
type TABLE.

While handling any table the programmer can get in return not only the exceptions 
issued or raised by the Eiffel Kernel classes but also the container ones. These excep-
tions will be explained separately later.

Some extracts from the short forms of the classes are put below.

indexing

title: "The project-wide universal properties: the class is an ancestor to all the 
container classes"

deferred class interface

SIMPLE_CONTAINER

feature -- Operations

make (is_unigue: BOOLEAN)

-- tells the container if its entries must be unigue

deferred

feature -- Queries

empty: BOOLEAN

-- is the container empty ?

count: INTEGER

-- gives the number of entries in the container or equally

-- the number of the slots used for storing the entries

deferred

capacity: INTEGER

-- gives the number of all the slots allocated in the container

-- (all the containers may have not only slots occupied by the

-- entries already put into but unused ones also)

deferred

invariant

no_miracle: count >= 0 and then count <= capacity

end

____________________________________________________________

indexing

title: "The manner of adding entries into keyed containers"

deferred class interface

KEYED_CONTAINER [G, H]

inherit

SIMPLE_CONTAINER

feature -- Operations

put (x: G; k: H) is

-- adds the new entry into the structure

require

valid_item: x /= Void;

valid_key:  k /= Void

deferred

end

1.5.1 TABLE

indexing

title: "Tables: containers with two element entries (element or item itself and the 
associated key) implementing a FIFO policy and supporting traversal in the 
both directions"

class interface

TABLE [G, H]

inherit

KEYED_CONTAINER [G, H]

TRAVERSABLE

creation

make

feature -- Operations

allocate_automatically

-- switches the table to the mode of automatic memory allocation

-- for new slots; it can be necessary only if allocate_manually

-- was called before because, by default, all types of the tables

-- allocate memory automatically

allocate_manually (new_capacity: INTEGER)

-- forbids the table to allocate memory for new slots automatically

-- and sets the total amount of slots allocated in the table to be

-- equal to new_capacity - being negligent after that in manual

-- allocating extra slots for new entries to be put might lead to

-- the exception of type SCIF ("container is full")

require

no_obstacle: not is_protected;

proper_argument: new_capacity >= count and then new_capacity > 0

make (is_unique: BOOLEAN)

-- tells the container if the keys of its entries must be unigue

merge (x: like Current)

-- puts all entries from other into the table

require

no_obstacle: not is_protected;

valid_argument: x /= Void

ensure

well_done: count = old count + x.count

put (x: G; k: H)

-- adds the new entry into the structure

require else

valid_item:  x /= Void;

valid_key:  k /= Void;

no_obstacle: not is_protected;

uniqueness:  unique_keys implies not has_key (k)

ensure then

well_done: not empty and then has_key (k)

remove (x: G; k: H)

-- deletes the entry from the table

require

no_obstacle: not is_protected;

valid_item:  x /= Void;

proper_key:  k /= Void and then has_key (k)

ensure

well_done: count = old count - 1

remove_all (k: H)

-- deletes all the entries items of which associated with key k

require

no_obstacle: not is_protected;

proper_key:  k /= Void and then has_key (k)

ensure

well_done: old count < count and then not has_key (k)

search (k: H)

-- looks for the key: after every successful attempt found is

-- set to true and found_item - to an element (item) associated

-- with key k; in case of failure found is set to false

require

valid_argument: k /= Void

wipe_out

-- remove all entries from the table

require

no_obstacle: not is_protected

ensure

well_done: empty

feature -- Queries

count: INTEGER

-- number of the entries currently stored in the table or

-- equally the number of slots used

found: BOOLEAN

-- was the last search successful ?

found_item: G

-- the element (item) found by the last call to search;

-- the value is valid if and only if found = true

unique_keys: BOOLEAN

-- must the keys of the entries be unique ?

capacity: INTEGER

-- gives the number of all slots allocated in the table (all types

-- of the tables may have not only slots occupied by the entries

-- already put into but unused ones also)

count_of (k: H): INTEGER

-- gives the number of the entries items of which associated with

-- key k; if unique_items = true then the number can be equal

-- only to zero or one

require

valid_key: k /= Void

ensure

well_done: Result <= count

current_keys: LIST [H]

-- delivers all the keys (without duplicates) currently stored in

-- the table

cursor_at (x: G; k: H): CURSOR

-- delivers a new cursor positioned to the entry; if there is no

-- such entry currently stored in the table Void is delivered

require

valid_item: x /= Void;

proper_key: k /= Void

ensure

well_done: Result /= Void implies is_protected

has_key (k: H): BOOLEAN

-- is the entry currently stored in the table ?

require

valid_key: k /= Void

item_at (cs: CURSOR): G

-- delivers the element or item of the entry the cursor is

-- positioned to

require

proper_cursor: is_inside (cs)

items_at_key (k: H): LIST [G]

-- delivers all the elements (items) currently stored in the

-- table and associated with key k

require

proper_key: k /= Void

key_at (cs: CURSOR): H

-- the key of the entry the cursor is positioned to

require

proper_cursor: is_inside (cs)

feature {CURSOR}

back (cs: CURSOR)

-- moves one item backwards

ensure then

well_done: not cs.is_finished implies has_key (key_at (cs))

first (cs: CURSOR)

-- positions on the first item

ensure then

well_done: not empty implies has_key (key_at (cs))

forth (cs: CURSOR)

-- moves one item forward

ensure then

well_done: not cs.is_finished implies has_key (key_at (cs))

last (cs: CURSOR)

-- positions on the last item

ensure then

well_done: not empty implies has_key (key_at (cs))

end

1.5.2 HASH_TABLE

indexing

title: "Hash tables: containers which store its two element entries (element or item 
itself and the associated key) widely using the ability of keys to be transformed 
into integers"

class interface

HASH_TABLE [G, H -> HASHABLE]

inherit

TABLE [G, H]

rename

make as table_make

export {NONE}

table_make

redefine

table_make

creation

make

feature -- Operations

make (is_unique: BOOLEAN; initial_capacity: INTEGER)

-- tells the container if its entries must be unigue and sets

-- the initial amount of slots to be allocated for storing entries

require

proper_initial_slots: initial_capacity > 0

end

1.5.3 SORTED_TABLE

indexing

title: "Sorted tables: containers which store their two element entries (element or 
item itself and the associated key) in a total relation order (ascending) based on 
the operations of class COMPARABLE (the Eiffel Kernel) being done on keys"

class interface

SORTED_TABLE [G, H -> COMPARABLE]

inherit

TABLE [G, H]

redefine

current_keys, make

creation

make

feature -- Operations

make (is_unique: BOOLEAN)

-- tells the container if its entries must be unigue

feature -- Queries

current_keys: LIST [H]

-- delivers all the keys (without duplicates) currently stored in

-- the table

cursor_after (k: H): CURSOR

-- delivers a new cursor positioned to the entry the key of which

-- is closest to and greater than k - the presence of entry with

-- key k is not essential but if currently there are at all no

-- such entries in the table then Void is delivered

require

proper_key: k /= Void

ensure

well_done: Result /= Void implies is_protected

cursor_before (k: H): CURSOR

-- delivers a new cursor positioned to the entry the key of which

-- is closest to and less than k - the presence of entry with

-- key k is not essential but if currently there are at all no

-- such entries in the table then Void is delivered

require

proper_key: k /= Void

ensure

well_done: Result /= Void implies is_protected

end

1.6  Containers of type DISPENSER

Dispensers are containers with access to only one entry, the "active entry". To access 
another entry the state of the dispensor must be changed. If for the same reason the dis-
pensor must "be read out" the whole content can be copied into another container 
structure. Note that a dispenser can be only read from - that is exactly the opposite 
functionality of the other containers (lists and tables).

This category of containers have four different dispensers being described by the fol-
lowing classes STACK, QUEUE, PRIORITY_QUEUE, KEY_PRIORITY_QUEUE.

The effective dispensers are very simple. They differ mainly in the underlaying 
semantics. In any case functions list_of_elements or table_of_elements deliver the ele-
ments in the sequence they would be removed if this happened.

The corresponding extracts from the short forms are put below.

indexing

title: "The project-wide universal properties: the class is an ancestor to all the 
container classes"

deferred class interface

SIMPLE_CONTAINER

feature -- Operations

make (is_unigue: BOOLEAN)

-- tells the container if its entries must be unigue

deferred

feature -- Queries

empty: BOOLEAN

-- is the container empty ?

count: INTEGER

-- gives the number of entries in the container or equally

-- the number of the slots used for storing the entries

deferred

capacity: INTEGER

-- gives the number of all the slots allocated in the container

-- (all the containers may have not only slots occupied by the

-- entries already put into but unused ones also)

deferred

invariant

no_miracle: count >= 0 and then count <= capacity

end

____________________________________________________________

indexing

title: "The manner of adding entries into non-keyed containers"

deferred class interface

CONTAINER [G]

inherit

SIMPLE_CONTAINER

feature -- Operations

put (x: G)

-- adds the new entry into the structure

require

valid_item: x /= Void

deferred

end

____________________________________________________________

indexing

title: "The manner of adding entries into keyed containers"

deferred class interface

KEYED_CONTAINER [G, H]

inherit

SIMPLE_CONTAINER

feature -- Operations

put (x: G; k: H) is

-- adds the new entry into the structure

require

valid_item: x /= Void;

valid_key:  k /= Void

deferred

end

____________________________________________________________

indexing

title: "The project-wide universal properties: the class is an ancestor to all the 
dispensers"

deferred class interface

SIMPLE_DISPENSER [G]

feature -- Operations

remove is

-- deletes the "active entry" and sets item and possibly key

-- to another entry (algorithms of selecting a new "active entry"

-- differ in all dispensers) or - Void if there are no more entries

require

not_empty: not empty

deferred

feature -- Queries

empty: BOOLEAN

-- is the dispenser empty ?

deferred

item: G

-- the "active entry" of the dispenser - in dispensers with two

-- element entries attribute key will be also considered as a

-- part of the "active entry"

invariant

correct_emptiness: (empty implies item = Void) and then (item = Void implies 
empty);

proper_tuning: (not empty implies item /= Void) and then (item /= Void implies 
not empty)

end

____________________________________________________________

indexing

title: "Facilities for all the dispensers with one element entries"

deferred class interface

DISPENSER [G]

inherit

CONTAINER [G]

export {NONE}

make

SIMPLE_DISPENSER [G]

feature -- Queries

capacity: INTEGER

-- gives the number of all the slots allocated in the dispenser

-- (all the dispensers may have not only slots occupied by the

-- entries already put into but unused ones also)

count: INTEGER

-- gives the number of entries in the dispenser or equally

-- the number of the slots used for storing the entries

list_of_elements: LIST [G]

-- delivers all the entries of the dispenser in the order in

-- which they would be repeatedly removed if it really happened

deferred

ensure

well_done: not empty implies Result /= Void

end

____________________________________________________________

indexing

title: "Facilities for the dispensers with two element entries"

deferred class interface

KEYED_DISPENSER [G, H]

inherit

KEYED_CONTAINER [G, H]

export {NONE}

make

SIMPLE_DISPENSER [G]

feature -- Queries

key: H

-- the other part of the "active entry" of the dispenser

capacity: INTEGER

-- gives the number of all the slots allocated in the dispenser

-- (all the dispensers may have not only slots occupied by the

-- entries already put into but unused ones also)

count: INTEGER

-- gives the number of entries in the dispenser or equally

-- the number of the slots used for storing the entries

table_of_elements: TABLE [G, H]

-- delivers all the entries of the dispenser in the order in

-- which they would be repeatedly removing if it really happened

deferred

ensure

well_done: not empty implies Result /= Void

end

1.6.1 STACK

indexing

title: "Stacks: dispensers implementing a last-in, first-out policy"

class interface

STACK [G]

inherit

DISPENSER [G]

feature -- Operations

put (x: G)

-- adds the new entry into the dispenser which immediately after

-- becomes the "active entry" of the dispenser

ensure then

well_done: count = old count + 1

remove

-- deletes the "active entry" and sets item to the one most

-- recently put into if at least one entry exists after the

-- removal - if it does not then item is set to Void

ensure then

well_done: count = old count - 1

feature -- Queries

list_of_elements: LIST [G]

-- delivers all the entries of the dispenser in the order in

-- which they would be repeatedly removed if it really happened

end

1.6.2 QUEUE

indexing

title: "Queues: dispensers implementing a first-in, first-out policy"

class interface

QUEUE [G]

inherit

DISPENSER [G]

feature -- Operations

put (x: G)

-- adds the new entry into the dispenser

ensure then

well_done: count = old count + 1

remove

-- deletes the "active entry" and sets item to the one that has

-- been in the dispenser for the longest time if there are no more

-- entries after the removal - item is set to Void

ensure then

well_done: count = old count - 1

feature -- Queries

list_of_elements: LIST [G]

-- delivers all the entries of the dispenser in the order in

-- which they would be repeatedly removed if it really happened

end

1.6.3 PRIORITY_QUEUE

indexing

title: "Priority queues: dispensers with one element entries in which only the 
largest element is accessed"

class interface

PRIORITY_QUEUE [G -> COMPARABLE]

inherit

DISPENSER [G]

feature -- Operations

put (x: G)

-- adds the new entry into the dispenser

ensure then

well_done: count = old count + 1

remove

-- deletes the "active entry" and sets item to the largest one

-- among those still existing in the dispenser after the removal;

-- if there are no more entries then item is set to Void

ensure then

well_done: count = old count - 1

feature -- Queries

list_of_elements: LIST [G]

-- delivers all the entries of the dispenser in the order in

-- which they would be repeatedly removed if it really happened

end

1.6.4 KEY_PRIORITY_QUEUE

indexing

title: "Key priority queues: dispensers with two element entries in which only the 
entry with the largest key is accessed"

class interface

KEY_PRIORITY_QUEUE [G, H -> COMPARABLE]

inherit

KEYED_DISPENSER [G, H]

feature -- Operations

put (x: G; k: H)

-- adds the new entry into the dispenser

ensure then

well_done: count = old count + 1

remove is

-- deletes the "active entry" and sets item to the element

-- associated with the largest key among those still existing in

-- the dispenser after the removal and key to this key; if there

-- are no more entries then item and key are both set to Void

ensure then

well_done: count = old count - 1

feature -- Queries

table_of_elements: TABLE [G, H]

-- delivers all the entries of the dispenser in the order in

-- which they would be repeatedly removed if it really happened

end





As it was mentioned above the effective dispensers are very simple. They differ in the 
underlaying semantics. In any case the functions list_of_elements or 
table_of_elements deliver the elements (items) in the sequence they would be 
removed should this really happened. This means that the following program parts 
would be equivalent:



-- some common stuff ...

x:  DISPENSER; -- any real dispenser could be here: now it's only an example ...

cs: CURSOR;

l:  LIST;

!!x 

-- 

-- putting some items into the dispenser ...

-- 



-- The first version (note: the content of the dispenser is changed ...)

	from

	until

		x.empty

	loop

		io.print (x.item)

		x.remove

	end



-- The second version (note: the dispenser remains unchanged ...)

	from

		l := x.list_of_elements

		cs := l.cursor

	until          

		cs.finish

	loop

		io.print (l.item_at (cs))

		cs.forth

	end

1.7  Iterators

They implement another "competitive" approach to the cursor concept. So one can 
write very simply a special TABLE_ITERATOR which extracts for example all ele-
ments with a key smaller than 50 into another table. As another possible name of this 
class we considered TOOLS - additionals tools to be applied on containers.

The base idea is that each ITERATOR should redefine routines test (delivering true by 
default) and action (by default it is empty) to fit the programmer's needs. Beside this 
it may be possible to extend the ITERATOR possibility by another routines - the rou-
tines we defined can be used as a starting point. We only want to remind you to keep 
in mind that such extensions should be selected very carefully.



indexing

title: "Alternative facilities for traversing the containers"

deferred class interface

ITERATOR

feature -- Operations

action

-- you can redefine this procedure to put all the operations

-- which should be done on every entry of the container being

-- traversed; the current entry is accessible through queries

-- item and key - the latter one is only for the tables

require

proper_target: target /= Void

do_all

-- procedure action will be performed on every entry of the

-- container being traversed unconditionally

require

proper_target: target /= Void

do_if

-- procedure action will be performed on every entry of the

-- container being traversed if it satisfies query test;

-- "testing" is performing on every entry automatically ...

require

proper_target: target /= Void

do_until

-- procedure action will be performed on every entry of the

-- container being traversed - one after another - until query

-- test gives true; every entry is being "tested" automatically

require

proper_target: target /= Void

do_while

-- procedure action will be performed on every entry of the

-- container being traversed - one after another - while query

-- test is giving true; every entry is being "tested" automatically

require

proper_target: target /= Void

feature -- Queries

target: TRAVERSABLE

-- the container is being traversed

deferred

test: BOOLEAN

-- you can redefine this query to put the conditions which should

-- be applied to every entry of the container - one after another;

-- the current entry is accessible through queries item and

-- key - the latter one is only for the tables

require

proper_target: target /= Void

end

1.7.1 LIST_ITERATOR

indexing

title: "List iterators: tools for traversing the lists (except the lists of class 
SIMPLE_LIST)"

class interface

LIST_ITERATOR [G]

inherit

ITERATOR

feature -- Operations

attach (x: LIST [G])

-- selects the target for traversal

require

proper_list: x /= Void

ensure

well_done: target /= Void

feature -- Queries

extract_list: like target

-- delivers all the entries of the list attached which are

-- satisfied the conditions put by test

require

proper_target: target /= Void

target: LIST [G]

-- the structure to be traversed

feature {NONE} -- Implementation

item: G

-- delivers the current entry for "being touched" in features

-- action and test while traversing the list

end

1.7.2 TABLE_ITERATOR

indexing

title: "Table iterators: tools for traversing the tables"

class interface

TABLE_ITERATOR [G, H]

inherit

ITERATOR

feature -- Operations

attach (x: TABLE [G, H])

-- selects the target for traversal

require

proper_table: x /= Void

feature -- Queries

extract_list: LIST [G]

-- delivers all the elements or items of the table attached the

-- entries of which including the keys satisfying the

-- conditions put by test

require

proper_target: target /= Void

extract_table: like target

-- delivers all the entries of the table attached which are

-- satisfied the conditions put by test

require

proper_target: target /= Void

target: TABLE [G, H]

-- the structure to be traversed

feature {NONE} -- Implementation

item: G

-- delivers the element or item of the current entry for

-- "being touched" in features action and test while

-- traversing the table

key: H

-- delivers the key of the current entry for "being touched"

-- in features action and test while traversing the table

end

1.7.3 Iterators: an example

A simple example for such a tool is a report writer. It looks like this:

class REPORT 

inherit 

	LIST_ITERATOR

		redefine action, test

creation

make

feature

fmt: STRING is "%n!20s !20s   !10.2d";

hdr: STRING is "%n%n!s%n!20s !20s   !10s"; 

zip: INTEGER;

make (title: STRING; p: INTEGER) is

		do

			io.printf (hdr, <<title, "name", "town", "salary">>);

			zip := p

		end

test (x: CUSTOMER): BOOLEAN is

		do

			Result := (x.zip_code = zip)

		end

action (x: CUSTOMER) is

		do

			io.printf (fmt, <<x.name + " " + x.first_name, x.town, x.salary >>)

		end

end -- class REPORT



This report is called like:

l, lx: LIST [CUSTOMER]

r: REPORT 

!!l.make;

-- 

-- putting some data into the list ...

-- 

!!r.make ("All customers with zip code", 12345);

r.attach (l);

r.do_if;



!!r.make ("All customers with zip code", 35619");

r.attach (l);

lx := r.extract_list;

-- delivers the list of all customers with zip code 35619



There is no doubt that the same class can be programmed using cursors. The reason 
that we introduce both versions in our class concept is the same why we use lists and 
tables: it is possible to live without tables - but in many cases tables are very conve-
nient. The same is true with the ITERATOR classes. At least this container library 
should serve the programmer's need. Please note that features extract... work like fil-
ters, i.e. that only those elements are included which fulfill the condition of the actual 
tool.

1.8  Exceptions

While using the containers the programmer might get an exception raised not only by 
the Eiffel Kernal or other classes but the container ones also. These exceptions are as 
follows:

SCDN	-	item/key duplication: if a list or container was created with the argu-
ment being equal to true then you are not allowed to put any dupli-
cate copies of the items or keys already being stored in the container

SCFL	-	container is full: if procedure allocate_manually was used and allo 
cating new slots for adding new entries is not done. If count = 
capacity you cannot put new entries after allocate_manually ...

SCIE	-	container is empty: for example, it is rather difficult to remove 
something from an empty container ...

SCIP	-	container is protected: it is not possible to put, remove, merge, etc 
(change the content of a container) if it is currently traversed at least 
one cursor ...

SCLD	-	possible loss of data: when using allocate_manually it is not possi-
ble to allocate the total amount of slots in a container less than the 
number of entries already stored in it

SCNF	-	item/key is not found: you cannot remove a non-existing entry ...

SCOR	-	cursor is out of range: it is not possible to traverse a container 
beyond its borders ...

SCXX	-	unexpected: you must not have it ...

The exceptions above can be raised not only in a debugging session (they are not pro-
grammed being based only on assertions)






