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 }