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