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