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 }