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 }