1 module nxt.unique_range;
2 
3 version(unittest)
4 {
5     import nxt.dbgio : dbg;
6     import std.algorithm.comparison : equal;
7 }
8 
9 import std.range.primitives : hasLength;
10 
11 /** Unique range (slice) owning its source of `Source`.
12 
13     Copy construction is disabled, explicit copying is instead done through
14     member `.dup`.
15  */
16 struct UniqueRange(Source)
17 if (hasLength!Source)       // TODO: use traits `isArrayContainer` checking fo
18 {
19 @safe pure nothrow @nogc:
20     import std.range.primitives : ElementType, isBidirectionalRange;
21     import std.traits : isArray;
22     alias SourceRange = typeof(Source.init[]);
23     alias E = ElementType!SourceRange;
24 
25     @disable this(this);        // not intended to be copied
26 
27     /// Construct from `source`.
28     this(Source source)
29     {
30         import std.algorithm.mutation : move;
31         move(source, _source); // TODO: remove `move` when compiler does it for us
32         _sourceRange = _source[];
33     }
34 
35     /// Construct from reference to `source`, used by `intoUniqueRange`.
36     private this(ref Source source)
37     {
38         import std.algorithm.mutation : move;
39         move(source, _source); // TODO: remove `move` when compiler does it for us
40         _sourceRange = _source[];
41     }
42 
43     /// Is `true` if range is empty.
44     @property bool empty() const @trusted
45     {
46         static if (!__traits(hasMember, SourceRange, "empty"))
47             import std.range.primitives : empty;
48         return (cast(Unqual!SourceRange)_sourceRange).empty; // TODO: remove cast and @trusted when SortedRange.empty is const
49     }
50 
51     /// Returns: length of `this`.
52     static if (hasLength!(typeof(Source.init[])))
53     {
54         @property size_t length() const @trusted
55         {
56             return (cast(Unqual!SourceRange)_sourceRange).length; // TODO: remove cast and @trusted when SortedRange.empty is const
57         }
58     }
59 
60     /// Front element.
61     @property scope auto ref inout(E) front() inout return @trusted
62     {
63         assert(!empty);
64         static if (!__traits(hasMember, SourceRange, "front"))
65             import std.range.primitives : front;
66         return cast(inout(E))(cast(SourceRange)_sourceRange).front;
67     }
68 
69     /// Pop front element.
70     void popFront()
71     {
72         static if (!__traits(hasMember, SourceRange, "popFront"))
73             import std.range.primitives : popFront;
74         _sourceRange.popFront(); // should include check for emptyness
75     }
76 
77     /// Pop front element and return it.
78     E frontPop()()
79     {
80         assert(!empty);
81 
82         import core.internal.traits : hasElaborateDestructor;
83         static if (__traits(isCopyable, E) &&
84                    !hasElaborateDestructor!E)
85         {
86             typeof(return) value = front;
87             popFront();
88             return value;
89         }
90         else
91         {
92             static assert(0, "TODO: if front is an l-value move it out and return it");
93             // import std.algorithm.mutation : move;
94             // import core.internal.traits : Unqual;
95             // TODO: reinterpret as typeof(*(cast(Unqual!E*)(&_source[_frontIx]))) iff `E` doesn't contain any immutable indirections
96             // typeof(return) value = move(_sourceRange.front);
97             // popFront();
98             // return value;
99         }
100     }
101     alias stealFront = frontPop;
102 
103     static if (isBidirectionalRange!(typeof(Source.init[])))
104     {
105         /// Back element.
106         @property scope auto ref inout(E) back() inout return @trusted
107         {
108             assert(!empty);
109             static if (!__traits(hasMember, SourceRange, "back"))
110                 import std.range.primitives : back;
111             return cast(inout(E))(cast(SourceRange)_sourceRange).back;
112         }
113 
114         /// Pop back element.
115         void popBack()
116         {
117             static if (!__traits(hasMember, SourceRange, "popBack"))
118                 import std.range.primitives : popBack;
119             _sourceRange.popBack(); // should include check for emptyness
120         }
121 
122         /// Pop back element and return it.
123         E backPop()()
124         {
125             assert(!empty);
126 
127             import core.internal.traits : hasElaborateDestructor;
128             static if (__traits(isCopyable, E) &&
129                        !hasElaborateDestructor!E)
130             {
131                 typeof(return) value = back;
132                 popBack();
133                 return value;
134             }
135             else
136             {
137                 static assert(0, "TODO: if back is an l-value move it out and return it");
138                 // import std.algorithm.mutation : move;
139                 // import core.internal.traits : Unqual;
140                 // TODO: reinterpret as typeof(*(cast(Unqual!E*)(&_source[_backIx]))) iff `E` doesn't contain any immutable indirections
141                 // typeof(return) value = move(_sourceRange.back);
142                 // popBack();
143                 // return value;
144             }
145         }
146         alias stealBack = backPop;
147     }
148 
149     /// Returns: shallow duplicate of `this`.
150     static if (__traits(hasMember, Source, "dup"))
151     {
152         @property typeof(this) dup()() const // template-lazy
153         {
154             return typeof(return)(_source.dup);
155         }
156     }
157 
158 private:
159     Source _source; // typically a non-reference counted container type with disable copy construction
160     SourceRange _sourceRange;
161 }
162 
163 /** Returns: A range of `Source` that owns its `source` (data container).
164     Similar to Rust's `into_iter`.
165  */
166 UniqueRange!Source intoUniqueRange(Source)(Source source)
167 {
168     return typeof(return)(source); // construct from reference
169 }
170 
171 /// A generator is a range which owns its state (typically a non-reference counted container).
172 alias intoGenerator = intoUniqueRange;
173 
174 /// basics
175 @safe pure nothrow @nogc unittest
176 {
177     import nxt.dynamic_array : SA = DynamicArray;
178     import std.traits : isIterable;
179     import std.range.primitives : isInputRange;
180     alias C = SA!int;
181 
182     auto cs = C([11, 13, 15, 17].s).intoUniqueRange;
183     auto cs2 = cs.dup;
184 
185     assert(cs !is cs2);
186 
187     assert(cs == cs2);
188     cs2.popFront();
189     assert(cs2.length == 3);
190     assert(cs != cs2);
191 
192     static assert(isInputRange!(typeof(cs)));
193     static assert(isIterable!(typeof(cs)));
194 
195     assert(!cs.empty);
196     assert(cs.length == 4);
197     assert(cs.front == 11);
198     assert(cs.back == 17);
199 
200     cs.popFront();
201     assert(cs.length == 3);
202     assert(cs.front == 13);
203     assert(cs.back == 17);
204 
205     cs.popBack();
206     assert(cs.length == 2);
207     assert(cs.front == 13);
208     assert(cs.back == 15);
209 
210     assert(cs.frontPop() == 13);
211     assert(cs.length == 1);
212     assert(cs.front == 15);
213     assert(cs.back == 15);
214 
215     assert(cs.backPop() == 15);
216     assert(cs.length == 0);
217     assert(cs.empty);
218 }
219 
220 /// combined with Phobos ranges
221 @safe pure nothrow unittest
222 {
223     import nxt.dynamic_array : SA = DynamicArray;
224     alias C = SA!int;
225     assert(C([11, 13, 15, 17].s)
226            .intoUniqueRange()
227            .filterUnique!(_ => _ != 11)
228            .mapUnique!(_ => 2*_)
229            .equal([2*13, 2*15, 2*17]));
230 }
231 
232 import std.functional : unaryFun;
233 
234 template mapUnique(fun...)
235 if (fun.length >= 1)
236 {
237     import std.algorithm.mutation : move;
238     import std.range.primitives : isInputRange, ElementType;
239     import core.internal.traits : Unqual;
240 
241     auto mapUnique(Range)(Range r)
242     if (isInputRange!(Unqual!Range))
243     {
244         import std.meta : AliasSeq, staticMap;
245 
246         alias RE = ElementType!(Range);
247         static if (fun.length > 1)
248         {
249             import std.functional : adjoin;
250             import std.meta : staticIndexOf;
251 
252             alias _funs = staticMap!(unaryFun, fun);
253             alias _fun = adjoin!_funs;
254 
255             // Once DMD issue #5710 is fixed, this validation loop can be moved into a template.
256             foreach (f; _funs)
257             {
258                 static assert(!is(typeof(f(RE.init)) == void),
259                     "Mapping function(s) must not return void: " ~ _funs.stringof);
260             }
261         }
262         else
263         {
264             alias _fun = unaryFun!fun;
265             alias _funs = AliasSeq!(_fun);
266 
267             // Do the validation separately for single parameters due to DMD issue #15777.
268             static assert(!is(typeof(_fun(RE.init)) == void),
269                 "Mapping function(s) must not return void: " ~ _funs.stringof);
270         }
271 
272         return MapUniqueResult!(_fun, Range)(move(r));
273     }
274 }
275 
276 private struct MapUniqueResult(alias fun, Range)
277 {
278     import core.internal.traits : Unqual;
279     import std.range.primitives : isInputRange, isForwardRange, isBidirectionalRange, isRandomAccessRange, isInfinite, hasSlicing;
280     import std.algorithm.mutation : move;
281 
282     alias R = Unqual!Range;
283     R _input;
284 
285     static if (isBidirectionalRange!R)
286     {
287         @property auto ref back()()
288         {
289             assert(!empty, "Attempting to fetch the back of an empty mapUnique.");
290             return fun(_input.back);
291         }
292 
293         void popBack()()
294         {
295             assert(!empty, "Attempting to popBack an empty mapUnique.");
296             _input.popBack();
297         }
298     }
299 
300     this(R input)
301     {
302         _input = move(input); // TODO: remove `move` when compiler does it for us
303     }
304 
305     static if (isInfinite!R)
306     {
307         // Propagate infinite-ness.
308         enum bool empty = false;
309     }
310     else
311     {
312         @property bool empty()
313         {
314             return _input.empty;
315         }
316     }
317 
318     void popFront()
319     {
320         assert(!empty, "Attempting to popFront an empty mapUnique.");
321         _input.popFront();
322     }
323 
324     @property auto ref front()
325     {
326         assert(!empty, "Attempting to fetch the front of an empty mapUnique.");
327         return fun(_input.front);
328     }
329 
330     static if (isRandomAccessRange!R)
331     {
332         static if (is(typeof(_input[ulong.max])))
333             private alias opIndex_t = ulong;
334         else
335             private alias opIndex_t = uint;
336 
337         auto ref opIndex(opIndex_t index)
338         {
339             return fun(_input[index]);
340         }
341     }
342 
343     static if (hasLength!R)
344     {
345         @property auto length()
346         {
347             return _input.length;
348         }
349 
350         alias opDollar = length;
351     }
352 
353     static if (hasSlicing!R &&
354                __traits(isCopyable, R))
355     {
356         static if (is(typeof(_input[ulong.max .. ulong.max])))
357             private alias opSlice_t = ulong;
358         else
359             private alias opSlice_t = uint;
360 
361         static if (hasLength!R)
362         {
363             auto opSlice(opSlice_t low, opSlice_t high)
364             {
365                 return typeof(this)(_input[low .. high]);
366             }
367         }
368         else static if (is(typeof(_input[opSlice_t.max .. $])))
369         {
370             struct DollarToken{}
371             enum opDollar = DollarToken.init;
372             auto opSlice(opSlice_t low, DollarToken)
373             {
374                 return typeof(this)(_input[low .. $]);
375             }
376 
377             auto opSlice(opSlice_t low, opSlice_t high)
378             {
379                 import std.range : takeExactly;
380                 return this[low .. $].takeExactly(high - low);
381             }
382         }
383     }
384 
385     static if (isForwardRange!R &&
386                __traits(isCopyable, R))    // TODO: should save be allowed for non-copyable?
387     {
388         @property auto save()
389         {
390             return typeof(this)(_input.save);
391         }
392     }
393 }
394 
395 // TODO: Add duck-typed interface that shows that result is still sorted according to `predicate`
396 template filterUnique(alias predicate)
397 if (is(typeof(unaryFun!predicate)))
398 {
399     import std.algorithm.mutation : move;
400     import std.range.primitives : isInputRange;
401     import core.internal.traits : Unqual;
402 
403     auto filterUnique(Range)(Range range)
404     if (isInputRange!(Unqual!Range))
405     {
406         return FilterUniqueResult!(unaryFun!predicate, Range)(move(range));
407     }
408 }
409 
410 // TODO: Add duck-typed interface that shows that result is still sorted according to `predicate`
411 private struct FilterUniqueResult(alias pred, Range)
412 {
413     import std.algorithm.mutation : move;
414     import std.range.primitives : isForwardRange, isInfinite;
415     import core.internal.traits : Unqual;
416     alias R = Unqual!Range;
417     R _input;
418 
419     this(R r)
420     {
421         _input = move(r);       // TODO: remove `move` when compiler does it for us
422         while (!_input.empty && !pred(_input.front))
423             _input.popFront();
424     }
425 
426     static if (__traits(isCopyable, Range))
427         auto opSlice() { return this; }
428 
429     static if (isInfinite!Range)
430         enum bool empty = false;
431     else
432         @property bool empty() { return _input.empty; }
433 
434     void popFront()
435     {
436         do
437             _input.popFront();
438         while (!_input.empty && !pred(_input.front));
439     }
440 
441     @property auto ref front()
442     {
443         assert(!empty, "Attempting to fetch the front of an empty filterUnique.");
444         return _input.front;
445     }
446 
447     static if (isForwardRange!R &&
448                __traits(isCopyable, R)) // TODO: should save be allowed for non-copyable?
449     {
450         @property auto save()
451         {
452             return typeof(this)(_input.save);
453         }
454     }
455 }
456 
457 // TODO: move these hidden behind template defs of takeUnique
458 import core.internal.traits : Unqual;
459 import std.range.primitives : isInputRange, isInfinite, hasSlicing;
460 
461 /// Unique take.
462 UniqueTake!R takeUnique(R)(R input, size_t n)
463     if (is(R T == UniqueTake!T))
464 {
465     import std.algorithm.mutation : move;
466     import std.algorithm.comparison : min;
467     return R(move(input.source), // TODO: remove `move` when compiler does it for us
468              min(n, input._maxAvailable));
469 }
470 
471 /// ditto
472 UniqueTake!(R) takeUnique(R)(R input, size_t n)
473 if (isInputRange!(Unqual!R) &&
474     (isInfinite!(Unqual!R) ||
475      !hasSlicing!(Unqual!R) &&
476      !is(R T == UniqueTake!T)))
477 {
478     import std.algorithm.mutation : move;
479     return UniqueTake!R(move(input), n); // TODO: remove `move` when compiler does it for us
480 }
481 
482 struct UniqueTake(Range)
483 if (isInputRange!(Unqual!Range) &&
484     //take _cannot_ test hasSlicing on infinite ranges, because hasSlicing uses
485     //take for slicing infinite ranges.
486     !((!isInfinite!(Unqual!Range) && hasSlicing!(Unqual!Range)) || is(Range T == UniqueTake!T)))
487 {
488     import std.range.primitives : isForwardRange, hasAssignableElements, ElementType, hasMobileElements, isRandomAccessRange, moveFront;
489 
490     private alias R = Unqual!Range;
491 
492     /// User accessible in read and write
493     public R source;
494 
495     private size_t _maxAvailable;
496 
497     alias Source = R;
498 
499     this(R source, size_t _maxAvailable)
500     {
501         import std.algorithm.mutation : move;
502         this.source = move(source);
503         this._maxAvailable = _maxAvailable;
504     }
505 
506     /// Range primitives
507     @property bool empty()
508     {
509         return _maxAvailable == 0 || source.empty;
510     }
511 
512     /// ditto
513     @property auto ref front()
514     {
515         assert(!empty,
516             "Attempting to fetch the front of an empty "
517             ~ UniqueTake.stringof);
518         return source.front;
519     }
520 
521     /// ditto
522     void popFront()
523     {
524         assert(!empty,
525             "Attempting to popFront() past the end of a "
526             ~ UniqueTake.stringof);
527         source.popFront();
528         --_maxAvailable;
529     }
530 
531     static if (isForwardRange!R)
532         /// ditto
533         @property UniqueTake save()
534         {
535             return UniqueTake(source.save, _maxAvailable);
536         }
537 
538     static if (hasAssignableElements!R)
539         /// ditto
540         @property auto front(ElementType!R v)
541         {
542             assert(!empty,
543                 "Attempting to assign to the front of an empty "
544                 ~ UniqueTake.stringof);
545             // This has to return auto instead of void because of Bug 4706.
546             source.front = v;
547         }
548 
549     // static if (hasMobileElements!R)
550     // {
551     //     /// ditto
552     //     auto moveFront()
553     //     {
554     //         assert(!empty,
555     //             "Attempting to move the front of an empty "
556     //             ~ UniqueTake.stringof);
557     //         return source.moveFront();
558     //     }
559     // }
560 
561     static if (isInfinite!R)
562     {
563         /// ditto
564         @property size_t length() const
565         {
566             return _maxAvailable;
567         }
568 
569         /// ditto
570         alias opDollar = length;
571 
572         //Note: Due to UniqueTake/hasSlicing circular dependency,
573         //This needs to be a restrained template.
574         /// ditto
575         auto opSlice()(size_t i, size_t j)
576         if (hasSlicing!R)
577         {
578             assert(i <= j, "Invalid slice bounds");
579             assert(j <= length, "Attempting to slice past the end of a "
580                 ~ UniqueTake.stringof);
581             return source[i .. j];
582         }
583     }
584     else static if (hasLength!R)
585     {
586         /// ditto
587         @property size_t length()
588         {
589             import std.algorithm.comparison : min;
590             return min(_maxAvailable, source.length);
591         }
592 
593         alias opDollar = length;
594     }
595 
596     static if (isRandomAccessRange!R)
597     {
598         /// ditto
599         void popBack()
600         {
601             assert(!empty,
602                 "Attempting to popBack() past the beginning of a "
603                 ~ UniqueTake.stringof);
604             --_maxAvailable;
605         }
606 
607         /// ditto
608         @property auto ref back()
609         {
610             assert(!empty,
611                 "Attempting to fetch the back of an empty "
612                 ~ UniqueTake.stringof);
613             return source[this.length - 1];
614         }
615 
616         /// ditto
617         auto ref opIndex(size_t index)
618         {
619             assert(index < length,
620                 "Attempting to index out of the bounds of a "
621                 ~ UniqueTake.stringof);
622             return source[index];
623         }
624 
625         static if (hasAssignableElements!R)
626         {
627             /// ditto
628             @property auto back(ElementType!R v)
629             {
630                 // This has to return auto instead of void because of Bug 4706.
631                 assert(!empty,
632                     "Attempting to assign to the back of an empty "
633                     ~ UniqueTake.stringof);
634                 source[this.length - 1] = v;
635             }
636 
637             /// ditto
638             void opIndexAssign(ElementType!R v, size_t index)
639             {
640                 assert(index < length,
641                     "Attempting to index out of the bounds of a "
642                     ~ UniqueTake.stringof);
643                 source[index] = v;
644             }
645         }
646 
647         static if (hasMobileElements!R)
648         {
649             /// ditto
650             auto moveBack()
651             {
652                 assert(!empty,
653                     "Attempting to move the back of an empty "
654                     ~ UniqueTake.stringof);
655                 return source.moveAt(this.length - 1);
656             }
657 
658             /// ditto
659             auto moveAt(size_t index)
660             {
661                 assert(index < length,
662                     "Attempting to index out of the bounds of a "
663                     ~ UniqueTake.stringof);
664                 return source.moveAt(index);
665             }
666         }
667     }
668 
669     /**
670     Access to maximal length of the range.
671     Note: the actual length of the range depends on the underlying range.
672     If it has fewer elements, it will stop before maxLength is reached.
673     */
674     @property size_t maxLength() const
675     {
676         return _maxAvailable;
677     }
678 }
679 
680 /// array range
681 @safe pure nothrow @nogc unittest
682 {
683     import nxt.dynamic_array : SA = DynamicArray;
684     import std.traits : isIterable;
685     import std.range.primitives : isInputRange;
686     alias C = SA!int;
687 
688     auto cs = C([11, 13].s).intoUniqueRange;
689 
690     alias CS = typeof(cs);
691     static assert(isInputRange!(typeof(cs)));
692     static assert(isIterable!(typeof(cs)));
693 
694     assert(cs.front == 11);
695     cs.popFront();
696 
697     assert(cs.front == 13);
698     cs.popFront();
699 
700     assert(cs.empty);
701 }
702 
703 import std.functional : binaryFun;
704 
705 InputRange findUnique(alias pred = "a == b", InputRange, Element)(InputRange haystack, scope Element needle)
706 if (isInputRange!InputRange &&
707     is (typeof(binaryFun!pred(haystack.front, needle)) : bool))
708 {
709     for (; !haystack.empty; haystack.popFront())
710         if (binaryFun!pred(haystack.front, needle))
711             break;
712     import std.algorithm.mutation : move;
713     return move(haystack);
714 }
715 
716 version(unittest)
717 {
718     import nxt.array_help : s;
719 }