1 /**
2  * Array utility functions
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.utils.array;
15 
16 import std.algorithm.iteration;
17 import std.algorithm.mutation;
18 import std.algorithm.searching;
19 import std.algorithm.sorting;
20 import std.array;
21 import std.exception;
22 import std.format;
23 import std.traits;
24 
25 import ae.utils.meta;
26 
27 public import ae.utils.aa;
28 public import ae.utils.appender;
29 
30 /// Slice a variable.
31 T[] toArray(T)(ref T v)
32 {
33 	return (&v)[0..1];
34 }
35 
36 /// Return the value represented as an array of bytes.
37 @property inout(ubyte)[] bytes(T)(ref inout(T) value)
38 	if (!hasIndirections!T)
39 {
40 	return value.toArray().bytes;
41 }
42 
43 /// ditto
44 @property inout(ubyte)[] bytes(T)(inout(T) value)
45 	if (is(T U : U[]) && !hasIndirections!U)
46 {
47 	return cast(inout(ubyte)[])value;
48 }
49 
50 unittest
51 {
52 	ubyte b = 5;
53 	assert(b.bytes == [5]);
54 
55 	struct S { ubyte b = 5; }
56 	S s;
57 	assert(s.bytes == [5]);
58 
59 	ubyte[1] sa = [5];
60 	assert(sa.bytes == [5]);
61 }
62 
63 /// Reverse of bytes()
64 ref inout(T) fromBytes(T)(inout(ubyte)[] bytes)
65 	if (!hasIndirections!T)
66 {
67 	assert(bytes.length == T.sizeof, "Data length mismatch for %s".format(T.stringof));
68 	return *cast(inout(T)*)bytes.ptr;
69 }
70 
71 /// ditto
72 inout(T) fromBytes(T)(inout(ubyte)[] bytes)
73 	if (is(T U : U[]) && !hasIndirections!U)
74 {
75 	return cast(inout(T))bytes;
76 }
77 
78 unittest
79 {
80 	{       ubyte b = 5; assert(b.bytes.fromBytes!ubyte == 5); }
81 	{ const ubyte b = 5; assert(b.bytes.fromBytes!ubyte == 5); }
82 	struct S { ubyte b; }
83 	{       ubyte b = 5; assert(b.bytes.fromBytes!S == S(5)); }
84 }
85 
86 unittest
87 {
88 	struct S { ubyte a, b; }
89 	ubyte[] arr = [1, 2];
90 	assert(arr.fromBytes!S == S(1, 2));
91 	assert(arr.fromBytes!(S[]) == [S(1, 2)]);
92 }
93 
94 int memcmp(in ubyte[] a, in ubyte[] b)
95 {
96 	assert(a.length == b.length);
97 	import core.stdc.string : memcmp;
98 	return memcmp(a.ptr, b.ptr, a.length);
99 }
100 
101 /// Like std.algorithm.copy, but without the auto-decode bullshit.
102 /// https://issues.dlang.org/show_bug.cgi?id=13650
103 void memmove(T)(T[] dst, in T[] src)
104 {
105 	assert(src.length == dst.length);
106 	import core.stdc.string : memmove;
107 	memmove(dst.ptr, src.ptr, dst.length * T.sizeof);
108 }
109 
110 T[] vector(string op, T)(T[] a, T[] b)
111 {
112 	assert(a.length == b.length);
113 	T[] result = new T[a.length];
114 	foreach (i, ref r; result)
115 		r = mixin("a[i]" ~ op ~ "b[i]");
116 	return result;
117 }
118 
119 T[] vectorAssign(string op, T)(T[] a, T[] b)
120 {
121 	assert(a.length == b.length);
122 	foreach (i, ref r; a)
123 		mixin("r " ~ op ~ "= b[i];");
124 	return a;
125 }
126 
127 T[] padRight(T)(T[] s, size_t l, T c)
128 {
129 	auto ol = s.length;
130 	if (ol < l)
131 	{
132 		s.length = l;
133 		s[ol..$] = c;
134 	}
135 	return s;
136 }
137 
138 T[] repeatOne(T)(T c, size_t l)
139 {
140 	T[] result = new T[l];
141 	result[] = c;
142 	return result;
143 }
144 
145 /// Complement to std.string.indexOf which works with arrays
146 /// of non-character types.
147 /// Unlike std.algorithm.countUntil, it does not auto-decode,
148 /// and returns an index usable for array indexing/slicing.
149 sizediff_t indexOf(T, D)(in T[] arr, in D val)
150 //	if (!isSomeChar!T)
151 	if (!isSomeChar!T && is(typeof(arr.countUntil(val))) && is(typeof(arr[0]==val)))
152 {
153 	//assert(arr[0]==val);
154 	return arr.countUntil(val);
155 }
156 
157 sizediff_t indexOf(T)(in T[] arr, in T[] val) /// ditto
158 	if (!isSomeChar!T && is(typeof(arr.countUntil(val))))
159 {
160 	return arr.countUntil(val);
161 }
162 
163 /// Index of element, no BS.
164 sizediff_t indexOfElement(T, D)(in T[] arr, auto ref in D val)
165 	if (is(typeof(arr[0]==val)))
166 {
167 	foreach (i, ref v; arr)
168 		if (v == val)
169 			return i;
170 	return -1;
171 }
172 
173 /// Whether array contains value, no BS.
174 bool contains(T, V)(in T[] arr, auto ref in V val)
175 	if (is(typeof(arr[0]==val)))
176 {
177 	return arr.indexOfElement(val) >= 0;
178 }
179 
180 /// Like startsWith, but with an offset.
181 bool containsAt(T)(in T[] haystack, in T[] needle, size_t offset)
182 {
183 	return haystack.length >= offset + needle.length
184 		&& haystack[offset..offset+needle.length] == needle;
185 }
186 
187 unittest
188 {
189 	assert( "abracadabra".containsAt("ada", 5));
190 	assert(!"abracadabra".containsAt("ada", 6));
191 	assert(!"abracadabra".containsAt("ada", 99));
192 }
193 
194 bool isIn(T)(T val, in T[] arr)
195 {
196 	return arr.contains(val);
197 }
198 
199 bool isOneOf(T)(T val, T[] arr...)
200 {
201 	return arr.contains(val);
202 }
203 
204 /// Like AA.get - soft indexing, throws an
205 /// Exception (not an Error) on out-of-bounds,
206 /// even in release builds.
207 ref T get(T)(T[] arr, size_t index)
208 {
209 	enforce(index < arr.length, "Out-of-bounds array access");
210 	return arr[index];
211 }
212 
213 /// Like AA.get - soft indexing, returns
214 /// default value on out-of-bounds.
215 auto get(T)(T[] arr, size_t index, auto ref T defaultValue)
216 {
217 	if (index >= arr.length)
218 		return defaultValue;
219 	return arr[index];
220 }
221 
222 /// Expand the array if index is out-of-bounds.
223 ref T getExpand(T)(ref T[] arr, size_t index)
224 {
225 	if (index >= arr.length)
226 		arr.length = index + 1;
227 	return arr[index];
228 }
229 
230 /// ditto
231 ref T putExpand(T)(ref T[] arr, size_t index, auto ref T value)
232 {
233 	if (index >= arr.length)
234 		arr.length = index + 1;
235 	return arr[index] = value;
236 }
237 
238 /// Slices an array. Throws an Exception (not an Error)
239 /// on out-of-bounds, even in release builds.
240 T[] slice(T)(T[] arr, size_t p0, size_t p1)
241 {
242 	enforce(p0 < p1 && p1 < arr.length, "Out-of-bounds array slice");
243 	return arr[p0..p1];
244 }
245 
246 /// Given an array and its slice, returns the
247 /// start index of the slice inside the array.
248 size_t sliceIndex(T)(in T[] arr, in T[] slice)
249 {
250 	auto a = arr.ptr;
251 	auto b = a + arr.length;
252 	auto p = slice.ptr;
253 	assert(a <= p && p <= b, "Out-of-bounds array slice");
254 	return p - a;
255 }
256 
257 /// Like std.array.split, but returns null if val was empty.
258 auto splitEmpty(T, S)(T value, S separator)
259 {
260 	return value.length ? split(value, separator) : null;
261 }
262 
263 import std.random;
264 
265 /// Select and return a random element from the array.
266 auto ref sample(T)(T[] arr)
267 {
268 	return arr[uniform(0, $)];
269 }
270 
271 unittest
272 {
273 	assert([7, 7, 7].sample == 7);
274 	auto s = ["foo", "bar"].sample(); // Issue 13807
275 	const(int)[] a2 = [5]; sample(a2);
276 }
277 
278 /// Select and return a random element from the array,
279 /// and remove it from the array.
280 T pluck(T)(ref T[] arr)
281 {
282 	auto pos = uniform(0, arr.length);
283 	auto result = arr[pos];
284 	arr = arr.remove(pos);
285 	return result;
286 }
287 
288 unittest
289 {
290 	auto arr = [1, 2, 3];
291 	auto res = [arr.pluck, arr.pluck, arr.pluck];
292 	res.sort();
293 	assert(res == [1, 2, 3]);
294 }
295 
296 import std.functional;
297 
298 T[] countSort(alias value = "a", T)(T[] arr)
299 {
300 	alias unaryFun!value getValue;
301 	alias typeof(getValue(arr[0])) V;
302 	if (arr.length == 0) return arr;
303 	V min = getValue(arr[0]), max = getValue(arr[0]);
304 	foreach (el; arr[1..$])
305 	{
306 		auto v = getValue(el);
307 		if (min > v)
308 			min = v;
309 		if (max < v)
310 			max = v;
311 	}
312 	auto n = max-min+1;
313 	auto counts = new size_t[n];
314 	foreach (el; arr)
315 		counts[getValue(el)-min]++;
316 	auto indices = new size_t[n];
317 	foreach (i; 1..n)
318 		indices[i] = indices[i-1] + counts[i-1];
319 	T[] result = new T[arr.length];
320 	foreach (el; arr)
321 		result[indices[getValue(el)-min]++] = el;
322 	return result;
323 }
324 
325 // ***************************************************************************
326 
327 void stackPush(T)(ref T[] arr, T val)
328 {
329 	arr ~= val;
330 }
331 alias stackPush queuePush;
332 
333 T stackPeek(T)(T[] arr) { return arr[$-1]; }
334 
335 T stackPop(T)(ref T[] arr)
336 {
337 	auto ret = arr[$-1];
338 	arr = arr[0..$-1];
339 	return ret;
340 }
341 
342 T queuePeek(T)(T[] arr) { return arr[0]; }
343 
344 T queuePeekLast(T)(T[] arr) { return arr[$-1]; }
345 
346 T queuePop(T)(ref T[] arr)
347 {
348 	auto ret = arr[0];
349 	arr = arr[1..$];
350 	if (!arr.length) arr = null;
351 	return ret;
352 }
353 
354 T shift(T)(ref T[] arr) { T result = arr[0]; arr = arr[1..$]; return result; }
355 void unshift(T)(ref T[] arr, T value) { arr.insertInPlace(0, value); }
356 
357 /// If arr starts with prefix, slice it off and return true.
358 /// Otherwise leave arr unchaned and return false.
359 deprecated("Use std.algorithm.skipOver instead")
360 bool eat(T)(ref T[] arr, T[] prefix)
361 {
362 	if (arr.startsWith(prefix))
363 	{
364 		arr = arr[prefix.length..$];
365 		return true;
366 	}
367 	return false;
368 }
369 
370 /// Returns the slice of source up to the first occurrence of delim,
371 /// and fast-forwards source to the point after delim.
372 /// If delim is not found, the behavior depends on orUntilEnd:
373 /// - If orUntilEnd is false (default), it returns null
374 ///   and leaves source unchanged.
375 /// - If orUntilEnd is true, it returns source,
376 ///   and then sets source to null.
377 T[] skipUntil(T, D)(ref T[] source, D delim, bool orUntilEnd = false)
378 {
379 	enum bool isSlice = is(typeof(source[0..1]==delim));
380 	enum bool isElem  = is(typeof(source[0]   ==delim));
381 	static assert(isSlice || isElem, "Can't skip " ~ T.stringof ~ " until " ~ D.stringof);
382 	static assert(isSlice != isElem, "Ambiguous types for skipUntil: " ~ T.stringof ~ " and " ~ D.stringof);
383 	static if (isSlice)
384 		auto delimLength = delim.length;
385 	else
386 		enum delimLength = 1;
387 
388 	static if (is(typeof(ae.utils.array.indexOf(source, delim))))
389 		alias indexOf = ae.utils.array.indexOf;
390 	else
391 	static if (is(typeof(std..string.indexOf(source, delim))))
392 		alias indexOf = std..string.indexOf;
393 
394 	auto i = indexOf(source, delim);
395 	if (i < 0)
396 	{
397 		if (orUntilEnd)
398 		{
399 			auto result = source;
400 			source = null;
401 			return result;
402 		}
403 		else
404 			return null;
405 	}
406 	auto result = source[0..i];
407 	source = source[i+delimLength..$];
408 	return result;
409 }
410 
411 deprecated("Use skipUntil instead")
412 enum OnEof { returnNull, returnRemainder, throwException }
413 
414 deprecated("Use skipUntil instead")
415 template eatUntil(OnEof onEof = OnEof.throwException)
416 {
417 	T[] eatUntil(T, D)(ref T[] source, D delim)
418 	{
419 		static if (onEof == OnEof.returnNull)
420 			return skipUntil(source, delim, false);
421 		else
422 		static if (onEof == OnEof.returnRemainder)
423 			return skipUntil(source, delim, true);
424 		else
425 			return skipUntil(source, delim, false).enforce("Delimiter not found in source");
426 	}
427 }
428 
429 deprecated unittest
430 {
431 	string s;
432 
433 	s = "Mary had a little lamb";
434 	assert(s.eatUntil(" ") == "Mary");
435 	assert(s.eatUntil(" ") == "had");
436 	assert(s.eatUntil(' ') == "a");
437 
438 	assertThrown!Exception(s.eatUntil("#"));
439 	assert(s.eatUntil!(OnEof.returnNull)("#") is null);
440 	assert(s.eatUntil!(OnEof.returnRemainder)("#") == "little lamb");
441 
442 	ubyte[] bytes = [1, 2, 0, 3, 4, 0, 0];
443 	assert(bytes.eatUntil(0) == [1, 2]);
444 	assert(bytes.eatUntil([ubyte(0), ubyte(0)]) == [3, 4]);
445 }
446 
447 // ***************************************************************************
448 
449 // Equivalents of array(xxx(...)), but less parens and UFCS-able.
450 auto amap(alias pred, T)(T[] arr) { return array(map!pred(arr)); }
451 auto afilter(alias pred, T)(T[] arr) { return array(filter!pred(arr)); }
452 auto auniq(T)(T[] arr) { return array(uniq(arr)); }
453 auto asort(alias pred, T)(T[] arr) { sort!pred(arr); return arr; }
454 
455 unittest
456 {
457 	assert([1, 2, 3].amap!`a*2`() == [2, 4, 6]);
458 	assert([1, 2, 3].amap!(n => n*n)() == [1, 4, 9]);
459 }
460 
461 // ***************************************************************************
462 
463 /// Array with normalized comparison and hashing.
464 /// Params:
465 ///   T = array element type to wrap.
466 ///   normalize = function which should return a range of normalized elements.
467 struct NormalizedArray(T, alias normalize)
468 {
469 	T[] arr;
470 
471 	this(T[] arr) { this.arr = arr; }
472 
473 	int opCmp    (in T[]                 other) const { return std.algorithm.cmp(normalize(arr), normalize(other    ))   ; }
474 	int opCmp    (    const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr))   ; }
475 	int opCmp    (ref const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr))   ; }
476 	bool opEquals(in T[]                 other) const { return std.algorithm.cmp(normalize(arr), normalize(other    ))==0; }
477 	bool opEquals(    const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr))==0; }
478 	bool opEquals(ref const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr))==0; }
479 
480 	hash_t toHashReal() const
481 	{
482 		import std.digest.crc;
483 		CRC32 crc;
484 		foreach (c; normalize(arr))
485 			crc.put(cast(ubyte[])((&c)[0..1]));
486 		static union Result { ubyte[4] crcResult; hash_t hash; }
487 		return Result(crc.finish()).hash;
488 	}
489 
490 	hash_t toHash() const nothrow @trusted
491 	{
492 		return (cast(hash_t delegate() nothrow @safe)&toHashReal)();
493 	}
494 }
495 
496 // ***************************************************************************
497 
498 /// Equivalent of PHP's `list` language construct:
499 /// http://php.net/manual/en/function.list.php
500 /// Works with arrays and tuples.
501 /// Specify `null` as an argument to ignore that index
502 /// (equivalent of `list(x, , y)` in PHP).
503 auto list(Args...)(auto ref Args args)
504 {
505 	struct List
506 	{
507 		auto dummy() { return args[0]; }
508 		void opAssign(T)(auto ref T t)
509 		{
510 			assert(t.length == args.length,
511 				"Assigning %d elements to list with %d elements"
512 				.format(t.length, args.length));
513 			foreach (i; RangeTuple!(Args.length))
514 				static if (!is(Args[i] == typeof(null)))
515 					args[i] = t[i];
516 		}
517 	}
518 	return List();
519 }
520 
521 ///
522 unittest
523 {
524 	string name, value;
525 	list(name, null, value) = "NAME=VALUE".findSplit("=");
526 	assert(name == "NAME" && value == "VALUE");
527 }