File Coverage

blib/lib/LinkedList/Single.pm
Criterion Covered Total %
statement 99 208 47.6
branch 20 68 29.4
condition 5 22 22.7
subroutine 21 41 51.2
pod 28 32 87.5
total 173 371 46.6


line stmt bran cond sub pod time code
1             ########################################################################
2             # housekeeping
3             ########################################################################
4              
5             package LinkedList::Single;
6              
7 8     8   10355 use v5.16;
  8         32  
  8         338  
8              
9 8     8   48 use Carp;
  8         14  
  8         689  
10              
11 8     8   63 use Scalar::Util qw( blessed refaddr reftype looks_like_number );
  8         15  
  8         594  
12 8     8   9269 use Symbol;
  8         8838  
  8         1821  
13              
14             use overload
15             (
16             q{bool} =>
17             sub
18             {
19             # the list handler is true if the current
20             # node is not empty (i.e., is not at the
21             # final node).
22             #
23             # this allows for:
24             #
25             # $listh->head; while( $listh ){ ... }
26              
27 0     0   0 my $listh = shift;
28              
29 0         0 $$listh # && @{ $$listh }
30             },
31              
32             # return a node at the given offset.
33             # update the list to that point.
34              
35             q{+} =>
36             sub
37             {
38 6 50   6   15 my ( $listh, $offset )
39             = $_[2]
40             ? @_[1,0]
41             : @_[0,1]
42             ;
43              
44 6   33     21 my $node = $$listh || $listh->head_node;
45              
46             # i.e., $offset == 0, gets ( 1 .. 0 ) for no change.
47              
48 6         11 for ( 1 .. $offset )
49             {
50 15 50       40 $node or last;
51              
52 15         30 $node = $node->[0];
53             }
54              
55             $node
56 6         14 },
57              
58             q{+=} =>
59             sub
60             {
61 6 50   6   34 my ( $listh, $offset )
62             = $_[2]
63             ? @_[1,0]
64             : @_[0,1]
65             ;
66              
67 6         12 $$listh = $listh + $offset;
68              
69 6         17 $listh
70             },
71              
72 8     8   56 );
  8         20  
  8         136  
73              
74             ########################################################################
75             # package variables
76             ########################################################################
77              
78             our $VERSION = '1.02';
79             $VERSION = eval $VERSION;
80              
81             # inside-out data for the heads of the lists.
82              
83             my %rootz = ();
84              
85             ########################################################################
86             # utility subs
87             ########################################################################
88              
89             ########################################################################
90             # perl's recursive cleanups is no longer an issue after 5.16.
91              
92             my $cleanup
93             = sub
94             {
95             my $node = shift;
96             @$node = ();
97              
98             return
99             };
100              
101              
102             ########################################################################
103             # public interface
104             ########################################################################
105              
106             # entry with a link-to-empty-next.
107             #
108             # the nested arrayref is the first
109             # node on the list. this is required
110             # for unshift to add the first node
111             # after the head.
112              
113             sub construct
114             {
115 27     27 1 62 my $proto = shift;
116              
117 27   66     543 my $listh = bless \[], blessed $proto || $proto;
118              
119 27         448 $rootz{ refaddr $listh } = [ $$listh ];
120              
121 27         70 $listh
122             }
123              
124             sub initialize
125             {
126 27     27 1 47 my $listh = shift;
127              
128             # data for the list is on the stack.
129             # otherwise the caller gets back an
130             # empty list.
131              
132 27 100       85 if( @_ )
133             {
134             # no telling if somone overloaded new and
135             # moved the node. only fix is to re-set the
136             # thing to the head before adding the data.
137             #
138             # or... they know enough to leave it alone
139             # if they want to so this should just take
140             # the location as-is.
141              
142 23         71 my $node = $listh->head_node;
143              
144             ( $node ) = @$node = ( [], $_ )
145 23         145567 for @_;
146             }
147             else
148             {
149 4         15 $listh->head;
150             }
151              
152             return
153 27         73 }
154              
155             sub new
156             {
157 27     27 1 44789 my $listh = &construct;
158              
159 27         2545 $listh->initialize( @_ );
160              
161 27         96 $listh
162             }
163              
164             sub clone
165             {
166             # recycle the head node
167              
168 0     0 1 0 my $listh = shift;
169              
170 0   0     0 my $clone = bless \[ $$listh ], blessed $listh || $listh;
171              
172 0         0 $rootz{ refaddr $clone } = $rootz{ refaddr $listh };
173              
174 0         0 $clone
175             }
176              
177             sub DESTROY
178             {
179 27     27   25158 my $head = delete $rootz{ refaddr shift };
180              
181 27         189 $#$head = -1;
182              
183             return
184 27         19357 }
185              
186             # if $rootz{ $key } isn't removed then the
187             # node = node->next approach works just fine.
188             # so, truncate can use the faster aproach.
189              
190             sub truncate
191             {
192 0     0 1 0 my $listh = shift;
193              
194 0         0 my $node = $$listh;
195              
196 0         0 my $next = splice @$node, 0, 1, [];
197              
198 0         0 $cleanup->( $next );
199              
200 0         0 $listh
201             }
202              
203             sub replace
204             {
205 0     0 1 0 my $listh = shift;
206              
207 0         0 $listh->head->truncate;
208              
209 0         0 $listh->initialize( @_ );
210              
211 0         0 $listh
212             }
213              
214             ########################################################################
215             # basic information: the current node referenced by the list handler
216             #
217             # calling node without an argument returns the current one, with one
218             # sets the node. this allows for tell/reset-style stacking of node
219             # positions.
220              
221             sub node
222             {
223 1     1 1 3 my $listh = shift;
224              
225             @_
226 1 50       6 ? $$listh = shift
227             : $$listh
228             }
229              
230             ########################################################################
231             # hide extra data in the head node after the first-node ref.
232             #
233             # splice with $#$head works since 1 .. end == length - 1 == offset.
234              
235             sub set_meta
236             {
237 0     0 1 0 my $listh = shift;
238              
239 0         0 my $root = $listh->root;
240              
241 0         0 splice @$root, 1, $#$root, @_;
242              
243 0         0 $listh
244             }
245              
246             sub add_meta
247             {
248 0     0 1 0 my $listh = shift;
249              
250 0         0 my $root = $listh->root;
251              
252 0         0 push @$root, @_;
253              
254 0         0 $listh
255             }
256              
257             sub get_meta
258             {
259 0     0 1 0 my $root = $_[0]->root;
260              
261             wantarray
262 0         0 ? @{ $root }[ 1 .. $#$root ]
  0         0  
263 0 0       0 : [ @{ $root }[ 1 .. $#$root ] ]
264             }
265              
266             ########################################################################
267             # node/list status
268              
269             sub has_nodes
270             {
271             # i.e., is the list populated?
272              
273 0     0 1 0 my $root = $_[0]->root;
274              
275 0         0 !! @{ $root->[0] }
  0         0  
276             }
277              
278             sub has_next
279             {
280             # i.e., while( $node->has_next ){ ... }
281              
282 0     0 1 0 my $listh = shift;
283              
284 0         0 scalar @{ $$listh->[0] }
  0         0  
285             }
286              
287             sub is_empty
288             {
289             # Q: does the current node have data?
290             # A: it will if there is more than one element.
291              
292 0     0 1 0 my $listh = shift;
293              
294 0         0 ! @{ $$listh }
  0         0  
295             }
296              
297             sub node_data
298             {
299 106     106 0 374 my $listh = shift;
300 106         157 my $node = $$listh;
301              
302             # return the existing data, sans the next ref.
303              
304             wantarray
305 106         360 ? @{ $node }[ 1 .. $#$node ]
  0         0  
306 106 50       275 : [ @{ $node }[ 1 .. $#$node ] ]
307             }
308              
309             sub next_data
310             {
311 0     0 0 0 my $listh = shift;
312 0         0 my $node = $$listh;
313 0         0 my $next = $node->[0];
314              
315 0         0 my @valz = @{ $next }[ 1 .. $#$next ];
  0         0  
316              
317             wantarray
318             ? @valz
319 0 0       0 : \@valz
320             }
321              
322             sub set_data
323             {
324 0     0 1 0 my $listh = shift;
325 0         0 my $node = $$listh;
326              
327             # any data to replace the current data is
328             # left on the stack.
329              
330             # return the existing data.
331              
332 0 0       0 if( defined wantarray )
333             {
334 0         0 my @valz = @{ $node }[ 1 .. $#$node ];
  0         0  
335              
336 0 0       0 @_ and splice @$node, 1, $#$node, @_;
337              
338             wantarray
339             ? @valz
340 0 0       0 : \@valz
341             }
342             else
343             {
344 0         0 splice @$node, 1, $#$node, @_;
345              
346             return
347 0         0 }
348             }
349              
350             sub clear_data
351             {
352 0     0 1 0 my $listh = shift;
353 0         0 my $node = $$listh;
354              
355 0         0 splice @$node, 1;
356              
357 0         0 $listh
358             }
359              
360             sub list_data
361             {
362 0     0 0 0 my $listh = shift;
363 0         0 my $node = $$listh;
364              
365 0         0 my @return = ();
366              
367 0         0 while( @$node )
368             {
369 0         0 my ( $node, @data ) = @$node;
370 0         0 push @return, \@data;
371             }
372              
373             wantarray
374             ? @return
375 0 0       0 : \@return
376             }
377              
378             ########################################################################
379             # access the list head.
380             #
381             # root is mainly useful for testing,
382             # head_node for externally walking the
383             # list (i.e., when OO calls are too expensive).
384             #
385             # new_head is surgery: replace the head node.
386             # leaves most sanity checks in the
387             # caller's hands.
388             #
389             # mainly useful for cross-linked lists.
390              
391             sub new_root
392             {
393 0     0 0 0 my $listh = shift;
394              
395             # called without arguments is an error: no reason to
396             # do it and it can cause real pain if not caught.
397              
398 0 0       0 my $root = shift
399             or confess "Bogus new_root: false root (use truncate instead?)";
400              
401 0 0       0 'ARRAY' eq reftype $root
402             or confess "Bogus new_root: non-arrayref root";
403              
404 0   0     0 $root->[0] ||= [];
405              
406 0         0 $$listh = $root->[0];
407              
408 0         0 my $key = refaddr $listh;
409              
410 0         0 $cleanup->( delete $rootz{ $key } );
411              
412 0         0 $rootz{ $key } = $root;
413              
414 0         0 $listh
415             }
416              
417             sub new_head
418             {
419 1     1 1 2 my ( $listh, $head ) = @_;
420              
421 1         2 my $root = $listh->root_node;
422              
423 1         3 $head = splice @$root, 0, 1, $head;
424              
425 1         2 $cleanup->( $head );
426              
427 1         2 $listh->head
428             }
429              
430             sub root_node
431             {
432 36     36 1 154 scalar $rootz{ refaddr $_[0] }
433             }
434              
435             sub head_node
436             {
437 34     34 1 58 my $listh = shift;
438              
439 34         84 my $root = $listh->root_node;
440              
441 34         99 $root->[0]
442             }
443              
444             sub root
445             {
446 1     1 1 12 my $listh = shift;
447 1         7 my $root = $listh->root_node;
448 1         3 $$listh = $root;
449              
450 1         3 $listh
451             }
452              
453             sub head
454             {
455 8     8 1 20 my $listh = shift;
456 8         22 my $head = $listh->head_node;
457 8         17 $$listh = $head;
458              
459 8         18 $listh
460             }
461              
462             ########################################################################
463             # walk the list.
464              
465             sub next
466             {
467 100     100 1 44683 my $listh = shift;
468 100         183 my $node = $$listh;
469              
470 100 50       229 my $count = @_ ? shift : 1;
471              
472 100 50       321 looks_like_number $count
473             or croak "Bogus move: '$count' is not a number";
474              
475 100         241 for( 1 .. $count )
476             {
477 100 50       203 @$node or last;
478            
479 100         280 $node = $node->[0];
480             }
481              
482 100         287 $$listh = $node;
483              
484 100         260 $listh
485             }
486              
487             # this returns false for the scalar
488             # case, where an empty node returns
489             # an empty arrayref.
490             #
491             # Q: Better to reset or stall and EOL?
492             # A: Better leaving the reset to the caller, who knows
493             # what to expect in that case.
494              
495             sub each
496             {
497 12     12 1 3322 my $listh = shift;
498 12         18 my $node = $$listh;
499              
500 12 50       25 $node
501             or return;
502              
503 12         26 ( $$listh, my @valz ) = @$node;
504              
505             wantarray
506             ? @valz
507 12 50       35 : \@valz
508             }
509              
510             ########################################################################
511             # keep this section at the end to avoid uses with CORE::* functions.
512             ########################################################################
513             # modify the list
514             #
515             # add uses a relative position (e.g., for insertion sort), others
516             # use the head (or last) node.
517              
518             sub add
519             {
520 0     0 1 0 my $listh = shift;
521 0         0 my $node = $$listh;
522              
523             # insert after the current node.
524              
525 0         0 $node->[0] = [ $node->[0], @_ ];
526              
527 0         0 $listh
528             }
529              
530             # shift and cut do the same basic thing, question
531             # is whether it's done mid-list or at the head.
532             # pop could work this way if it weren't so bloody
533             # expensive to find/maintain the end of a list.
534             #
535             # note that shift has one bit of extra work in that
536             # it has to replace $$listh when it currently references
537             # the first node.
538              
539             sub cut
540             {
541             # no need to modify $$listh here since the
542             # node after the current one is always removed.
543              
544 0     0 1 0 my $listh = shift;
545 0         0 my $node = $$listh;
546              
547             # nothing to cut if we are at the end-of-list.
548             # or the node prior to it.
549              
550 0 0       0 @{ $node->[0] }
  0         0  
551             or return;
552              
553 0 0       0 if( defined wantarray )
554             {
555 0         0 ( $node->[0], my @valz ) = @{ $node->[0] };
  0         0  
556              
557             wantarray
558             ? @valz
559 0 0       0 : \@valz
560             }
561             else
562             {
563             # just discard the data if the
564             # user doesn't want it.
565              
566 0         0 $node->[0] = $node->[0][0];
567             }
568             }
569              
570             ########################################################################
571             # put these last to avoid having to use CORE::*
572             # everywhere else.
573              
574             sub splice
575             {
576 1     1 1 5 my $listh = shift;
577 1   50     3 my $count = shift || 0;
578              
579 1 50       5 looks_like_number $count
580             or croak "Bogus splice: non-numeric count '$count'";
581              
582 1 50       3 $count < 0
583             and croak "Bogus splice: negative count '$count'";
584              
585             # short circut if there is nothing to do.
586              
587 1 50 33     5 $count > 0 || @_ or return;
588              
589 1 50       5 my $node = $$listh
590             or confess "Bogus splice: empty list handler";
591              
592 1         2 my $dead = '';
593              
594 1 50       3 if( $count > 0 )
595             {
596 1         2 my $tail = $node;
597              
598 1         2 for( 1 .. $count )
599             {
600 3 50       9 @$tail or last;
601              
602 3         5 $tail = $tail->[0];
603             }
604              
605             # this is the start of the chain that gets removed.
606             # keep it alive for a few steps to see if the caller
607             # wants it back or we should clean it up.
608             #
609             # after that, splice the node out of the list.
610              
611 1         23 $dead = $node->[0];
612 1         3 $node->[0] = delete $tail->[0];
613 1         2 $tail->[0] = [];
614             }
615              
616             # at this point $dead is either false or
617             # a runt linked list lacking its terminating
618             # node.
619             #
620             # insert anything on the stack after the
621             # current node.
622              
623 1         2 for( @_ )
624             {
625 0         0 $node = $node->[0] = [ $node->[0], $_ ];
626             }
627              
628             # nothing to return or clean up if there
629             # wasn't anything removed.
630              
631 1 50       3 $dead or return;
632              
633             # if the caller wants anything back then
634             # clean up the dead chain and hand it back.
635             #
636             # alternative: array of data?
637              
638 1 50       3 if( defined wantarray )
639             {
640             # hand back a linked list with $dead as the head node.
641            
642 1         3 my $new = $listh->new;
643              
644 1         1 my $head = $$new;
645              
646 1         3 @$head = @$dead;
647              
648 1         4 return $new
649             }
650             else
651             {
652 0           $cleanup->( $dead );
653              
654             return
655 0           }
656             }
657              
658             ########################################################################
659             # aside: push can be very expensive.
660             # but, then, so is maintaining a separate
661             # node-before-the-tail entry.
662             #
663             # successive pushes are quite fast, due to
664             # leaving $$listh on the newly added node,
665             # which leaves the while loop running only
666             # once per push.
667              
668             sub push
669             {
670 0     0 1   my $listh = shift;
671 0           my $node = $$listh;
672              
673 0           $node = $node->[0]
674             while @$node;
675              
676             # at this point we're at the list tail: the
677             # empty placeholder arrayref. populate it in
678             # place with a new tail.
679              
680 0           @$node = ( [], @_ );
681              
682 0           $$listh = $node;
683              
684 0           $listh
685             }
686              
687             sub unshift
688             {
689 0     0 1   my $root = $_[0]->root_node;
690              
691 0           $root->[0] = [ $root->[0], @_ ];
692              
693 0           $_[0]
694             }
695              
696             sub shift
697             {
698 0     0 1   my $listh = shift;
699 0           my $root = $listh->root_node;
700              
701             # need to replace $listh contents if it
702             # referrs to the head we are removing!
703              
704 0 0         $$listh = ''
705             if $$listh == $root->[0];
706              
707 0 0         if( defined wantarray )
708             {
709 0           my @valz = @{ $root->[0] };
  0            
710              
711 0           $root->[0] = shift @valz;
712              
713 0   0       $$listh ||= $root->[0];
714              
715             wantarray
716             ? @valz
717 0 0         : \@valz
718             }
719             else
720             {
721             # get this over with for cases where
722             # the user doesn't want the data.
723              
724 0 0         $root->[0][0]
725             and $root->[0] = $root->[0][0];
726              
727 0   0       $$listh ||= $root->[0];
728              
729             return
730 0           }
731             }
732              
733             # keep require happy
734              
735             1
736              
737             __END__