1 /** Extensions to std.algorithm.sort. 2 * 3 * License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 */ 5 6 module nxt.sort_ex; 7 8 import std.traits : isAggregateType; 9 import std.range.primitives : ElementType, isRandomAccessRange; 10 11 /** Sort random access range $(D R) of aggregates on value of calls to $(D xtor). 12 See_Also: http://forum.dlang.org/thread/nqwzojnlidlsmpunpqqy@forum.dlang.org#post-dmfvkbfhzigecnwglrur:40forum.dlang.org 13 */ 14 auto sortBy(alias xtor, R)(R r) 15 if (isRandomAccessRange!R && 16 isAggregateType!(ElementType!R)) 17 { 18 import std.algorithm : sort; 19 import std.functional : unaryFun; 20 return r.sort!((a, b) => (xtorFun!xtor(a) < 21 xtorFun!xtor(b))); 22 } 23 24 /** Reverse sort random access range $(D R) of aggregates on value of calls to $(D xtor). 25 See_Also: http://forum.dlang.org/thread/nqwzojnlidlsmpunpqqy@forum.dlang.org#post-dmfvkbfhzigecnwglrur:40forum.dlang.org 26 */ 27 auto rsortBy(alias xtor, R)(R r) 28 if (isRandomAccessRange!R && 29 isAggregateType!(ElementType!R)) 30 { 31 import std.algorithm : sort; 32 import std.functional : unaryFun; 33 return r.sort!((a, b) => (xtorFun!xtor(a) > 34 xtorFun!xtor(b))); 35 } 36 37 /* private alias makePredicate(alias xtor) = (a, b) => (xtorFun!xtor(a) < xtorFun!xtor(b)); */ 38 39 /// Extractor function used by `sortBy` and `rsortBy`. 40 private static template xtorFun(alias xtor) 41 { 42 import std.traits: isIntegral; 43 static if (is(typeof(xtor) : string)) 44 { 45 auto ref xtorFun(T)(auto return ref T a) 46 { 47 version (LDC) pragma(inline, true); 48 mixin("with (a) { return " ~ xtor ~ "; }"); 49 } 50 } 51 else static if (isIntegral!(typeof(xtor))) 52 { 53 pragma(inline, true) // must be inlined 54 auto ref xtorFun(T)(auto ref T a) 55 { 56 import std.conv: to; 57 mixin("return a.tupleof[" ~ xtor.to!string ~ "];"); 58 } 59 } 60 else 61 alias xtorFun = xtor; 62 } 63 64 /// 65 pure nothrow @safe unittest { 66 static struct X { int x, y, z; } 67 68 auto r = [ X(1, 2, 1), 69 X(0, 1, 2), 70 X(2, 0, 0) ]; 71 72 r.sortBy!(a => a.x); 73 assert(r == [ X(0, 1, 2), 74 X(1, 2, 1), 75 X(2, 0, 0) ]); 76 r.sortBy!(a => a.y); 77 assert(r == [ X(2, 0, 0), 78 X(0, 1, 2), 79 X(1, 2, 1)] ); 80 r.sortBy!(a => a.z); 81 assert(r == [ X(2, 0, 0), 82 X(1, 2, 1), 83 X(0, 1, 2) ]); 84 85 r.sortBy!"x"; 86 assert(r == [ X(0, 1, 2), 87 X(1, 2, 1), 88 X(2, 0, 0) ]); 89 r.sortBy!"y"; 90 assert(r == [ X(2, 0, 0), 91 X(0, 1, 2), 92 X(1, 2, 1)] ); 93 r.sortBy!"z"; 94 assert(r == [ X(2, 0, 0), 95 X(1, 2, 1), 96 X(0, 1, 2) ]); 97 98 r.sortBy!0; 99 assert(r == [ X(0, 1, 2), 100 X(1, 2, 1), 101 X(2, 0, 0) ]); 102 r.sortBy!1; 103 assert(r == [ X(2, 0, 0), 104 X(0, 1, 2), 105 X(1, 2, 1)] ); 106 r.sortBy!2; 107 assert(r == [ X(2, 0, 0), 108 X(1, 2, 1), 109 X(0, 1, 2) ]); 110 } 111 112 /** Returns: $(D r) sorted. 113 If needed a GC-copy of $(D r) is allocated, sorted and returned. 114 See_Also: http://forum.dlang.org/thread/tnrvudehinmkvbifovwo@forum.dlang.org#post-tnrvudehinmkvbifovwo:40forum.dlang.org 115 TODO: Move to Phobos 116 */ 117 auto sorted(R, E = ElementType!R)(R r) 118 { 119 import std.traits : isNarrowString; 120 import std.range: hasLength; 121 import nxt.range_ex : isSortedRange; 122 123 static if (isSortedRange!R) 124 return r; 125 else 126 { 127 static if (isRandomAccessRange!R) 128 auto s = r.dup; /+ TODO: remove this +/ 129 else static if (isNarrowString!R) 130 { 131 import std.conv : to; 132 auto s = r.to!(dchar[]); // need dchar for random access 133 } 134 else static if (hasLength!R) 135 { 136 import std.algorithm: copy; 137 auto s = new E[r.length]; 138 static if (is(typeof(r[]))) /+ TODO: unpretty +/ 139 r[].copy(s); 140 else 141 r.copy(s); 142 } 143 else 144 { 145 E[] s; /+ TODO: use Appender? +/ 146 foreach (const ref e; r[]) 147 s ~= e; /+ TODO: optimize? +/ 148 } 149 150 import std.algorithm.sorting : sort; 151 return sort(s); 152 } 153 } 154 155 version (unittest) import std.algorithm.comparison : equal; 156 157 /// 158 pure @safe unittest { 159 assert(equal("öaA".sorted, "Aaö")); 160 assert(equal("öaA"w.sorted, "Aaö")); 161 assert(equal("öaA"d.sorted, "Aaö")); 162 } 163 164 /// 165 pure @safe unittest { 166 import std.algorithm.sorting : sort; 167 auto x = "öaA"d; 168 auto y = sort(x.dup).sorted; // parameter to sorted is a SortedRange 169 assert(equal(y, "Aaö")); 170 } 171 172 /// 173 pure @safe unittest { 174 import std.algorithm.sorting : sort; 175 immutable x = [3, 2, 1]; 176 auto y = x.dup; 177 sort(y); 178 assert(equal(x.sorted, y)); 179 } 180 181 /// 182 unittest { 183 import std.container: Array; 184 auto x = Array!int(3, 2, 1); 185 assert(equal(x.sorted, [1, 2, 3])); 186 } 187 188 /// 189 unittest { 190 import std.container: SList; 191 auto x = SList!int(3, 2, 1); 192 assert(equal(x.sorted, [1, 2, 3])); 193 } 194 195 import std.random : Random; 196 197 /** Functional version of `std.random.randomShuffle`. 198 * 199 * Returns: $(D r) randomly shuffled. 200 * 201 * If needed a GC-copy of $(D r) is allocated, sorted and returned. 202 */ 203 auto randomlyShuffled(Range, RandomGen)(Range r, ref RandomGen gen) 204 { 205 import std.random : randomShuffle; 206 r.randomShuffle(gen); 207 /+ TODO: reuse copying logic in `sorted` +/ 208 return r; 209 } 210 211 /// 212 auto randomlyShuffled(Range)(Range r) 213 { 214 import std.random : randomShuffle; 215 r.randomShuffle(); 216 /+ TODO: reuse copying logic in `sorted` +/ 217 return r; 218 } 219 220 /// 221 pure @safe unittest { 222 immutable x = [3, 2, 1]; 223 auto y = x.dup; 224 Random random; 225 y.randomlyShuffled(random); 226 } 227 228 /** Sort sub-range of `subrange` defined by index range [i..j]. 229 * 230 * See_Also: https://forum.dlang.org/thread/lkggfpgvskvxvgwyjgcs@forum.dlang.org 231 */ 232 auto sortSubRange(R)(R range, size_t i, size_t j) 233 if (isRandomAccessRange!R) 234 { 235 import std.algorithm.sorting : topN, partialSort; 236 size_t start = i; 237 if (i != 0) 238 { 239 topN(range, i); /+ TODO: `assumePure` +/ 240 start++; 241 } 242 partialSort(range[start .. $], j-start); 243 return range[i .. j]; 244 } 245 246 unittest { 247 auto x = [1,2,7,4,2,6,8,3,9,3]; 248 auto y = sortSubRange(x, 3, 6); 249 assert(x == [2, 2, 1, 250 3, 3, 4, 251 6, 8, 9, 7]); 252 assert(y == [3, 3, 4]); 253 }