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