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