6 use vars qw($VERSION $BRANCH);
7 $VERSION = sprintf( "%d.%03d", q$Revision$ =~ /(\d+)\.(\d+)/ );
8 $BRANCH = sprintf( "%d.%03d", q$Revision$ =~ /\d+\.\d+\.(\d+)\.(\d+)/ || (0,0));
9 $main::build += $VERSION;
10 $main::branch += $BRANCH;
12 use constant NEXT => 0;
13 use constant PREV => 1;
14 use constant OBJ => 2;
16 use vars qw($docheck);
22 confess("chain broken $_[1]") unless ref $_[0] && $_[0]->isa('Chain') &&
23 $_[0]->[PREV]->[NEXT] == $_[0] &&
24 $_[0]->[NEXT]->[PREV] == $_[0];
28 # set internal checking
38 my $name = ref $pkg || $pkg;
41 push @$self, $self, $self, @_;
42 return bless $self, $name;
45 # Insert before this point of the chain
50 $docheck && _check($p);
52 my $q = ref $ref && $ref->isa('Chain') ? $ref : Chain->new($ref);
53 $q->[PREV] = $p->[PREV];
55 $p->[PREV]->[NEXT] = $q;
59 # Insert after this point of the chain
64 $docheck && _check($p);
66 $p->[NEXT]->ins($ref);
69 # Delete this item from the chain, returns the NEXT item in the chain
74 $docheck && _check($p);
76 my $q = $p->[PREV]->[NEXT] = $p->[NEXT];
77 $p->[NEXT]->[PREV] = $p->[PREV];
78 $p->[NEXT] = $p->[PREV] = undef;
82 # Is this chain empty?
87 $docheck && _check($p);
89 return $p->[NEXT] == $p;
92 # return next item or undef if end of chain
97 $docheck && _check($base);
99 return $base->[NEXT] == $base ? undef : $base->[NEXT] unless $p;
101 $docheck && _check($p);
103 return $p->[NEXT] != $base ? $p->[NEXT] : undef;
106 # return previous item or undef if end of chain
111 $docheck && _check($base);
113 return $base->[PREV] == $base ? undef : $base->[PREV] unless $p;
115 $docheck && _check($p);
117 return $p->[PREV] != $base ? $p->[PREV] : undef;
120 # return (and optionally replace) the object in this chain item
124 $p->[OBJ] = $ref if $ref;
128 # clear out the chain
132 while (!$base->isempty) {
137 # move this item after the 'base' item
142 $docheck && _check($base, "base") && _check($p, "rechained ref");
148 # count the no of items in a chain
155 ++$count while ($p = $base->next($p));
161 # Below is the stub of documentation for your module. You better edit it!
165 Chain - Double linked circular chain handler
170 $base = new Chain [$obj];
171 $p->ins($ref [,$obj]);
172 $p->add($ref [,$obj]);
173 $ref = $p->obj or $p->obj($ref);
174 $q = $base->next($p);
175 $q = $base->prev($p);
186 A module to handle those nasty jobs where a perl list simply will
187 not do what is required.
189 This module is a transliteration from a C routine I wrote in 1987, which
190 in turn was taken directly from the doubly linked list handling in ICL
191 George 3 originally written in GIN5 circa 1970.
193 The type of list this module manipulates is circularly doubly linked
194 with a base. This means that you can traverse the list backwards or
195 forwards from any point.
197 The particular quality that makes this sort of list useful is that you
198 can insert and delete items anywhere in the list without having to
199 worry about end effects.
201 The list has a I<base> but it doesn't have any real end! The I<base> is
202 really just another (invisible) list member that you choose to
203 remember the position of and is the reference point that determines
206 There is nothing special about a I<base>. You can choose another member
207 of the list to be a I<base> whenever you like.
209 The difference between this module and a normal list is that it allows
210 one to create persistant arbitrary directed graphs reasonably
211 efficiently that are easy to traverse, insert and delete objects. You
212 will never need to use I<splice>, I<grep> or I<map> again (for this
215 A particular use of B<Chain> is for connection maps that come and go
216 during the course of execution of your program.
218 An artificial example of this is:-
222 my $base = new Chain;
223 $base->ins({call=>'GB7BAA', users => new Chain});
224 $base->ins({call=>'GB7DJK', users => new Chain});
225 $base->ins({call=>'GB7MRS', users => new Chain});
227 # order is now GB7BAA, GB7DJK, GB7MRS
230 while ($p = $base->next($p)) {
232 if ($obj->{call} eq 'GB7DJK') {
233 my $ubase = $obj->{users};
234 $ubase->ins( {call => 'G1TLH'} );
235 $ubase->ins( {call => 'G7BRN'} );
236 } elsif ($obj->{call} eq 'GB7MRS') {
237 my $ubase = $obj->{users};
238 $ubase->ins( {call => 'G4BAH'} );
239 $ubase->ins( {call => 'G4PIQ'} );
240 } elsif ($obj->{call} eq 'GB7BAA') {
241 my $ubase = $obj->{users};
242 $ubase->ins( {call => 'G8TIC'} );
243 $ubase->ins( {call => 'M0VHF'} );
247 # move the one on the end to the beginning (LRU on a stick :-).
248 $base->rechain($base->prev);
250 # order is now GB7MRS, GB7BAA, GB7DJK
252 # this is exactly equivalent to :
257 # order is now GB7DJK, GB7MRS, GB7BAA
259 # disconnect (ie remove) GB7MRS
260 for ($p = 0; $p = $base->next($p); ) {
261 if ($p->obj->{call} eq 'GB7MRS') {
262 $p->del; # remove this 'branch' from the tree
263 $p->obj->{users}->flush; # get rid of all its users
272 Dirk Koopman <djk@tobit.co.uk>
276 ICL George 3 internals reference manual (a.k.a the source)