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 pure nothrow @safe @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 	this(this) @disable;		// 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 pure nothrow @safe @nogc unittest {
141 	import std.experimental.allocator.mallocator : Mallocator;
142 	import nxt.container.dynamic_array : DA = DynamicArray;
143 	import std.traits : isIterable;
144 	import std.range.primitives : isInputRange;
145 	alias C = DA!(int, Mallocator);
146 
147 	auto cs = C([11, 13, 15, 17].s).intoUniqueRange;
148 	auto cs2 = C([11, 13, 15, 17].s).intoUniqueRange;
149 	// TODO: instead use auto cs2 = cs.dupShallow;
150 
151 	assert(cs !is cs2);
152 
153 	assert(cs == cs2);
154 	cs2.popFront();
155 	assert(cs2.length == 3);
156 	assert(cs != cs2);
157 
158 	static assert(isInputRange!(typeof(cs)));
159 	static assert(isIterable!(typeof(cs)));
160 
161 	assert(!cs.empty);
162 	assert(cs.length == 4);
163 	assert(cs.front == 11);
164 	assert(cs.back == 17);
165 
166 	cs.popFront();
167 	assert(cs.length == 3);
168 	assert(cs.front == 13);
169 	assert(cs.back == 17);
170 
171 	cs.popBack();
172 	assert(cs.length == 2);
173 	assert(cs.front == 13);
174 	assert(cs.back == 15);
175 
176 	assert(cs.takeFront() == 13);
177 	assert(cs.length == 1);
178 	assert(cs.front == 15);
179 	assert(cs.back == 15);
180 
181 	assert(cs.takeBack() == 15);
182 	assert(cs.length == 0);
183 	assert(cs.empty);
184 }
185 
186 /// combined with Phobos ranges
187 pure nothrow @safe unittest {
188 	import std.experimental.allocator.mallocator : Mallocator;
189 	import nxt.container.dynamic_array : DA = DynamicArray;
190 	alias C = DA!(int, Mallocator);
191 	assert(C([11, 13, 15, 17].s)
192 		   .intoUniqueRange()
193 		   .filterUnique!(_ => _ != 11)
194 		   .mapUnique!(_ => 2*_)
195 		   .equal([2*13, 2*15, 2*17]));
196 }
197 
198 import std.functional : unaryFun;
199 
200 template mapUnique(fun...)
201 if (fun.length >= 1)
202 {
203 	import std.algorithm.mutation : move;
204 	import std.range.primitives : isInputRange, ElementType;
205 	import core.internal.traits : Unqual;
206 
207 	auto mapUnique(Range)(Range r)
208 	if (isInputRange!(Unqual!Range))
209 	{
210 		import std.meta : AliasSeq, staticMap;
211 
212 		alias RE = ElementType!(Range);
213 		static if (fun.length > 1)
214 		{
215 			import std.functional : adjoin;
216 			import std.meta : staticIndexOf;
217 
218 			alias _funs = staticMap!(unaryFun, fun);
219 			alias _fun = adjoin!_funs;
220 
221 			// Once DMD issue #5710 is fixed, this validation loop can be moved into a template.
222 			foreach (f; _funs)
223 				static assert(!is(typeof(f(RE.init)) == void),
224 					"Mapping function(s) must not return void: " ~ _funs.stringof);
225 		}
226 		else
227 		{
228 			alias _fun = unaryFun!fun;
229 			alias _funs = AliasSeq!(_fun);
230 
231 			// Do the validation separately for single parameters due to DMD issue #15777.
232 			static assert(!is(typeof(_fun(RE.init)) == void),
233 				"Mapping function(s) must not return void: " ~ _funs.stringof);
234 		}
235 
236 		return MapUniqueResult!(_fun, Range)(move(r));
237 	}
238 }
239 
240 private struct MapUniqueResult(alias fun, Range)
241 {
242 	import core.internal.traits : Unqual;
243 	import std.range.primitives : isInputRange, isForwardRange, isBidirectionalRange, isRandomAccessRange, isInfinite, hasSlicing;
244 	import std.algorithm.mutation : move;
245 
246 	alias R = Unqual!Range;
247 	R _input;
248 
249 	this(R input)
250 	{
251 		_input = move(input); /+ TODO: remove `move` when compiler does it for us +/
252 	}
253 
254 	static if (isInfinite!R)
255 		enum bool empty = false; ///< Propagate infinite-ness.
256 	else
257 		@property bool empty() => _input.empty;
258 
259 	auto ref front() @property in(!empty) => fun(_input.front);
260 	void popFront() in(!empty) => _input.popFront();
261 
262 	static if (isBidirectionalRange!R)
263 	{
264 		auto ref back()() @property in(!empty) => fun(_input.back);
265 		void popBack()() in(!empty) => _input.popBack();
266 	}
267 
268 	static if (isRandomAccessRange!R)
269 	{
270 		static if (is(typeof(_input[ulong.max])))
271 			private alias opIndex_t = ulong;
272 		else
273 			private alias opIndex_t = uint;
274 		auto ref opIndex(opIndex_t index) => fun(_input[index]);
275 	}
276 
277 	static if (hasLength!R)
278 	{
279 		@property auto length() => _input.length;
280 		alias opDollar = length;
281 	}
282 
283 	static if (hasSlicing!R &&
284 			   __traits(isCopyable, R))
285 	{
286 		static if (is(typeof(_input[ulong.max .. ulong.max])))
287 			private alias opSlice_t = ulong;
288 		else
289 			private alias opSlice_t = uint;
290 		static if (hasLength!R)
291 			auto opSlice(opSlice_t low, opSlice_t high) => typeof(this)(_input[low .. high]);
292 		else static if (is(typeof(_input[opSlice_t.max .. $])))
293 		{
294 			struct DollarToken{}
295 			enum opDollar = DollarToken.init;
296 			auto opSlice(opSlice_t low, DollarToken) => typeof(this)(_input[low .. $]);
297 			import std.range : takeExactly;
298 			auto opSlice(opSlice_t low, opSlice_t high) => this[low .. $].takeExactly(high - low);
299 		}
300 	}
301 
302 	static if (isForwardRange!R &&
303 			   __traits(isCopyable, R))	/+ TODO: should save be allowed for non-copyable? +/
304 		@property auto save() => typeof(this)(_input.save);
305 }
306 
307 /+ TODO: Add duck-typed interface that shows that result is still sorted according to `predicate` +/
308 template filterUnique(alias predicate)
309 if (is(typeof(unaryFun!predicate)))
310 {
311 	import std.algorithm.mutation : move;
312 	import std.range.primitives : isInputRange;
313 	import core.internal.traits : Unqual;
314 	auto filterUnique(Range)(Range range)
315 	if (isInputRange!(Unqual!Range))
316 		=> FilterUniqueResult!(unaryFun!predicate, Range)(move(range));
317 }
318 
319 /+ TODO: Add duck-typed interface that shows that result is still sorted according to `predicate` +/
320 private struct FilterUniqueResult(alias pred, Range)
321 {
322 	import std.algorithm.mutation : move;
323 	import std.range.primitives : isForwardRange, isInfinite;
324 	import core.internal.traits : Unqual;
325 	alias R = Unqual!Range;
326 	R _input;
327 
328 	this(R r)
329 	{
330 		_input = move(r);	   /+ TODO: remove `move` when compiler does it for us +/
331 		while (!_input.empty && !pred(_input.front))
332 			_input.popFront();
333 	}
334 
335 	static if (__traits(isCopyable, Range))
336 		auto opSlice() => this;
337 
338 	static if (isInfinite!Range)
339 		enum bool empty = false;
340 	else
341 		bool empty() @property => _input.empty;
342 
343 	void popFront()
344 	{
345 		do
346 			_input.popFront();
347 		while (!_input.empty && !pred(_input.front));
348 	}
349 
350 	auto ref front() @property in(!empty) => _input.front;
351 
352 	static if (isForwardRange!R &&
353 			   __traits(isCopyable, R)) /+ TODO: should save be allowed for non-copyable? +/
354 		auto save() @property => typeof(this)(_input.save);
355 }
356 
357 /+ TODO: move these hidden behind template defs of takeUnique +/
358 import core.internal.traits : Unqual;
359 import std.range.primitives : isInputRange, isInfinite, hasSlicing;
360 
361 /// Unique take.
362 UniqueTake!R takeUnique(R)(R input, size_t n)
363 	if (is(R T == UniqueTake!T))
364 {
365 	import std.algorithm.mutation : move;
366 	import std.algorithm.comparison : min;
367 	return R(move(input.source), /+ TODO: remove `move` when compiler does it for us +/
368 			 min(n, input._maxAvailable));
369 }
370 
371 /// ditto
372 UniqueTake!(R) takeUnique(R)(R input, size_t n)
373 if (isInputRange!(Unqual!R) &&
374 	(isInfinite!(Unqual!R) ||
375 	 !hasSlicing!(Unqual!R) &&
376 	 !is(R T == UniqueTake!T)))
377 {
378 	import std.algorithm.mutation : move;
379 	return UniqueTake!R(move(input), n); /+ TODO: remove `move` when compiler does it for us +/
380 }
381 
382 struct UniqueTake(Range)
383 if (isInputRange!(Unqual!Range) &&
384 	//take _cannot_ test hasSlicing on infinite ranges, because hasSlicing uses
385 	//take for slicing infinite ranges.
386 	!((!isInfinite!(Unqual!Range) && hasSlicing!(Unqual!Range)) || is(Range T == UniqueTake!T)))
387 {
388 	import std.range.primitives : isForwardRange, hasAssignableElements, ElementType, hasMobileElements, isRandomAccessRange, moveFront;
389 
390 	private alias R = Unqual!Range;
391 
392 	/// User accessible in read and write
393 	public R source;
394 
395 	private size_t _maxAvailable;
396 
397 	alias Source = R;
398 
399 	this(R source, size_t _maxAvailable)
400 	{
401 		import std.algorithm.mutation : move;
402 		this.source = move(source);
403 		this._maxAvailable = _maxAvailable;
404 	}
405 
406 	/// Range primitives
407 	bool empty() @property => _maxAvailable == 0 || source.empty;
408 
409 	/// ditto
410 	auto ref front() @property in(!empty) => source.front;
411 
412 	/// ditto
413 	void popFront() in(!empty)
414 	{
415 		source.popFront();
416 		--_maxAvailable;
417 	}
418 
419 	static if (isForwardRange!R)
420 		/// ditto
421 		UniqueTake save() @property => UniqueTake(source.save, _maxAvailable);
422 
423 	static if (hasAssignableElements!R)
424 		/// ditto
425 		@property auto front(ElementType!R v)
426 		in(!empty, "Attempting to assign to the front of an empty " ~ UniqueTake.stringof)
427 		{
428 			// This has to return auto instead of void because of Bug 4706.
429 			source.front = v;
430 		}
431 
432 	// static if (hasMobileElements!R)
433 	// {
434 	//	 /// ditto
435 	//	 auto moveFront()
436 	//	 {
437 	//		 assert(!empty,
438 	//			 "Attempting to move the front of an empty "
439 	//			 ~ UniqueTake.stringof);
440 	//		 return source.moveFront();
441 	//	 }
442 	// }
443 
444 	static if (isInfinite!R)
445 	{
446 		/// ditto
447 		@property size_t length() const => _maxAvailable;
448 		/// ditto
449 		alias opDollar = length;
450 
451 		//Note: Due to UniqueTake/hasSlicing circular dependency,
452 		//This needs to be a restrained template.
453 		/// ditto
454 		auto opSlice()(size_t i, size_t j)
455 		if (hasSlicing!R)
456 		in(i <= j, "Invalid slice bounds")
457 		in(j <= length, "Attempting to slice past the end of a " ~ UniqueTake.stringof)
458 			=> source[i .. j];
459 	}
460 	else static if (hasLength!R)
461 	{
462 		/// ditto
463 		@property size_t length()
464 		{
465 			import std.algorithm.comparison : min;
466 			return min(_maxAvailable, source.length);
467 		}
468 		alias opDollar = length;
469 	}
470 
471 	static if (isRandomAccessRange!R)
472 	{
473 		/// ditto
474 		void popBack()
475 		in(!empty, "Attempting to popBack() past the beginning of a " ~ UniqueTake.stringof)
476 		{
477 			--_maxAvailable;
478 		}
479 
480 		/// ditto
481 		@property auto ref back()
482 		in(!empty, "Attempting to fetch the back of an empty " ~ UniqueTake.stringof)
483 			=> source[this.length - 1];
484 
485 		/// ditto
486 		auto ref opIndex(size_t index)
487 		in(index < length, "Attempting to index out of the bounds of a " ~ UniqueTake.stringof)
488 			=> source[index];
489 
490 		static if (hasAssignableElements!R)
491 		{
492 			/// ditto
493 			@property auto back(ElementType!R v)
494 			in(!empty, "Attempting to assign to the back of an empty " ~ UniqueTake.stringof)
495 			{
496 				// This has to return auto instead of void because of Bug 4706.
497 				source[this.length - 1] = v;
498 			}
499 
500 			/// ditto
501 			void opIndexAssign(ElementType!R v, size_t index)
502 			in(index < length, "Attempting to index out of the bounds of a " ~ UniqueTake.stringof)
503 			{
504 				source[index] = v;
505 			}
506 		}
507 
508 		static if (hasMobileElements!R)
509 		{
510 			/// ditto
511 			auto moveBack()
512 			in(!empty, "Attempting to move the back of an empty " ~ UniqueTake.stringof)
513 				=> source.moveAt(this.length - 1);
514 
515 			/// ditto
516 			auto moveAt(size_t index)
517 			in(index < length, "Attempting to index out of the bounds of a " ~ UniqueTake.stringof)
518 				=> source.moveAt(index);
519 		}
520 	}
521 
522 	/**
523 	Access to maximal length of the range.
524 	Note: the actual length of the range depends on the underlying range.
525 	If it has fewer elements, it will stop before lengthMax is reached.
526 	*/
527 	@property size_t lengthMax() const => _maxAvailable;
528 }
529 
530 /// array range
531 pure nothrow @safe @nogc unittest {
532 	import std.experimental.allocator.mallocator : Mallocator;
533 	import nxt.container.dynamic_array : DA = DynamicArray;
534 	import std.traits : isIterable;
535 	import std.range.primitives : isInputRange;
536 	alias C = DA!(int, Mallocator);
537 
538 	auto cs = C([11, 13].s).intoUniqueRange;
539 
540 	alias CS = typeof(cs);
541 	static assert(isInputRange!(typeof(cs)));
542 	static assert(isIterable!(typeof(cs)));
543 
544 	assert(cs.front == 11);
545 	cs.popFront();
546 
547 	assert(cs.front == 13);
548 	cs.popFront();
549 
550 	assert(cs.empty);
551 }
552 
553 import std.functional : binaryFun;
554 
555 InputRange findUnique(alias pred = "a == b", InputRange, Element)(InputRange haystack, scope Element needle)
556 if (isInputRange!InputRange &&
557 	is (typeof(binaryFun!pred(haystack.front, needle)) : bool))
558 {
559 	for (; !haystack.empty; haystack.popFront())
560 		if (binaryFun!pred(haystack.front, needle))
561 			break;
562 	import std.algorithm.mutation : move;
563 	return move(haystack);
564 }
565 
566 version (unittest)
567 {
568 	import nxt.array_help : s;
569 	import nxt.debugio : dbg;
570 	import nxt.algorithm.comparison : equal;
571 }
572 
573 version (unittest) {
574 	import nxt.construction : dupShallow;
575 }