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 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 @safe pure nothrow unittest
66 {
67     static struct X { int x, y, z; }
68 
69     auto r = [ X(1, 2, 1),
70                X(0, 1, 2),
71                X(2, 0, 0) ];
72 
73     r.sortBy!(a => a.x);
74     assert(r == [ X(0, 1, 2),
75                   X(1, 2, 1),
76                   X(2, 0, 0) ]);
77     r.sortBy!(a => a.y);
78     assert(r == [ X(2, 0, 0),
79                   X(0, 1, 2),
80                   X(1, 2, 1)] );
81     r.sortBy!(a => a.z);
82     assert(r == [ X(2, 0, 0),
83                   X(1, 2, 1),
84                   X(0, 1, 2) ]);
85 
86     r.sortBy!"x";
87     assert(r == [ X(0, 1, 2),
88                   X(1, 2, 1),
89                   X(2, 0, 0) ]);
90     r.sortBy!"y";
91     assert(r == [ X(2, 0, 0),
92                   X(0, 1, 2),
93                   X(1, 2, 1)] );
94     r.sortBy!"z";
95     assert(r == [ X(2, 0, 0),
96                   X(1, 2, 1),
97                   X(0, 1, 2) ]);
98 
99     r.sortBy!0;
100     assert(r == [ X(0, 1, 2),
101                   X(1, 2, 1),
102                   X(2, 0, 0) ]);
103     r.sortBy!1;
104     assert(r == [ X(2, 0, 0),
105                   X(0, 1, 2),
106                   X(1, 2, 1)] );
107     r.sortBy!2;
108     assert(r == [ X(2, 0, 0),
109                   X(1, 2, 1),
110                   X(0, 1, 2) ]);
111 }
112 
113 /** Returns: $(D r) sorted.
114     If needed a GC-copy of $(D r) is allocated, sorted and returned.
115     See_Also: http://forum.dlang.org/thread/tnrvudehinmkvbifovwo@forum.dlang.org#post-tnrvudehinmkvbifovwo:40forum.dlang.org
116     TODO: Move to Phobos
117 */
118 auto sorted(R, E = ElementType!R)(R r)
119 {
120     import std.traits : isNarrowString;
121     import std.range: hasLength;
122     import nxt.range_ex : isSortedRange;
123 
124     static if (isSortedRange!R)
125         return r;
126     else
127     {
128         static if (isRandomAccessRange!R)
129             auto s = r.dup;     // TODO: remove this
130         else static if (isNarrowString!R)
131         {
132             import std.conv : to;
133             auto s = r.to!(dchar[]); // need dchar for random access
134         }
135         else static if (hasLength!R)
136         {
137             import std.algorithm: copy;
138             auto s = new E[r.length];
139             static if (is(typeof(r[]))) // TODO: unpretty
140                 r[].copy(s);
141             else
142                 r.copy(s);
143         }
144         else
145         {
146             E[] s; // TODO: use Appender?
147             foreach (const ref e; r[])
148                 s ~= e;             // TODO: optimize?
149         }
150 
151         import std.algorithm.sorting : sort;
152         return sort(s);
153     }
154 }
155 
156 version(unittest) import std.algorithm.comparison : equal;
157 
158 ///
159 @safe pure unittest
160 {
161     assert(equal("öaA".sorted, "Aaö"));
162     assert(equal("öaA"w.sorted, "Aaö"));
163     assert(equal("öaA"d.sorted, "Aaö"));
164 }
165 
166 ///
167 @safe pure unittest
168 {
169     import std.algorithm.sorting : sort;
170     auto x = "öaA"d;
171     auto y = sort(x.dup).sorted; // parameter to sorted is a SortedRange
172     assert(equal(y, "Aaö"));
173 }
174 
175 ///
176 @safe pure unittest
177 {
178     import std.algorithm.sorting : sort;
179     immutable x = [3, 2, 1];
180     auto y = x.dup;
181     sort(y);
182     assert(equal(x.sorted, y));
183 }
184 
185 ///
186 unittest
187 {
188     import std.container: Array;
189     auto x = Array!int(3, 2, 1);
190     assert(equal(x.sorted, [1, 2, 3]));
191 }
192 
193 ///
194 unittest
195 {
196     import std.container: SList;
197     auto x = SList!int(3, 2, 1);
198     assert(equal(x.sorted, [1, 2, 3]));
199 }
200 
201 import std.random : Random;
202 
203 /** Functional version of `std.random.randomShuffle`.
204  *
205  * Returns: $(D r) randomly shuffled.
206  *
207  * If needed a GC-copy of $(D r) is allocated, sorted and returned.
208 */
209 auto randomlyShuffled(Range, RandomGen)(Range r, ref RandomGen gen)
210 {
211     import std.random : randomShuffle;
212     r.randomShuffle(gen);
213     // TODO: reuse copying logic in `sorted`
214     return r;
215 }
216 
217 ///
218 auto randomlyShuffled(Range)(Range r)
219 {
220     import std.random : randomShuffle;
221     r.randomShuffle();
222     // TODO: reuse copying logic in `sorted`
223     return r;
224 }
225 
226 ///
227 @safe pure unittest
228 {
229     immutable x = [3, 2, 1];
230     auto y = x.dup;
231     Random random;
232     y.randomlyShuffled(random);
233 }
234 
235 /** Sort sub-range of `subrange` defined by index range [i..j].
236  *
237  * See_Also: https://forum.dlang.org/thread/lkggfpgvskvxvgwyjgcs@forum.dlang.org
238  */
239 auto sortSubRange(R)(R range, size_t i, size_t j)
240 if (isRandomAccessRange!R)
241 {
242     import std.algorithm.sorting : topN, partialSort;
243     size_t start = i;
244     if (i != 0)
245     {
246         topN(range, i);         // TODO: `assumePure`
247         start++;
248     }
249     partialSort(range[start .. $], j-start);
250     return range[i .. j];
251 }
252 
253 unittest
254 {
255     auto x = [1,2,7,4,2,6,8,3,9,3];
256     auto y = sortSubRange(x, 3, 6);
257     assert(x == [2, 2, 1,
258                  3, 3, 4,
259                  6, 8, 9, 7]);
260     assert(y == [3, 3, 4]);
261 }