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