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 }