From: Stephen Leake <stephen.a.leake.1@gsfc.nasa.gov>
Subject: Re: A generic list package (long)
Date: 02 Sep 2001 18:29:47 -0400
Date: 2001-09-02T22:30:42+00:00 [thread overview]
Message-ID: <ubskt2plw.fsf@gsfc.nasa.gov> (raw)
In-Reply-To: 87ae0dp7gn.fsf@deneb.enyo.de
Florian Weimer <fw@deneb.enyo.de> writes:
> I'm currently working on a general-purpose low-level list package.
> The existing approaches were either too heavy-weight, too limited, or
> covered by an unacceptable license.
>
> What do you think about the approach documented below?
I've written a list package with similar goals; see
http://users.erols.com/leakstan/Stephe/Ada/sal.html. In particular,
see SAL.Gen.Lists.Double. I've found few uses for a singly linked
list, so my generic is a double linked list.
I have far fewer operations in my list package. I would separate out
the "convert to array", and "filter" operations into child packages;
I've never used such operations on lists. I also split the "for each"
and "find" operations into separate packages. For "for each", I
provide an Iterators child. For "find", I use an algorithms package
approach, as the C++ STL does; see SAL.Gen.Alg.Find_Linear.
I find it curious that you expect the client to provide the "next"
field/operation, rather than declaring a Node_Type containing the
user's type and a Next field. Can you elaborate on this? Hmm, maybe
because of Controlled types? I did have to provide some auxiliary
packages to make it easier to instantiate my list package for simple
types, but complex types need the full power I provide. Your approach
leaves everything up to the user; mine ensures that garbage is
collected appropriately, when necessary; the user only needs to provide
the appropriate 'free' operation.
>
>
> BTW: Could you suggest a better name than 'ADT' (meaning:
> 'imlementations of abstract data types') for this sub-hierarchy?
> What about 'Generics'? Some day, this hierarchy will provide
> implementations of several different data structures, in a few
> different flavors, at least I hope so. ;-)
>
>
> The Generic Package `ADT.Lists.Intrusive'
> -----------------------------------------
>
> This generic package provides a very simple list type which has the
> following characteristics:
>
> - The list is intrusive, i.e. an element can be contained in at most
> one list at a time, and the each element must provide a reference
> to the following element (this reference is accessed via `Next_Of'
> and `Set_Next').
>
> - This package can be used as a mixin to add list capabilities to
> another type.
>
> - The list implementation itself does not need any additional memory
> (except for storing the root of the list).
>
> - The list implementation is rather compact. Most of the more
> complex subprograms are generics themselves and do not contribute
> to the code footprint of a simple instantiation of the package.
>
> - The implementation does not use tagged types. The generic does
> not need to be instanced at library level.
>
> - All operations are not task safe. Two tasks may not operate on
> the same list object.
>
> In this section, time is measured by counting the simple operations
> (such as assignments of `Item_Access' values) and the number of
> `Next_Of', `Set_Next', `"="', and `Free' subprogram invocations for
> `Item_Access' objects.
>
> Assigning objects of type `List_Type' does not copy the elements of
> the list. Operations which change the list root (see below) render such
> copies invalid.
>
> Code example
> ------------
>
> declare
> type Integer_Item;
> type Integer_Item_Access is access Integer_Item;
> type Integer_Item is record
> Value : Integer;
> Next : Integer_Item_Access;
> end record;
>
> function Next_Of
> (Item : Integer_Item_Acces)
> return Integer_Item_Access
> is
> begin
> return Item.Next;
> end Next_Of;
>
> procedure Set_Next (Item, Next : Integer_Item_Access) is
> begin
> Item.Next := Next;
> end Set_Next;
>
> package Integer_Lists is
> new ADT.Lists.Intrusive (Integer_Item_Access, null);
> use List_Type;
>
> A_List : List_Type;
> package My_Append is new Generic_Append (A_List);
>
> Iter : Integer_Item_Access;
>
> begin
> -- Append the numbers from 1 to 10 to A_List.
> for J in 1 .. 10 loop
> My_Append.Append (new Integer_Item (J, null));
> end loop;
>
> -- Print out the items in A_List.
> -- (We could use a suitable For_Each instance instead.)
> Iter := First_Item (A_List);
> while not Is_Done (Iter) loop
> Put(Iter.Value);
> New_Line;
> Advance (Iter);
> end loop;
> end;
>
> Formal parameters of the generic
> --------------------------------
>
> The caller has to provide the following information when
> instantiating the generic:
>
> generic
> type Item_Access is private;
>
> Conceptually, this is a pointer to the actual item. Copying
> `Item_Access' values shall have no side effects, and it is assumed
> that such copy operations are not expensive in terms of CPU cycles.
>
> No_Item : Item_Access;
>
> An `Item_Access' value which is different from all values which
> denote actual objects. It is used to signal special conditions,
> much like a `null' access value.
>
> with function "=" (Left, Right : Item_Access) return Boolean is <>;
>
> Compares two `Item_Access' values, _and not the objects they refer
> to_. In particular, `No_Item = No_Item' must be true, and if `X =
> No_Item' is true, `X' is `No_Item'.
>
> with function Next_Of (Item : Item_Access) return Item_Access is <>;
>
> Returns the value of the `next element' field of ITEM (this value
> is stored in the object itself, not in the reference).
>
> with procedure Set_Next
> (Item : in Item_Access;
> Next : in Item_Access) is <>;
>
> Changes the `next element' field. This sets the return value of
> future calls to `Next_Of'.
>
> with procedure Free (Item : in out Item_Access) is <>;
>
> Deallocates ITEM and sets the variable to `No_Item'.
>
> Facilities provided by an instantiation
> ---------------------------------------
>
> An instantation of the generic provides the following facilities:
>
> type List_Type is private;
>
> The type of the list. Conceptually, this is a refernce to the
> first element of the list. A list is initially empty.
>
> procedure Free (List : in out List_Type);
>
> Calls `Free (Item : in out Item_Access)' for each item in LIST and
> makes LIST empty. (Changes root of LIST.)
>
> generic
> with function Clone_Item (Item : Item_Access) return Item_Access;
> procedure Clone (Source : in List_Type; Target : out List_Type);
>
> Creates a copy of the elements of SOURCE in TARGET. Before the
> cloning operation, all elements of TARGET are removed. `Clone' is
> called for each element in SOURCE, and these new elements are
> added to TARGET. SOURCE and TARGET have to be differnt. `Clone'
> changes the root of TARGET.
>
> If CLONE_ITEM returns `No_Item', the corresponding item of SOURCE
> is not included in `Target'. Using this convention, it is
> possible to filter the list while cloning it.
>
> procedure Transfer (Source, Target : in out List_Type);
>
> Frees TARGET, copies the SOURCE list to TARGET, and sets SOURCE to
> the empty list.
>
> function First_Item (List : List_Type) return Item_Access;
>
> Returns the first element of LIST, or `No_Item' if LIST is empty.
> Conceptually, the returned value is an iterator, see `Advance' and
> `Is_Done'. (Requires constant time.)
>
> function Last_Item (List : List_Type) return Item_Access;
>
> Returns the last element of LIST, or `No_Item' if LIST is empty.
> (Requires time proportional to the list length.)
>
> function Nth_Item
> (List : List_Type; Nth : Positive)
> return Item_Access;
>
> Returns the element which is at position NTH in LIST. The first
> element has the number 1. Raises `Index_Error' if LIST contains
> fewer elements than indicated by NTH. (Requires time
> proportionally to NTH.)
>
> procedure Advance (Item : in out Item_Access);
>
> Advances the iterator ITEM to the element after the old value of
> ITEM, or to `No_Item' if there is no following element. (Requires
> constant time.)
>
> function Is_Done (Item : Item_Access) return Boolean;
>
> Returns true if ITEM is equal to `No_Item', false otherwise.
> (Requires constant time.)
>
> function Is_Last (Item : Item_Access) return Boolean;
>
> Returns true if ITEM is the last element in the list, i.e. if
> `Is_Done (Next_Of (Item))' is true. If ITEM equals `No_Item',
> `Index_Error' is raised. (Requires constant time.)
>
> procedure Advance_To_Last (Item : in out Item_Access);
>
> Advances the iterator ITEM multiple times until `Is_Last (Item)'
> is true. Raises `Index_Error' if ITEM equals `No_Item'.
> (Requires time proportional to the tail of the list starting at
> ITEM.)
>
> procedure Add_At_Front
> (List : in out List_Type;
> Item : in Item_Access);
>
> Inserts ITEM at the beginning of LIST. ITEM becomes the new first
> element of LIST, and the position (see `Nth') of the following
> elements increases by 1. (Requires constant time, changes root of
> LIST.)
>
> procedure Add_After
> (List : in out List_Type;
> After : in Item_Access;
> Item : in Item_Access);
>
> Adds ITEM to LIST after item AFTER, and the position (see `Nth')
> of the following elements increases by 1. ITEM most not equal
> `No_Item', and AFTER must be part of LIST. (Requires constant
> time.)
>
> procedure Append (List : in out List_Type; Item : in Item_Access);
>
> Appends ITEM to LIST. making ITEM the last element of LIST. ITEM
> must not be `No_Item'. (Requires time proportional to the length
> of LIST, changes root of LIST if was empty.)
>
> procedure Append_List (First_List, Second_List : in out List_Type);
>
> Appends SECOND_LIST to FIRST_LIST and sets SECOND_LIST to the
> empty list. After this operation, FIRST_LIST contains the all the
> items it contained before, followed by the items of SECOND_LIST,
> in order. (Requires time proportional to the length of LIST,
> changes root of LIST if was empty.)
>
> type Append_Cache is limited private;
>
>
> procedure Append
> (List : in out List_Type;
> Item : in Item_Access;
> Cache : in out Append_Cache);
> procedure Append_List
> (First_List, Second_List : in out List_Type;
> First_Cache : in out Append_Cache);
> procedure Reset_Cache (Cache : out Append_Cache);
>
> These procedures are very similar to the ones above, but speed up
> repeated appends using a cache value for the list end. Each list
> to which items are appended requires a different cache. Between
> the calls to `Append' and `Append_List', elements may be added to
> the list, but no elements must be removed (unless `Reset_Cache' is
> called after items were removed). (If the cache is up to date
> (i.e. no elements were added by other means than `Append'),
> `Append' requires constant time after the first call. The first
> call of `Append' after a reset requires time proportional to the
> length of the list. A call to `Append_List' requires constant
> time, too, if the cache is up to date, but updating the cache in
> the next call to `Append' or `Append_List' requires time
> proportional to the length of SECOND_LIST.)
>
> generic
> List : in out List_Type;
> package Generic_Append is
> procedure Append (Item : in Item_Access);
> procedure Append_List (Second_List : in out List_Type);
> procedure Reset_Cache;
> procedure Free;
> end Generic_Append;
>
> This package wraps the `Append', `Append_List' `Reset_Cache'
> procedures above, together with an implicit cache. The same
> restrictions for removing elements apply. For convenience, you
> can clear the list by calling `Free'.
>
> procedure Remove_First (List : in out List_Type);
>
> Removes the first element of LIST and performs `Free' on it.
> Raises `Index_Error' if LIST is empty. (Requires constant time,
> changes root of LIST.)
>
> procedure Remove
> (List : in out List_Type;
> Item : in out Item_Access);
>
> Removes an arbitrary element ITEM from LIST and performs `Free' on
> it. Raises `Index_Error' if ITEM cannot be found in LIST.
> (Requires time proportional to the length of LIST, changes root of
> LIST.)
>
> function Is_Empty (List : List_Type) return Boolean;
>
> Returns true if LIST contains no elements, otherwise false.
> (Requires constant time.)
>
> function Length (List : List_Type) return Natural;
>
> Returns the number of elements contained in LIST. (Requires time
> proportional to the length of LIST.)
>
> generic
> type Item_Access_Index is range <>;
> type Item_Access_Array is array (Item_Access_Index range <>)
> of Item_Access;
> function To_Array (List : List_Type) return Item_Access_Array;
>
> Returns an array of all elements contained in LIST, preserving the
> order of elements. ITEM_ACCESS_INDEX must include the range from
> 1 to `Length (List)' (otherwise, `Constraint_Error' is raised).
>
> generic
> type Item_Access_Index is range <>;
> type Item_Access_Array is array (Item_Access_Index range <>)
> of Item_Access;
> procedure To_List
> (Source : in Item_Access_Array;
> Target : in out List_Type);
>
> Removes all elements from TARGET and replaces them with the
> elements in SOURCE, preserving order. (Changes root of TARGET.)
>
> generic
> with procedure Action (Item : Item_Access);
> procedure For_Each (List : List_Type);
>
> Iterates over LIST, i.e. calls ACTION for each element in LIST,
> obeying the order of elements. ACTION may destroy or otherwise
> modify ITEM, or destroy elements before ITEM in LIST.
>
> generic
> with function Predicate (Item : Item_Access) return Boolean;
> procedure Filter_In_Place (List : in out List_Type);
>
> Removes all elements from LIST (using `Remove') for which
> PREDICATE is false. The calls to PREDICATE are performed in the
> order the elements are stored in `List'. (Changes root of LIST.)
>
> See the `Clone' subprogram if you need a filtered copy of a list
> (which leaves the source list unaffected), or use the following
> subprogram.
>
> generic
> with function Predicate (Item : Item_Access) return Boolean;
> type Item_Access_Index is range <>;
> type Item_Access_Array is array (Item_Access_Index range <>)
> of Item_Access;
> function Filter (List : List_Type) return Item_Access_Array;
>
> Returns an array of all elements in LIST for which PREDICATE is
> true, preserving the order of elements. ITEM_ACCESS_INDEX must
> include the range from 1 to `Length (List)' (otherwise,
> `Constraint_Error' is raised). Internally, `Filter' creates an
> array with `Length (List)' elements. Therefore, if you want to
> select a few elements of a large list, you should probably use
> `Clone'.
>
> generic
> with function Predicate (Item : Item_Access) return Boolean;
> function First_That (List : List_Type) return Item_Access;
>
> Returns the first element of LIST for which PREDICATE is true, or
> `No_Item' if there is no such element.
>
> generic
> with function Predicate (Item : Item_Access) return Boolean;
> procedure Next_That (Item : in out Item_Access);
>
> Sets ITEM to the next element in the list after the old ITEM for
> which PREDICATE is true, or to `No_Item' if there is no such
> element.
>
> generic
> with function Predicate (Item : Item_Access) return Boolean;
> function Last_That (List : List_Type) return Item_Access;
>
> Returns the last element of LIST for which PREDICATE is true, or
> `No_Item' if there is no such element.
>
> procedure Split
> (Source : in out List_Type;
> Target_1, Target_2 : in out List_Type);
>
> Splits SOURCE to TARGET_1 and TARGET_2. The old lists TARGET_1
> and TARGET_2 are deallocated. After a `Split' operation, SOURCE
> is empty, and TARGET_1 and TARGET_2 contain all elements of
> SOURCE, and the length difference between TARGET_1 and TARGET_2 is
> at most 1. TARGET_1 and TARGET_2 have to be different lists.
> SOURCE, TARGET_1 and TARGET_2 have to refer to different lists.
> (Changes root of SOURCE, TARGET_1 and TARGET_2.)
>
> generic
> with function "<="
> (Left, Right : Item_Access)
> return Boolean is <>;
> procedure Merge
> (Source_1, Source_2 : in out List_Type;
> Target : in out List_Type);
>
> Merges SOURCE_1 and SOURCE_2 and stores the result in TARGET. In
> this case, "merging" means that if both source lists are sorted
> according to the "<=" predicate, TARGET will be sorted as well.
> All three lists have to be distinct. Note that the order in which
> equal elements are taken from SOURCE_1 and SOURCE_2 and put into
> TARGET is not defined, so it is not possible to construct a stable
> sorting procedure based on this routine. SOURCE_1, SOURCE_2 and
> TARGET have to denote different lists. (Changes root of SOURCE_1,
> SOURCE_2 and TARGET.)
>
> generic
> with function "<="
> (Left, Right : Item_Access)
> return Boolean is <>;
> procedure Sort (List : in out List_Type);
>
> Sorts LIST according to the "<=" predicate, in place. The sort
> operation is _not_ stable. (Changes root of LIST.)
--
-- Stephe
next prev parent reply other threads:[~2001-09-02 22:29 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2001-09-02 22:13 A generic list package (long) Florian Weimer
2001-09-02 22:29 ` Stephen Leake [this message]
2001-09-03 9:53 ` Florian Weimer
2001-09-03 16:18 ` A generic list package Florian Weimer
2001-09-03 17:16 ` Jeffrey Carter
2001-09-04 9:40 ` Florian Weimer
2001-09-04 16:42 ` Stephen Leake
2001-09-18 14:58 ` Lutz Donnerhacke
2001-09-18 19:07 ` Simon Wright
2001-09-19 8:05 ` Lutz Donnerhacke
2001-09-19 19:45 ` Simon Wright
2001-09-20 8:21 ` Lutz Donnerhacke
replies disabled
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox