1 /** Sorted array-like.
2  *
3  * See_Also: https://en.wikipedia.org/wiki/Sorted_array
4  *
5  * Test: dmd -version=show -preview=dip1000 -preview=in -vcolumns -preview=in -debug -g -unittest -checkaction=context -allinst -main -unittest -I../.. -i -run sorted.d
6  */
7 module nxt.container.sorted;
8 
9 import std.algorithm.mutation : SwapStrategy;
10 import nxt.container.common;
11 
12 /** Wrapper container around array-like type `A`.
13  *
14  * See_Also: https://en.wikipedia.org/wiki/Sorted_array
15  * See_Also: `nxt.container.cyclic.Cyclic`.
16  *
17  * TODO: Add flag bool deferred which when true defers sorting to when it's needed via an index that keeps track
18          of the split between sorted and non-sorted elements.
19  * TODO: Make use of `GrowthStrategy` and `reserveWithGrowth`
20  *
21  * TODO: Implement support for storing duplicate elements by setting uniqueElements to false
22  *
23  * TODO: Parameterize sorting algorithm sort and add test and benchmark for hybridSort in test/containers/source/app.d
24  *
25  * TODO: Add template parameter GrowthStrategy (and reference the term growth
26  * factor) and reuse in other dynamic containers, such as `growScaleP` and
27  * `growScaleQ` in hybrid_hashmap.d and SoA._growthP and SoA._growthQ in soa.d.
28  */
29 struct Sorted(A, bool uniqueElements = true, alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable)
30 if (is(typeof(A.init[]))) /* hasSlicing */ {
31 	import std.range : SortedRange, assumeSorted;
32 	import std.traits : isAssignable;
33 
34 	private enum isDynamic = !__traits(isStaticArray, A); /* TODO: generalize to is(typeof(A.init.reserve(0)) : size_t) */
35 	private alias E = typeof(A.init[0]); ///< Element type.
36 
37 	this(A source) @trusted {
38 		import std.algorithm.sorting : sort;
39 		static if (isDynamic) {
40 			static if (is(A == U[], U)) // isArray
41 				_source = sort!(less, ss)(source[]);
42 			else {
43 				static if (__traits(isPOD, A))
44 					_raw = source;
45 				else {
46 					import core.lifetime : move;
47 					_raw = move(source);
48 				}
49 				sort!(less, ss)(_raw[]);
50 			}
51 		} else
52 			_source = sort!(less, ss)(source[]).release();
53 	}
54 
55 	auto opSlice() @trusted {
56 		pragma(inline, true);
57 		static if (isDynamic) {
58 			static if (is(A == U[], U)) // isArray
59 				return _source[0 .. _source.length];
60 			else
61 				return _raw[].assumeSorted!(less);
62 		} else
63 			return _source[].assumeSorted!(less);
64 	}
65 
66 	static if (isDynamic) {
67 		pragma(inline, true) {
68 			auto capacity() const @property @trusted => _raw.capacity;
69 			auto reserve(in size_t capacity) @trusted => _raw.reserve(capacity);
70 		}
71 
72 		/** Insert `value` into `this`.
73 		 *
74 		 * Returns: `false` if `this` already contained `value`, `true` otherwise.
75 		 */
76 		bool insert(U)(in U value) scope @trusted
77 		if (isAssignable!(E, U)) {
78 			auto ub = _source.upperBound(value);
79 			const off = ub.release.ptr - _raw.ptr; // offset to all elements > `value`
80 
81 			if (off > 0 &&		// there are values before `upperBound`
82 				_raw[off-1] == value)
83 				return false;
84 
85 			const n = _source.length;
86 
87 			/+ TODO: needed?: _raw.reserve(length + values.length); +/
88 			_raw.length += 1;
89 
90 			// import std.algorithm.mutation : moveAll;
91 			/+ TODO: why does this fail: +/
92 			// moveAll(_raw[off .. $-1], _raw[off + 1 .. $]);
93 			foreach_reverse (const i; 0 .. ub.length) /+ TODO: replace with std.algorithm.mutation.move() +/
94 				_raw[off + i + 1] = _raw[off + i];	  /+ TODO: or use `emplace` here instead +/
95 
96 			_raw[off] = value;
97 
98 			return true;
99 		}
100 
101 		static if (uniqueElements) {
102 			/* TODO: Add alternative implementation.
103 			 * See_Also: https://discord.com/channels/242094594181955585/625407836473524246/1044707571606495252
104 			 */
105 		} else {
106 			import std.range.primitives : isInputRange, front;
107 
108 			/** Insert `values` into `this`.
109 			 *
110 			 * Returns: number of elements inserted.
111 			 */
112 			size_t insert(R)(R values) scope @trusted
113 			if (isInputRange!R &&
114 				isAssignable!(E, typeof(R.init.front))) {
115 				import std.algorithm.sorting : completeSort;
116 				const n = _source.length;
117 
118 				/+ TODO: needed?: _raw.reserve(length + values.length); +/
119 				_raw.length += values.length;
120 
121 				size_t i = 0;
122 				foreach (ref value; values) {
123 					_raw[n + i] = value;
124 					i += 1;
125 				}
126 				/* Require explicit template arguments because IFTI fails here: */
127 				completeSort!(less, ss, SortedRange!(A, less), A)(_source[0 .. n], _raw[n .. $]);
128 				return i;
129 			}
130 		}
131 
132 		static if (is(A == U[], U)) /* isArray */ {
133 			pragma(inline, true)
134 			auto source() @trusted => _source;
135 			union {
136 				private SortedRange!(A, less) _source;
137 				private A _raw;
138 			}
139 		} else {
140 			pragma(inline, true)
141 			auto source() => _raw[].assumeSorted;
142 			private A _raw;
143 		}
144 
145 		size_t length() @trusted scope pure nothrow @nogc {
146 			return source.length;
147 		}
148 
149 		alias source this;
150 	} else {
151 		private A _source;
152 	}
153 
154 }
155 
156 /// construct from dynamic array
157 pure nothrow @safe unittest {
158 	alias A = int[];
159 	scope A x = [3,2,1];
160 
161 	auto sx = Sorted!(A, false)(x);
162 	assert(sx[].isSorted);
163 
164 	assert(sx.reserve(3));
165 	assert(!sx.insert(1));
166 	assert(!sx.insert(2));
167 	assert(!sx.insert(3));
168 	assert(sx.length == 3);
169 
170 	assert(sx.insert(0));
171 	assert(sx.length == 4);
172 	assert(isIota(sx));
173 
174 	assert(sx.insert([4,5]));
175 	assert(sx.length == 6);
176 	assert(isIota(sx));
177 
178 	assert(sx.insert([4,5]));
179 	assert(sx.length == 8);
180 
181 	assert(sx.release == [0, 1, 2, 3, 4, 4, 5, 5]);
182 
183 }
184 
185 /// construct from static array
186 pure nothrow @safe @nogc unittest {
187 	alias A = int[3];
188 	A x = [3,2,1];
189 
190 	auto sx = Sorted!(A, true)(x);
191 	assert(sx[].isSorted);
192 
193 	static assert(!is(typeof(x.reserve(0))));
194 	static assert(!is(typeof(sx.insert(0))));
195 }
196 
197 /// construct from dynamic container
198 pure nothrow @safe @nogc unittest {
199 	import nxt.container.dynamic_array : DynamicArray;
200 	alias A = DynamicArray!(int, Mallocator);
201 
202 	auto sx = Sorted!(A, true)(A([3,2,1]));
203 	assert(sx.capacity == 3);
204 	assert(sx[].release == [1,2,3]);
205 	assert(sx[].isSorted);
206 
207 	sx.reserve(4);
208 	assert(sx.capacity >= 4);
209 }
210 
211 version (unittest) {
212 	import std.experimental.allocator.mallocator : Mallocator;
213 	static private bool isIota(T)(scope T x) pure nothrow @safe @nogc {
214 		size_t i = 0;
215 		foreach (const ref e; x)
216 			if (e != i++)
217 				return false;
218 		return true;
219 	}
220 	import std.algorithm.sorting : isSorted;
221 }