[comp.sys.mac.programmer] How to best do a linked list on Mac

jess@gn.ecn.purdue.edu (Jess M Holle) (06/10/91)

I missed the discussion on linked list implementation awhile back.

I now need to implement a linked list on the Mac (or similar structure) and
would really appreciate a summary of the posts back then if somebody has
one still. 

I am going to be using a lot of small objects (~200 objects of say 296 
bits a piece) if this helps any.

Jess Holle

jtgorman@cs.arizona.edu (J. Taggart Gorman) (06/10/91)

In article <1991Jun10.002416.5656@gn.ecn.purdue.edu> jess@gn.ecn.purdue.edu (Jess M Holle) writes:
>I now need to implement a linked list on the Mac (or similar structure) and
>would really appreciate a summary of the posts back then if somebody has
>one still. 

  Hey mark me down on this list, too.  I have been avoiding dynamically
allocated structures in my game I'm working on like the plague.

  Is the solution a Handle, ie..

	dataStruct **dataHandle;

  ?????

|      J. Taggart Gorman Jr.     | "I'm a no rust build up man myself."
|                                |          -Christian Slater
| jtgorman@caslon.cs.arizona.edu |             in 'Heathers'

hairston@henry.ece.cmu.edu (David Hairston) (06/10/91)

[jess@gn.ecn.purdue.edu (Jess M Holle) writes:]
[] I now need to implement a linked list on the Mac (or similar structure) and
[] would really appreciate a summary of the posts back then if somebody has
[] one still. 

[jtgorman@cs.arizona.edu (J. Taggart Gorman) writes:]
[]  Hey mark me down on this list, too.  I have been avoiding dynamically
[] allocated structures in my game I'm working on like the plague.
[]
[]  Is the solution a Handle, ie..
[]
[]	dataStruct **dataHandle;

there is no "the solution"!  it depends!!  however there is no
substitute for understanding the issues involved (i'm not trying
to be arrogant, just helpful)!!!

in many cases, a dynamic linked list should be implemented as
(unlocked) handles with the handles data containing a handle to
the next element.  this lets the memory manager take care of
keeping the list "out of the way".

in some cases, handles are too slow (i.e. double dereference) and
in other cases you want to have better control over your heap
allocation so you go with pointers.  using pointers requires more
attention to detail on your part so that you avoid heap fragmentation,
if that is a problem to you, and so on.

the best solution is whatever is most appropriate in your case.
btw, the Memory Manager chapters in Inside Mac should be of some
assistance in this matter.

  -dave-  
hairston@henry.ece.cmu.edu

palmer@nntp-server.caltech.edu (David Palmer) (06/11/91)

hairston@henry.ece.cmu.edu (David Hairston) writes:

>[jess@gn.ecn.purdue.edu (Jess M Holle) writes:]
>[] I now need to implement a linked list on the Mac (or similar structure) and
>[] would really appreciate a summary of the posts back then if somebody has
>[] one still. 

>there is no "the solution"!  it depends!!  however there is no
>substitute for understanding the issues involved (i'm not trying
>to be arrogant, just helpful)!!!

>in many cases, a dynamic linked list should be implemented as
>(unlocked) handles with the handles data containing a handle to
>the next element.  this lets the memory manager take care of
>keeping the list "out of the way".

If you do this, you should make sure you call 'MoreMasters' enough at
the start of your program.

It all depends on how large a linked list you are trying to make (among other
things.)

-- 
		David Palmer
		palmer@gap.cco.caltech.edu
		...rutgers!cit-vax!gap.cco.caltech.edu!palmer
	"Operator, get me the number for 911"  --Homer Simpson

jess@gn.ecn.purdue.edu (Jess M Holle) (06/11/91)

Well, I received this response by e-mail and was asked to post it.

Here it is:
------------------------------------------------------------------
typedef struct MyLinkedListType {
	struct MyLinkedListType * * next ;
	char data [ DATASIZE ] ;
} MyLinkedListType ;

void
InsertAfter ( char * data , MyLinkedListType * * * head )
{
	MyLinkedListType * * it = ( MyLinkedListType * * ) NewHandle
		( sizeof ( * * it ) ) ;

	if ( ! it ) {
		SeriousOSErr ( outOfMemory ) ;
	}

	/* BlockMove doesn't move memory, so we don't have to
	   lock the handle */
	BlockMove ( data , ( * it ) -> data , DATASIZE ) ;

	if ( * head ) {
		( * it ) -> next = ( * * head ) -> next ;
		( * * head ) -> next = it ;
	} else {
		* head = it ;
		( * it ) -> next = NULL ;
	}
}
----------------------------------------------------------------

Jess Holle

francis@zaphod.uchicago.edu (Francis Stracke) (06/11/91)

In article <1991Jun10.170415.24845@nntp-server.caltech.edu> palmer@nntp-server.caltech.edu (David Palmer) writes:


>hairston@henry.ece.cmu.edu (David Hairston) writes:

>>[jess@gn.ecn.purdue.edu (Jess M Holle) writes:]
>>[] I now need to implement a linked list on the Mac (or similar structure) and
>>[] would really appreciate a summary of the posts back then if somebody has
>>[] one still. 

>>there is no "the solution"!  it depends!!  however there is no
>>substitute for understanding the issues involved (i'm not trying
>>to be arrogant, just helpful)!!!

My favorite solution is to declare an array type, then allocate a
handle to such an array, and resize it dynamically.

That way you can do it with only one handle (though you may want to
have an array of handles, if your records are large), and you can
index into the array rather than searching the list.

Credits: I got this idea from the TObjectList class in the ObjectDraw
sample app of ThP 2.0.  If you're using the TCL, of course, you can
use CCluster or CList.

--
/============================================================================\
| Francis Stracke	       | My opinions are my own.  I don't steal them.|
| Department of Mathematics    |=============================================|
| University of Chicago	       | Welcome to the Real World.  Enjoy the       |
| francis@zaphod.uchicago.edu  |  show.				 	     |
\============================================================================/

casseres@apple.com (David Casseres) (06/12/91)

In article <FRANCIS.91Jun10150034@math.uchicago.edu>,
francis@zaphod.uchicago.edu (Francis Stracke) writes:

> My favorite solution is to declare an array type, then allocate a
> handle to such an array, and resize it dynamically.
> 
> That way you can do it with only one handle (though you may want to
> have an array of handles, if your records are large), and you can
> index into the array rather than searching the list.

This works fine but it isn't a linked list -- it's a dynamically sized
array.  It's good as long as all you will do is add elements at the end
or remove them from the end, but it doesn't allow you to add elements
in the middle, or remove them from the middle and reclaim their storage
space.

David Casseres

scasterg@magnus.acs.ohio-state.edu (Stuart M Castergine) (06/13/91)

In article <14026@goofy.Apple.COM> casseres@apple.com (David Casseres) writes:
>In article <FRANCIS.91Jun10150034@math.uchicago.edu>,
>francis@zaphod.uchicago.edu (Francis Stracke) writes:
>
>> My favorite solution is to declare an array type, then allocate a
>> handle to such an array, and resize it dynamically.
>> 
>> That way you can do it with only one handle (though you may want to
>> have an array of handles, if your records are large), and you can
>> index into the array rather than searching the list.
>
>This works fine but it isn't a linked list -- it's a dynamically sized
>array.  It's good as long as all you will do is add elements at the end
>or remove them from the end, but it doesn't allow you to add elements
>in the middle, or remove them from the middle and reclaim their storage
>space.
>
>David Casseres

Here's something I learned long ago in a Pascal Class at OSU. I don't
program enough to remember the code, but it went something like this
in english:

pointer item

record item
	pointer prev
	real    data
	pointer next


Then you can dynamically create lists of any size, by having the
pointers in each item point to the previous and next items. You can
even make your list circular by having the end point back around to the
beginning.

The routines for navigating through the list are pretty simple, as I
remember. You just have a "thumb" that you use to point to your
current record, and maybe the first record, so you don't get lost :-)
I suspect you could have multiple thumbs.

 You can easily add items anywhere in the list by creating
a pointer to a new record and changing the pointers of the surrounding
items to refer to it.

I'd like to get back in the habit of programming enough to actually
remember this stuff (see my previous post).

-- 
scasterg@magnus.acs.ohio-state.edu	Stuart M Castergine
"Step by step they were led to practices which disposed to vice -- the
lounge, the bath, the elegant banquet. All this in their ignorance
they called civilisation, when it was but part of their servitude." 

wolf@piquet.cipl.uiowa.edu (Michael J. Wolf) (06/13/91)

In article <14026@goofy.Apple.COM> casseres@apple.com (David Casseres) writes:
>In article <FRANCIS.91Jun10150034@math.uchicago.edu>,
>francis@zaphod.uchicago.edu (Francis Stracke) writes:
>
>> My favorite solution is to declare an array type, then allocate a
>> handle to such an array, and resize it dynamically.
>> 
>> That way you can do it with only one handle (though you may want to
>> have an array of handles, if your records are large), and you can
>> index into the array rather than searching the list.
>
>This works fine but it isn't a linked list -- it's a dynamically sized
>array.  It's good as long as all you will do is add elements at the end
>or remove them from the end, but it doesn't allow you to add elements
>in the middle, or remove them from the middle and reclaim their storage
>space.
>
>David Casseres

If you wanted to not have empty sapces in the 'array' and wanted to do true insertions
so that your 'array' is always progressive, you could have your insert routine use a 
temphandle, set that handle to point to the array item number where you wish to insert,
then do a HandtoHand copy, resize the List handle to add another item, then add the item
at the insertion point (just write over the original items data) then do a HandTohand copy
from the temp handle back to the List handle.  This would be virtually the same for deleting
items also.

In this scheme you would not need to have a next or previous pointer since the array is
always progressive.

Just a thought...

Michael

laf@mitre.org (Lee Fyock) (06/14/91)

In article <14026@goofy.Apple.COM>, casseres@apple.com (David Casseres) writes:
> 
> In article <FRANCIS.91Jun10150034@math.uchicago.edu>,
> francis@zaphod.uchicago.edu (Francis Stracke) writes:
> 
> > My favorite solution is to declare an array type, then allocate a
> > handle to such an array, and resize it dynamically.
> > 
> > That way you can do it with only one handle (though you may want to
> > have an array of handles, if your records are large), and you can
> > index into the array rather than searching the list.
> 
> This works fine but it isn't a linked list -- it's a dynamically sized
> array.  It's good as long as all you will do is add elements at the end
> or remove them from the end, but it doesn't allow you to add elements
> in the middle, or remove them from the middle and reclaim their storage
> space.

For adding or deleting elements from the middel of an array that has
been allocated as a handle, use Munger!  For example, given that filesH
is a handle to an array of handles and fileIndex is the index (into the
array) of the handle you want to delete from the array,

				errLong = Munger(filesH,
					fileIndex * sizeof(Handle), NULL, (long) sizeof(char **), 1L, NULL);

will delete the fileIndex'th (:-) handle from the array.

Munger is documented in IM I, pages 468-470.


Lee Fyock
laf@mitre.org

time@ice.com (Tim Endres) (06/14/91)

In article <6455@ns-mx.uiowa.edu>, wolf@piquet.cipl.uiowa.edu (Michael J. Wolf) writes:
> If you wanted to not have empty sapces in the 'array' and wanted to do true insertions
> so that your 'array' is always progressive, you could have your insert routine use a 
> temphandle, set that handle to point to the array item number where you wish to insert,
> then do a HandtoHand copy, resize the List handle to add another item, then add the item
> at the insertion point (just write over the original items data) then do a HandTohand copy
> from the temp handle back to the List handle.  This would be virtually the same for deleting
> items also.
> 
> In this scheme you would not need to have a next or previous pointer since the array is
> always progressive.
> 
> Just a thought...

Have you thought about the time it takes to do tens of thousands of
BlockMove()'s of say 50 KBytes? Arrays are wonderful for things like
ptr's, but if my data structure is large, and I have many of them,
then array insertion and deletion is unacceptable.



-------------------------------------------------------------
Tim Endres                |  time@ice.com
ICE Engineering           |  uupsi!ice.com!time
8840 Main Street          |  Voice            FAX
Whitmore Lake MI. 48189   |  (313) 449 8288   (313) 449 9208

d88-jwa@byse.nada.kth.se (Jon W{tte) (06/14/91)

> laf@mitre.org (Lee Fyock) writes:

   > This works fine but it isn't a linked list -- it's a dynamically sized
   > array.  It's good as long as all you will do is add elements at the end
   > or remove them from the end, but it doesn't allow you to add elements
   > in the middle, or remove them from the middle and reclaim their storage
   > space.

   For adding or deleting elements from the middel of an array that has
   been allocated as a handle, use Munger!  For example, given that filesH
   is a handle to an array of handles and fileIndex is the index (into the
   array) of the handle you want to delete from the array,

Come on guys ! What's wrong with people these days ? You
can't do that as a general case since the memory move would
take forever when put to some serious use. The suggested
method of a handle with offsets in the record, and a free
list, seems OK. You could have a compaction routine that
purged the free list and resized the handle on idle time.

--
						Jon W{tte
						h+@nada.kth.se
						- Speed !

laf@mitre.org (Lee Fyock) (06/14/91)

In article <D88-JWA.91Jun14085334@byse.nada.kth.se>, d88-jwa@byse.nada.kth.se (Jon W{tte) writes:
> 
> > laf@mitre.org (Lee Fyock) writes:
> 
>    > This works fine but it isn't a linked list -- it's a dynamically sized
>    > array.  It's good as long as all you will do is add elements at the end
>    > or remove them from the end, but it doesn't allow you to add elements
>    > in the middle, or remove them from the middle and reclaim their storage
>    > space.
> 
>    For adding or deleting elements from the middel of an array that has
>    been allocated as a handle, use Munger!  For example, given that filesH
>    is a handle to an array of handles and fileIndex is the index (into the
>    array) of the handle you want to delete from the array,
> 
> Come on guys ! What's wrong with people these days ? You
> can't do that as a general case since the memory move would
> take forever when put to some serious use. The suggested
> method of a handle with offsets in the record, and a free
> list, seems OK. You could have a compaction routine that
> purged the free list and resized the handle on idle time.

Chill out!  The first guy didn't give any conditions about what he wanted
to use the list for.  The array method works spiffy keen (and is very clean)
for lists that aren't altered much.  BTW, what's this list going to be used
for?  Maybe we can kick around some hashed B-tree implementations for
a while!  :-)


Lee Fyock
laf@mitre.org

strasser@psych.psy.uq.oz.au (Michael Strasser) (06/14/91)

I have looked at the code for the TList class in MacApp 2.0b9.
It sets up a pseudo-linked list (which is really a variable-
sized array in memory) with methods for insertion, deletion
etc., and an Each method which is used to perform operations
on each element in turn.  All the elements must be the same
size (which might be a limitation to you).

Anyway (if I remember correctly), the coding uses SetHandleSize 
to change the size of the chunk of memory, and Munger to shift
things around.  During an Each "loop", any deletions are not
made completely, but elements are marked as deleted, and
actually tidied up at the end of all Each "loops" (which may
be nested).

I hope this helps you a bit.


--
Mike Strasser
Psychology Dept
The University of Queensland

aries@rhi.hi.is (Mimir Reynisson) (06/21/91)

As has been noted "billions" of times before which method suits you
best depends on what you intend to do (that is probably why there are
some many different storage and retrieval methods available to-day).

Anyways, here's my 0.02$

Below you'll find a brief summary of which method suits which data,
my list is of course far from complete, but it should suffice to give
an idea of these structures.

One note before we start, I'm not really into hashing so I didn't include
it here. There are quite a lot of reasons for discussing hashing, but
as Clint Eastwood says: "right now I can't think of one" :)

Fast access methods:

 Ordered
  Binary trees
   Very fast tree structure as long as the tree doesn't get
   too unbalanced. It the tree get's unbalanced it will be
   no better than a sequential search through a linked list.
   However, it's probably the simplest tree to implement and
   most of the time more than enough. Insertion and deletion
   are quite fast, so it should be very good for procedures
   that require fast insertion, deletion, and retrieval of
   relatively randomly entered data. Good for variable tables
   of simple language compilers, cross reference generators, and
   other tables of random size, which require fast insertion,
   deletion, and retrieval.

  2-3 trees
   2-3 trees are basically binary trees, except they can have
   3 and sometimes 4 nodes instead of the usual 2. This as far
   as I can remember reduces the likely-hood of unbalanced trees.
   Haven't used this one in a long time, so I'm not quite sure.

  Balanced trees
   The complexity of the balancing operations suggests that balanced
   trees should be used only if infromation retrievals are considerably
   more frequent than insertions. This is particualarily true because the
   nodes of such search trees are usually implemented as densely packed
   records in order to economize storage. The problem is that empirical
   evaluations show that balanced trees lose much of their appeal if
   tight record packing is mandatory. It is indeed difficult to beat
   th straightforward, simple tree insertion algorithm :)

  Optimal trees
   Optimal search trees are hard to describe accuratly because as far
   as our consideration of data structure here goes, they have all based
   on the assumption that frequency of access is equal for all nodes,
   that is, all keys are equally probable to occur in a search. However,
   sometimes (really the exceptional cases) in which data about the
   probabilities of access to individual keys is available. If you have
   that data you should look into this structure; otherwise, this isn't
   a good place to look.

  B Trees and B+ Trees
   Quite an efficent data structure if the data is resonably
   random and most importantly that the data requires to be
   stored on an external storage device, like a hard-disk.
   It's really silly-as far as I'm concerned-to create a B Tree
   or a B+ Tree just for a memory resident data and not to mention
   horrible inefficent. B Trees are disk based data structures
   and quite good disk based data structure, but disk based data
   structures all the same. Look at 2-3 trees or binary trees if
   you need a memory based tree structure. Good for database index
   files and other disk based data.

  Sorted arrays
   Excellent for data that doesn't grow in size or if it does
   contains few items on the whole, because the entire array
   will have to be sorted or at least some part of it moved
   around in memory.

 Not ordered
  Static arrays
   Contains information that is of a fixed size or below
   the maximum size. Good for data that doesn't require
   a lot of space such as character strings, static tables,
   etc ... Should preferably not be used for structures that
   require searching unless the array has few items or the
   search is a rare procedure. Searching sequentially through
   200 - 500 is not noticable unless the search is all the more
   frequent. Above 500 things start to get a little slow.

  Dynamic arrays
   Contains information that might grow or shrink over a
   period of time. Good for structure that don't have a lot
   of items, such as window record storage lists, but are
   added to irregularily or randomly and might cause heap
   fragmentation by other means.

Slow access methods:

  Linked-lists
   Linked lists or linear lists are the simplest way to interrelate
   or link a set of elements. Insertion and deletion is fast. Searching,
   however, is linear except in the case of ordered list. The biggest
   disadvantage to this method is that it consumes a lot of pointers or
   handles if your each element is large. So if you have a list with few
   elements and each element isn't very large, a dynamic array is probably
   a better bet. A simple solution to solve heap fragmenation is to allocate
   your own heap and take care of the free-list yourself. This could be
   very fast if all the elements are of a fixed size and you somehow manage
   to resize your heap intelligently. Remember that the minimum number of
   a dynamically allocated block is 12 bytes, regardless of whether you
   use all these 12 bytes. So a linked list of integers whould be wasteful.

  Sets
   Sets are special structures that require that there be only one of each
   elements. They are usually a composition of several structure unless it
   is a bit set. I just thought I should mention them, since I was on a
   roll anyway.

  Bags (heaps)
   Since I mention sets I decided I should mention bags too :) Bags are sets
   basically, except for a minor detail. Bags are not sets, that is, they
   are allow multiple cases of the same key (or element).

---
Hope this helps a little bit :)

Mimir (aries@rhi.hi.is)
Aries Software, Inc.