1 /**
2 This module implements a singly-linked list container.
3 It can be used as a stack.
4
5 This module is a submodule of $(MREF std, container).
6
7 Source: $(PHOBOSSRC std/container/slist.d)
8
9 Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders.
10
11 License: Distributed under the Boost Software License, Version 1.0.
12 (See accompanying file LICENSE_1_0.txt or copy at $(HTTP
13 boost.org/LICENSE_1_0.txt)).
14
15 Authors: $(HTTP erdani.com, Andrei Alexandrescu)
16
17 $(SCRIPT inhibitQuickIndex = 1;)
18 */
19 module std.container.slist;
20
21 ///
22 @safe unittest
23 {
24 import std.algorithm.comparison : equal;
25 import std.container : SList;
26
27 auto s = SList!int(1, 2, 3);
28 assert(equal(s[], [1, 2, 3]));
29
30 s.removeFront();
31 assert(equal(s[], [2, 3]));
32
33 s.insertFront([5, 6]);
34 assert(equal(s[], [5, 6, 2, 3]));
35
36 // If you want to apply range operations, simply slice it.
37 import std.algorithm.searching : countUntil;
38 import std.range : popFrontN, walkLength;
39
40 auto sl = SList!int(1, 2, 3, 4, 5);
41 assert(countUntil(sl[], 2) == 1);
42
43 auto r = sl[];
44 popFrontN(r, 2);
45 assert(walkLength(r) == 3);
46 }
47
48 public import std.container.util;
49
50 /**
51 Implements a simple and fast singly-linked list.
52 It can be used as a stack.
53
54 `SList` uses reference semantics.
55 */
56 struct SList(T)
57 if (!is(T == shared))
58 {
59 import std.exception : enforce;
60 import std.range : Take;
61 import std.range.primitives : isInputRange, isForwardRange, ElementType;
62 import std.traits : isImplicitlyConvertible;
63
64 private struct Node
65 {
66 Node * _next;
67 T _payload;
68 }
69 private struct NodeWithoutPayload
70 {
71 Node* _next;
72 }
73 static assert(NodeWithoutPayload._next.offsetof == Node._next.offsetof);
74
75 private Node * _root;
76
77 private void initialize() @trusted nothrow pure
78 {
79 if (_root) return;
80 _root = cast (Node*) new NodeWithoutPayload();
81 }
82
83 private ref inout(Node*) _first() @property @safe nothrow pure inout
84 {
85 assert(_root, "root pointer must not be null");
86 return _root._next;
87 }
88
89 private static Node * findLastNode(Node * n)
90 {
91 assert(n, "Node n pointer must not be null");
92 auto ahead = n._next;
93 while (ahead)
94 {
95 n = ahead;
96 ahead = n._next;
97 }
98 return n;
99 }
100
101 private static Node * findLastNode(Node * n, size_t limit)
102 {
103 assert(n, "Node n pointer must not be null");
104 assert(limit, "limit must be greater than 0");
105 auto ahead = n._next;
106 while (ahead)
107 {
108 if (!--limit) break;
109 n = ahead;
110 ahead = n._next;
111 }
112 return n;
113 }
114
115 private static Node * findNode(Node * n, Node * findMe)
116 {
117 assert(n, "Node n pointer must not be null");
118 auto ahead = n._next;
119 while (ahead != findMe)
120 {
121 n = ahead;
122 enforce(n);
123 ahead = n._next;
124 }
125 return n;
126 }
127
128 private static Node* findNodeByValue(Node* n, T value)
129 {
130 if (!n) return null;
131 auto ahead = n._next;
132 while (ahead && ahead._payload != value)
133 {
134 n = ahead;
135 ahead = n._next;
136 }
137 return n;
138 }
139
140 private static auto createNodeChain(Stuff)(Stuff stuff)
141 if (isImplicitlyConvertible!(Stuff, T))
142 {
143 import std.range : only;
144 return createNodeChain(only(stuff));
145 }
146
147 private static auto createNodeChain(Stuff)(Stuff stuff)
148 if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
149 {
150 static struct Chain
151 {
152 Node* first;
153 Node* last;
154 size_t length;
155 }
156
157 Chain ch;
158
159 foreach (item; stuff)
160 {
161 auto newNode = new Node(null, item);
162 (ch.first ? ch.last._next : ch.first) = newNode;
163 ch.last = newNode;
164 ++ch.length;
165 }
166
167 return ch;
168 }
169
170 private static size_t insertAfterNode(Stuff)(Node* n, Stuff stuff)
171 {
172 auto ch = createNodeChain(stuff);
173
174 if (!ch.length) return 0;
175
176 ch.last._next = n._next;
177 n._next = ch.first;
178
179 return ch.length;
180 }
181
182 /**
183 Constructor taking a number of nodes
184 */
185 this(U)(U[] values...)
186 if (isImplicitlyConvertible!(U, T))
187 {
188 insertFront(values);
189 }
190
191 /**
192 Constructor taking an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
193 */
194 this(Stuff)(Stuff stuff)
195 if (isInputRange!Stuff
196 && isImplicitlyConvertible!(ElementType!Stuff, T)
197 && !is(Stuff == T[]))
198 {
199 insertFront(stuff);
200 }
201
202 /**
203 Comparison for equality.
204
205 Complexity: $(BIGOH min(n, n1)) where `n1` is the number of
206 elements in `rhs`.
207 */
208 bool opEquals(const SList rhs) const
209 {
210 return opEquals(rhs);
211 }
212
213 /// ditto
214 bool opEquals(ref const SList rhs) const
215 {
216 if (_root is rhs._root) return true;
217 if (_root is null) return rhs._root is null || rhs._first is null;
218 if (rhs._root is null) return _root is null || _first is null;
219
220 const(Node) * n1 = _first, n2 = rhs._first;
221
222 for (;; n1 = n1._next, n2 = n2._next)
223 {
224 if (!n1) return !n2;
225 if (!n2 || n1._payload != n2._payload) return false;
226 }
227 }
228
229 /**
230 Defines the container's primary range, which embodies a forward range.
231 */
232 struct Range
233 {
234 private Node * _head;
235 private this(Node * p) { _head = p; }
236
237 /// Input range primitives.
238 @property bool empty() const { return !_head; }
239
240 /// ditto
241 @property ref T front()
242 {
243 assert(!empty, "SList.Range.front: Range is empty");
244 return _head._payload;
245 }
246
247 /// ditto
248 void popFront()
249 {
250 assert(!empty, "SList.Range.popFront: Range is empty");
251 _head = _head._next;
252 }
253
254 /// Forward range primitive.
255 @property Range save() { return this; }
256
257 T moveFront()
258 {
259 import std.algorithm.mutation : move;
260
261 assert(!empty, "SList.Range.moveFront: Range is empty");
262 return move(_head._payload);
263 }
264
265 bool sameHead(Range rhs)
266 {
267 return _head && _head == rhs._head;
268 }
269 }
270
271 @safe unittest
272 {
273 static assert(isForwardRange!Range);
274 }
275
276 /**
277 Property returning `true` if and only if the container has no
278 elements.
279
280 Complexity: $(BIGOH 1)
281 */
282 @property bool empty() const
283 {
284 return _root is null || _first is null;
285 }
286
287 /**
288 Duplicates the container. The elements themselves are not transitively
289 duplicated.
290
291 Complexity: $(BIGOH n).
292 */
293 @property SList dup()
294 {
295 return SList(this[]);
296 }
297
298 /**
299 Returns a range that iterates over all elements of the container, in
300 forward order.
301
302 Complexity: $(BIGOH 1)
303 */
304 Range opSlice()
305 {
306 if (empty)
307 return Range(null);
308 else
309 return Range(_first);
310 }
311
312 /**
313 Forward to `opSlice().front`.
314
315 Complexity: $(BIGOH 1)
316 */
317 @property ref T front()
318 {
319 assert(!empty, "SList.front: List is empty");
320 return _first._payload;
321 }
322
323 @safe unittest
324 {
325 auto s = SList!int(1, 2, 3);
326 s.front = 42;
327 assert(s == SList!int(42, 2, 3));
328 }
329
330 /**
331 Returns a new `SList` that's the concatenation of `this` and its
332 argument. `opBinaryRight` is only defined if `Stuff` does not
333 define `opBinary`.
334 */
335 SList opBinary(string op, Stuff)(Stuff rhs)
336 if (op == "~" && is(typeof(SList(rhs))))
337 {
338 import std.range : chain, only;
339
340 static if (isInputRange!Stuff)
341 alias r = rhs;
342 else
343 auto r = only(rhs);
344
345 return SList(this[].chain(r));
346 }
347
348 /// ditto
349 SList opBinaryRight(string op, Stuff)(Stuff lhs)
350 if (op == "~" && !is(typeof(lhs.opBinary!"~"(this))) && is(typeof(SList(lhs))))
351 {
352 import std.range : chain, only;
353
354 static if (isInputRange!Stuff)
355 alias r = lhs;
356 else
357 auto r = only(lhs);
358
359 return SList(r.chain(this[]));
360 }
361
362 /**
363 Removes all contents from the `SList`.
364
365 Postcondition: `empty`
366
367 Complexity: $(BIGOH 1)
368 */
369 void clear()
370 {
371 if (_root)
372 _first = null;
373 }
374
375 /**
376 Reverses SList in-place. Performs no memory allocation.
377
378 Complexity: $(BIGOH n)
379 */
380 void reverse()
381 {
382 if (!empty)
383 {
384 Node* prev;
385 while (_first)
386 {
387 auto next = _first._next;
388 _first._next = prev;
389 prev = _first;
390 _first = next;
391 }
392 _first = prev;
393 }
394 }
395
396 /**
397 Inserts `stuff` to the front of the container. `stuff` can be a
398 value convertible to `T` or a range of objects convertible to $(D
399 T). The stable version behaves the same, but guarantees that ranges
400 iterating over the container are never invalidated.
401
402 Returns: The number of elements inserted
403
404 Complexity: $(BIGOH m), where `m` is the length of `stuff`
405 */
406 size_t insertFront(Stuff)(Stuff stuff)
407 if (isInputRange!Stuff || isImplicitlyConvertible!(Stuff, T))
408 {
409 initialize();
410 return insertAfterNode(_root, stuff);
411 }
412
413 /// ditto
414 alias insert = insertFront;
415
416 /// ditto
417 alias stableInsert = insert;
418
419 /// ditto
420 alias stableInsertFront = insertFront;
421
422 /**
423 Picks one value in an unspecified position in the container, removes
424 it from the container, and returns it. The stable version behaves the same,
425 but guarantees that ranges iterating over the container are never invalidated.
426
427 Precondition: `!empty`
428
429 Returns: The element removed.
430
431 Complexity: $(BIGOH 1).
432 */
433 T removeAny()
434 {
435 import std.algorithm.mutation : move;
436
437 assert(!empty, "SList.removeAny: List is empty");
438 auto result = move(_first._payload);
439 _first = _first._next;
440 return result;
441 }
442 /// ditto
443 alias stableRemoveAny = removeAny;
444
445 /**
446 Removes the value at the front of the container. The stable version
447 behaves the same, but guarantees that ranges iterating over the
448 container are never invalidated.
449
450 Precondition: `!empty`
451
452 Complexity: $(BIGOH 1).
453 */
454 void removeFront()
455 {
456 assert(!empty, "SList.removeFront: List is empty");
457 _first = _first._next;
458 }
459
460 /// ditto
461 alias stableRemoveFront = removeFront;
462
463 /**
464 Removes `howMany` values at the front or back of the
465 container. Unlike the unparameterized versions above, these functions
466 do not throw if they could not remove `howMany` elements. Instead,
467 if $(D howMany > n), all elements are removed. The returned value is
468 the effective number of elements removed. The stable version behaves
469 the same, but guarantees that ranges iterating over the container are
470 never invalidated.
471
472 Returns: The number of elements removed
473
474 Complexity: $(BIGOH howMany * log(n)).
475 */
476 size_t removeFront(size_t howMany)
477 {
478 size_t result;
479 while (_first && result < howMany)
480 {
481 _first = _first._next;
482 ++result;
483 }
484 return result;
485 }
486
487 /// ditto
488 alias stableRemoveFront = removeFront;
489
490 /**
491 Inserts `stuff` after range `r`, which must be a range
492 previously extracted from this container. Given that all ranges for a
493 list end at the end of the list, this function essentially appends to
494 the list and uses `r` as a potentially fast way to reach the last
495 node in the list. Ideally `r` is positioned near or at the last
496 element of the list.
497
498 `stuff` can be a value convertible to `T` or a range of objects
499 convertible to `T`. The stable version behaves the same, but
500 guarantees that ranges iterating over the container are never
501 invalidated.
502
503 Returns: The number of values inserted.
504
505 Complexity: $(BIGOH k + m), where `k` is the number of elements in
506 `r` and `m` is the length of `stuff`.
507
508 Example:
509 --------------------
510 auto sl = SList!string(["a", "b", "d"]);
511 sl.insertAfter(sl[], "e"); // insert at the end (slowest)
512 assert(std.algorithm.equal(sl[], ["a", "b", "d", "e"]));
513 sl.insertAfter(std.range.take(sl[], 2), "c"); // insert after "b"
514 assert(std.algorithm.equal(sl[], ["a", "b", "c", "d", "e"]));
515 --------------------
516 */
517
518 size_t insertAfter(Stuff)(Range r, Stuff stuff)
519 if (isInputRange!Stuff || isImplicitlyConvertible!(Stuff, T))
520 {
521 initialize();
522 if (!_first)
523 {
524 enforce(!r._head);
525 return insertFront(stuff);
526 }
527 enforce(r._head);
528 auto n = findLastNode(r._head);
529 return insertAfterNode(n, stuff);
530 }
531
532 /**
533 Similar to `insertAfter` above, but accepts a range bounded in
534 count. This is important for ensuring fast insertions in the middle of
535 the list. For fast insertions after a specified position `r`, use
536 $(D insertAfter(take(r, 1), stuff)). The complexity of that operation
537 only depends on the number of elements in `stuff`.
538
539 Precondition: $(D r.original.empty || r.maxLength > 0)
540
541 Returns: The number of values inserted.
542
543 Complexity: $(BIGOH k + m), where `k` is the number of elements in
544 `r` and `m` is the length of `stuff`.
545 */
546 size_t insertAfter(Stuff)(Take!Range r, Stuff stuff)
547 if (isInputRange!Stuff || isImplicitlyConvertible!(Stuff, T))
548 {
549 auto orig = r.source;
550 if (!orig._head)
551 {
552 // Inserting after a null range counts as insertion to the
553 // front
554 return insertFront(stuff);
555 }
556 enforce(!r.empty);
557 // Find the last valid element in the range
558 foreach (i; 1 .. r.maxLength)
559 {
560 if (!orig._head._next) break;
561 orig.popFront();
562 }
563 // insert here
564 return insertAfterNode(orig._head, stuff);
565 }
566
567 /// ditto
568 alias stableInsertAfter = insertAfter;
569
570 /**
571 Removes a range from the list in linear time.
572
573 Returns: An empty range.
574
575 Complexity: $(BIGOH n)
576 */
577 Range linearRemove(Range r)
578 {
579 if (!_first)
580 {
581 enforce(!r._head);
582 return this[];
583 }
584 auto n = findNode(_root, r._head);
585 n._next = null;
586 return Range(null);
587 }
588
589 /**
590 Removes a `Take!Range` from the list in linear time.
591
592 Returns: A range comprehending the elements after the removed range.
593
594 Complexity: $(BIGOH n)
595 */
596 Range linearRemove(Take!Range r)
597 {
598 auto orig = r.source;
599 // We have something to remove here
600 if (orig._head == _first)
601 {
602 // remove straight from the head of the list
603 for (; !r.empty; r.popFront())
604 {
605 removeFront();
606 }
607 return this[];
608 }
609 if (!r.maxLength)
610 {
611 // Nothing to remove, return the range itself
612 return orig;
613 }
614 // Remove from somewhere in the middle of the list
615 enforce(_first);
616 auto n1 = findNode(_root, orig._head);
617 auto n2 = findLastNode(orig._head, r.maxLength);
618 n1._next = n2._next;
619 return Range(n1._next);
620 }
621
622 /// ditto
623 alias stableLinearRemove = linearRemove;
624
625 /**
626 Removes the first occurence of an element from the list in linear time.
627
628 Returns: True if the element existed and was successfully removed, false otherwise.
629
630 Params:
631 value = value of the node to be removed
632
633 Complexity: $(BIGOH n)
634 */
635 bool linearRemoveElement(T value)
636 {
637 auto n1 = findNodeByValue(_root, value);
638
639 if (n1 && n1._next)
640 {
641 n1._next = n1._next._next;
642 return true;
643 }
644
645 return false;
646 }
647 }
648
649 @safe unittest
650 {
651 import std.algorithm.comparison : equal;
652
653 auto e = SList!int();
654 auto b = e.linearRemoveElement(2);
655 assert(b == false);
656 assert(e.empty());
657 auto a = SList!int(-1, 1, 2, 1, 3, 4);
658 b = a.linearRemoveElement(1);
659 assert(equal(a[], [-1, 2, 1, 3, 4]));
660 assert(b == true);
661 b = a.linearRemoveElement(-1);
662 assert(b == true);
663 assert(equal(a[], [2, 1, 3, 4]));
664 b = a.linearRemoveElement(1);
665 assert(b == true);
666 assert(equal(a[], [2, 3, 4]));
667 b = a.linearRemoveElement(2);
668 assert(b == true);
669 b = a.linearRemoveElement(20);
670 assert(b == false);
671 assert(equal(a[], [3, 4]));
672 b = a.linearRemoveElement(4);
673 assert(b == true);
674 assert(equal(a[], [3]));
675 b = a.linearRemoveElement(3);
676 assert(b == true);
677 assert(a.empty());
678 a.linearRemoveElement(3);
679 }
680
681 @safe unittest
682 {
683 import std.algorithm.comparison : equal;
684
685 auto a = SList!int(5);
686 auto b = a;
687 auto r = a[];
688 a.insertFront(1);
689 b.insertFront(2);
690 assert(equal(a[], [2, 1, 5]));
691 assert(equal(b[], [2, 1, 5]));
692 r.front = 9;
693 assert(equal(a[], [2, 1, 9]));
694 assert(equal(b[], [2, 1, 9]));
695 }
696
697 @safe unittest
698 {
699 auto s = SList!int(1, 2, 3);
700 auto n = s.findLastNode(s._root);
701 assert(n && n._payload == 3);
702 }
703
704 @safe unittest
705 {
706 import std.range.primitives;
707 auto s = SList!int(1, 2, 5, 10);
708 assert(walkLength(s[]) == 4);
709 }
710
711 @safe unittest
712 {
713 import std.range : take;
714 auto src = take([0, 1, 2, 3], 3);
715 auto s = SList!int(src);
716 assert(s == SList!int(0, 1, 2));
717 }
718
719 @safe unittest
720 {
721 auto a = SList!int();
722 auto b = SList!int();
723 auto c = a ~ b[];
724 assert(c.empty);
725 }
726
727 @safe unittest
728 {
729 auto a = SList!int(1, 2, 3);
730 auto b = SList!int(4, 5, 6);
731 auto c = a ~ b[];
732 assert(c == SList!int(1, 2, 3, 4, 5, 6));
733 }
734
735 @safe unittest
736 {
737 auto a = SList!int(1, 2, 3);
738 auto b = [4, 5, 6];
739 auto c = a ~ b;
740 assert(c == SList!int(1, 2, 3, 4, 5, 6));
741 }
742
743 @safe unittest
744 {
745 auto a = SList!int(1, 2, 3);
746 auto c = a ~ 4;
747 assert(c == SList!int(1, 2, 3, 4));
748 }
749
750 @safe unittest
751 {
752 auto a = SList!int(2, 3, 4);
753 auto b = 1 ~ a;
754 assert(b == SList!int(1, 2, 3, 4));
755 }
756
757 @safe unittest
758 {
759 auto a = [1, 2, 3];
760 auto b = SList!int(4, 5, 6);
761 auto c = a ~ b;
762 assert(c == SList!int(1, 2, 3, 4, 5, 6));
763 }
764
765 @safe unittest
766 {
767 auto s = SList!int(1, 2, 3, 4);
768 s.insertFront([ 42, 43 ]);
769 assert(s == SList!int(42, 43, 1, 2, 3, 4));
770 }
771
772 @safe unittest
773 {
774 auto s = SList!int(1, 2, 3);
775 assert(s.removeAny() == 1);
776 assert(s == SList!int(2, 3));
777 assert(s.stableRemoveAny() == 2);
778 assert(s == SList!int(3));
779 }
780
781 @safe unittest
782 {
783 import std.algorithm.comparison : equal;
784
785 auto s = SList!int(1, 2, 3);
786 s.removeFront();
787 assert(equal(s[], [2, 3]));
788 s.stableRemoveFront();
789 assert(equal(s[], [3]));
790 }
791
792 @safe unittest
793 {
794 auto s = SList!int(1, 2, 3, 4, 5, 6, 7);
795 assert(s.removeFront(3) == 3);
796 assert(s == SList!int(4, 5, 6, 7));
797 }
798
799 @safe unittest
800 {
801 auto a = SList!int(1, 2, 3);
802 auto b = SList!int(1, 2, 3);
803 assert(a.insertAfter(a[], b[]) == 3);
804 }
805
806 @safe unittest
807 {
808 import std.range : take;
809 auto s = SList!int(1, 2, 3, 4);
810 auto r = take(s[], 2);
811 assert(s.insertAfter(r, 5) == 1);
812 assert(s == SList!int(1, 2, 5, 3, 4));
813 }
814
815 @safe unittest
816 {
817 import std.algorithm.comparison : equal;
818 import std.range : take;
819
820 // insertAfter documentation example
821 auto sl = SList!string(["a", "b", "d"]);
822 sl.insertAfter(sl[], "e"); // insert at the end (slowest)
823 assert(equal(sl[], ["a", "b", "d", "e"]));
824 sl.insertAfter(take(sl[], 2), "c"); // insert after "b"
825 assert(equal(sl[], ["a", "b", "c", "d", "e"]));
826 }
827
828 @safe unittest
829 {
830 import std.range.primitives;
831 auto s = SList!int(1, 2, 3, 4, 5);
832 auto r = s[];
833 popFrontN(r, 3);
834 auto r1 = s.linearRemove(r);
835 assert(s == SList!int(1, 2, 3));
836 assert(r1.empty);
837 }
838
839 @safe unittest
840 {
841 auto s = SList!int(1, 2, 3, 4, 5);
842 auto r = s[];
843 auto r1 = s.linearRemove(r);
844 assert(s == SList!int());
845 assert(r1.empty);
846 }
847
848 @safe unittest
849 {
850 import std.algorithm.comparison : equal;
851 import std.range;
852
853 auto s = SList!int(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
854 auto r = s[];
855 popFrontN(r, 3);
856 auto r1 = take(r, 4);
857 assert(equal(r1, [4, 5, 6, 7]));
858 auto r2 = s.linearRemove(r1);
859 assert(s == SList!int(1, 2, 3, 8, 9, 10));
860 assert(equal(r2, [8, 9, 10]));
861 }
862
863 @safe unittest
864 {
865 import std.range.primitives;
866 auto lst = SList!int(1, 5, 42, 9);
867 assert(!lst.empty);
868 assert(lst.front == 1);
869 assert(walkLength(lst[]) == 4);
870
871 auto lst2 = lst ~ [ 1, 2, 3 ];
872 assert(walkLength(lst2[]) == 7);
873
874 auto lst3 = lst ~ [ 7 ];
875 assert(walkLength(lst3[]) == 5);
876 }
877
878 @safe unittest
879 {
880 auto s = make!(SList!int)(1, 2, 3);
881 }
882
883 // https://issues.dlang.org/show_bug.cgi?id=5193
884 @safe unittest
885 {
886 static struct Data
887 {
888 const int val;
889 }
890 SList!Data list;
891 }
892
893 @safe unittest
894 {
895 auto s = SList!int([1, 2, 3]);
896 s.front = 5; //test frontAssign
897 assert(s.front == 5);
898 auto r = s[];
899 r.front = 1; //test frontAssign
900 assert(r.front == 1);
901 }
902
903 // https://issues.dlang.org/show_bug.cgi?id=14920
904 @safe unittest
905 {
906 SList!int s;
907 s.insertAfter(s[], 1);
908 assert(s.front == 1);
909 }
910
911 // https://issues.dlang.org/show_bug.cgi?id=15659
912 @safe unittest
913 {
914 SList!int s;
915 s.clear();
916 }
917
918 @safe unittest
919 {
920 SList!int s;
921 s.reverse();
922 }
923
924 @safe unittest
925 {
926 import std.algorithm.comparison : equal;
927
928 auto s = SList!int([1, 2, 3]);
929 assert(s[].equal([1, 2, 3]));
930
931 s.reverse();
932 assert(s[].equal([3, 2, 1]));
933 }
934
935 @safe unittest
936 {
937 auto s = SList!int([4, 6, 8, 12, 16]);
938 auto d = s.dup;
939 assert(d !is s);
940 assert(d == s);
941 }