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