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 <ae@cy.md>
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.functional;
24 import std.traits;
25 
26 import ae.utils.meta;
27 
28 public import ae.utils.aa;
29 public import ae.utils.appender;
30 
31 /// Slice a variable.
32 T[] toArray(T)(ref T v)
33 {
34 	return (&v)[0..1];
35 }
36 
37 /// std.array.staticArray shim
38 static if (__traits(hasMember, std.array, "staticArray"))
39 	public import std.array : staticArray;
40 else
41 	pragma(inline, true) T[n] staticArray(T, size_t n)(auto ref T[n] a) { return a; }
42 
43 /// Convert a dynamic array to a static array
44 template toStaticArray(size_t n)
45 {
46 	ref T[n] toStaticArray(T)(return T[] a)
47 	{
48 		assert(a.length == n, "Size mismatch");
49 		return a[0 .. n];
50 	}
51 }
52 
53 unittest
54 {
55 	auto a = [1, 2, 3];
56 	assert(a.toStaticArray!3 == [1, 2, 3]);
57 
58 	import std.range : chunks;
59 	auto b = [1, 2, 3, 4];
60 	assert(b.chunks(2).map!(toStaticArray!2).array == [[1, 2], [3, 4]]);
61 }
62 
63 /// Return the value represented as an array of bytes.
64 @property inout(ubyte)[] bytes(T)(ref inout(T) value)
65 	if (!hasIndirections!T)
66 {
67 	return value.toArray().bytes;
68 }
69 
70 /// ditto
71 @property inout(ubyte)[] bytes(T)(inout(T) value)
72 	if (is(T U : U[]) && !hasIndirections!U)
73 {
74 	return cast(inout(ubyte)[])value;
75 }
76 
77 unittest
78 {
79 	ubyte b = 5;
80 	assert(b.bytes == [5]);
81 
82 	struct S { ubyte b = 5; }
83 	S s;
84 	assert(s.bytes == [5]);
85 
86 	ubyte[1] sa = [5];
87 	assert(sa.bytes == [5]);
88 
89 	void[] va = sa[];
90 	assert(va.bytes == [5]);
91 }
92 
93 /// Reverse of bytes()
94 ref inout(T) fromBytes(T)(inout(ubyte)[] bytes)
95 	if (!hasIndirections!T)
96 {
97 	assert(bytes.length == T.sizeof, "Data length mismatch for %s".format(T.stringof));
98 	return *cast(inout(T)*)bytes.ptr;
99 }
100 
101 /// ditto
102 inout(T) fromBytes(T)(inout(ubyte)[] bytes)
103 	if (is(T U : U[]) && !hasIndirections!U)
104 {
105 	return cast(inout(T))bytes;
106 }
107 
108 unittest
109 {
110 	{       ubyte b = 5; assert(b.bytes.fromBytes!ubyte == 5); }
111 	{ const ubyte b = 5; assert(b.bytes.fromBytes!ubyte == 5); }
112 	struct S { ubyte b; }
113 	{       ubyte b = 5; assert(b.bytes.fromBytes!S == S(5)); }
114 }
115 
116 unittest
117 {
118 	struct S { ubyte a, b; }
119 	ubyte[] arr = [1, 2];
120 	assert(arr.fromBytes!S == S(1, 2));
121 	assert(arr.fromBytes!(S[]) == [S(1, 2)]);
122 }
123 
124 /// Returns an empty, but non-null slice of T.
125 auto emptySlice(T)() pure
126 {
127 	static if (false) // LDC optimizes this out
128 	{
129 		T[0] arr;
130 		auto p = arr.ptr;
131 	}
132 	else
133 		auto p = cast(T*)1;
134 	return p[0..0];
135 }
136 
137 unittest
138 {
139 	int[] arr = emptySlice!int;
140 	assert(arr.ptr);
141 	immutable int[] iarr = emptySlice!int;
142 	assert(iarr.ptr);
143 }
144 
145 /// C `memcmp` wrapper.
146 int memcmp(in ubyte[] a, in ubyte[] b)
147 {
148 	assert(a.length == b.length);
149 	import core.stdc.string : memcmp;
150 	return memcmp(a.ptr, b.ptr, a.length);
151 }
152 
153 /// Like std.algorithm.copy, but without the auto-decode bullshit.
154 /// https://issues.dlang.org/show_bug.cgi?id=13650
155 void memmove(T)(T[] dst, in T[] src)
156 {
157 	assert(src.length == dst.length);
158 	import core.stdc.string : memmove;
159 	memmove(dst.ptr, src.ptr, dst.length * T.sizeof);
160 }
161 
162 /// Performs binary operation `op` on every element of `a` and `b`.
163 T[] vector(string op, T)(T[] a, T[] b)
164 {
165 	assert(a.length == b.length);
166 	T[] result = new T[a.length];
167 	foreach (i, ref r; result)
168 		r = mixin("a[i]" ~ op ~ "b[i]");
169 	return result;
170 }
171 
172 /// Performs in-place binary operation `op` on every element of `a` and `b`.
173 T[] vectorAssign(string op, T)(T[] a, T[] b)
174 {
175 	assert(a.length == b.length);
176 	foreach (i, ref r; a)
177 		mixin("r " ~ op ~ "= b[i];");
178 	return a;
179 }
180 
181 /// Return `s` expanded to at least `l` elements, filling them with `c`.
182 T[] padRight(T)(T[] s, size_t l, T c)
183 {
184 	auto ol = s.length;
185 	if (ol < l)
186 	{
187 		s.length = l;
188 		s[ol..$] = c;
189 	}
190 	return s;
191 }
192 
193 /// Return a new `T[]` of length `l`, filled with `c`.
194 T[] repeatOne(T)(T c, size_t l)
195 {
196 	T[] result = new T[l];
197 	result[] = c;
198 	return result;
199 }
200 
201 /// Complement to std.string.indexOf which works with arrays
202 /// of non-character types.
203 /// Unlike std.algorithm.countUntil, it does not auto-decode,
204 /// and returns an index usable for array indexing/slicing.
205 sizediff_t indexOf(T, D)(in T[] arr, in D val)
206 //	if (!isSomeChar!T)
207 	if (!isSomeChar!T && is(typeof(arr.countUntil(val))) && is(typeof(arr[0]==val)))
208 {
209 	//assert(arr[0]==val);
210 	return arr.countUntil(val);
211 }
212 
213 sizediff_t indexOf(T)(in T[] arr, in T[] val) /// ditto
214 	if (!isSomeChar!T && is(typeof(arr.countUntil(val))))
215 {
216 	return arr.countUntil(val);
217 } /// ditto
218 
219 /// Reimplementation of `std.algorithm.indexOf`,
220 /// but with no auto-decoding.
221 sizediff_t indexOfElement(T, D)(in T[] arr, auto ref const D val)
222 	if (is(typeof(arr[0]==val)))
223 {
224 	foreach (i, ref v; arr)
225 		if (v == val)
226 			return i;
227 	return -1;
228 }
229 
230 /// Whether array contains value, no BS.
231 bool contains(T, V)(in T[] arr, auto ref const V val)
232 	if (is(typeof(arr[0]==val)))
233 {
234 	return arr.indexOfElement(val) >= 0;
235 }
236 
237 /// Ditto, for substrings
238 bool contains(T, U)(T[] str, U[] what)
239 if (is(Unqual!T == Unqual!U))
240 {
241 	return str._indexOf(what) >= 0;
242 }
243 
244 unittest
245 {
246 	assert( "abc".contains('b'));
247 	assert(!"abc".contains('x'));
248 	assert( "abc".contains("b"));
249 	assert(!"abc".contains("x"));
250 }
251 
252 /// Like startsWith, but with an offset.
253 bool containsAt(T)(in T[] haystack, in T[] needle, size_t offset)
254 {
255 	return haystack.length >= offset + needle.length
256 		&& haystack[offset..offset+needle.length] == needle;
257 }
258 
259 unittest
260 {
261 	assert( "abracadabra".containsAt("ada", 5));
262 	assert(!"abracadabra".containsAt("ada", 6));
263 	assert(!"abracadabra".containsAt("ada", 99));
264 }
265 
266 /// Returns `true` if one of the elements of `arr` contains `val`.
267 bool isIn(T)(T val, in T[] arr)
268 {
269 	return arr.contains(val);
270 }
271 
272 /// Returns `true` if one of the elements of `arr` contains `val`.
273 bool isOneOf(T)(T val, T[] arr...)
274 {
275 	return arr.contains(val);
276 }
277 
278 /// Like AA.get - soft indexing, throws an
279 /// Exception (not an Error) on out-of-bounds,
280 /// even in release builds.
281 ref T get(T)(T[] arr, size_t index)
282 {
283 	enforce(index < arr.length, "Out-of-bounds array access");
284 	return arr[index];
285 }
286 
287 /// Like AA.get - soft indexing, returns
288 /// default value on out-of-bounds.
289 auto get(T)(T[] arr, size_t index, auto ref T defaultValue)
290 {
291 	if (index >= arr.length)
292 		return defaultValue;
293 	return arr[index];
294 }
295 
296 /// Expand the array if index is out-of-bounds.
297 ref T getExpand(T)(ref T[] arr, size_t index)
298 {
299 	if (index >= arr.length)
300 		arr.length = index + 1;
301 	return arr[index];
302 }
303 
304 /// ditto
305 ref T putExpand(T)(ref T[] arr, size_t index, auto ref T value)
306 {
307 	if (index >= arr.length)
308 		arr.length = index + 1;
309 	return arr[index] = value;
310 }
311 
312 /// Slices an array. Throws an Exception (not an Error)
313 /// on out-of-bounds, even in release builds.
314 T[] slice(T)(T[] arr, size_t p0, size_t p1)
315 {
316 	enforce(p0 < p1 && p1 < arr.length, "Out-of-bounds array slice");
317 	return arr[p0..p1];
318 }
319 
320 /// Given an array and a reference to an element inside it, returns its index.
321 /// The reverse operation of indexing an array.
322 size_t elementIndex(T)(in T[] arr, in ref T element)
323 {
324 	auto start = arr.ptr;
325 	auto end = start + arr.length;
326 	auto p = &element;
327 	assert(start <= p && p < end, "Element is not in array");
328 	return p - start;
329 }
330 
331 unittest
332 {
333 	auto arr = [1, 2, 3];
334 	assert(arr.elementIndex(arr[1]) == 1);
335 }
336 
337 /// Given an array and its slice, returns the
338 /// start index of the slice inside the array.
339 /// The reverse operation of slicing an array.
340 size_t sliceIndex(T)(in T[] arr, in T[] slice)
341 {
342 	auto a = arr.ptr;
343 	auto b = a + arr.length;
344 	auto p = slice.ptr;
345 	assert(a <= p && p <= b, "Out-of-bounds array slice");
346 	return p - a;
347 }
348 
349 /// Like std.array.split, but returns null if val was empty.
350 auto splitEmpty(T, S)(T value, S separator)
351 {
352 	return value.length ? split(value, separator) : null;
353 }
354 
355 /// Like std.array.split, but always returns a non-empty array.
356 auto split1(T, S)(T value, S separator)
357 {
358 	auto result = split(value, separator);
359 	return result.length ? result : [value];
360 }
361 
362 /// Include delimiter in result chunks as suffix
363 H[] splitWithSuffix(H, S)(H haystack, S separator)
364 {
365 	H[] result;
366 	while (haystack.length)
367 	{
368 		auto pos = haystack._indexOf(separator);
369 		if (pos < 0)
370 			pos = haystack.length;
371 		else
372 		{
373 			static if (is(typeof(haystack[0] == separator)))
374 				pos += 1;
375 			else
376 			static if (is(typeof(haystack[0..1] == separator)))
377 				pos += separator.length;
378 			else
379 				static assert(false, "Don't know how to split " ~ H.stringof ~ " by " ~ S.stringof);
380 		}
381 		result ~= haystack[0..pos];
382 		haystack = haystack[pos..$];
383 	}
384 	return result;
385 }
386 
387 unittest
388 {
389 	assert("a\nb".splitWithSuffix('\n') == ["a\n", "b"]);
390 	assert([1, 0, 2].splitWithSuffix(0) == [[1, 0], [2]]);
391 
392 	assert("a\r\nb".splitWithSuffix("\r\n") == ["a\r\n", "b"]);
393 	assert([1, 0, 0, 2].splitWithSuffix([0, 0]) == [[1, 0, 0], [2]]);
394 }
395 
396 /// Include delimiter in result chunks as prefix
397 H[] splitWithPrefix(H, S)(H haystack, S separator)
398 {
399 	H[] result;
400 	while (haystack.length)
401 	{
402 		auto pos = haystack[1..$]._indexOf(separator);
403 		if (pos < 0)
404 			pos = haystack.length;
405 		else
406 			pos++;
407 		result ~= haystack[0..pos];
408 		haystack = haystack[pos..$];
409 	}
410 	return result;
411 }
412 
413 unittest
414 {
415 	assert("a\nb".splitWithPrefix('\n') == ["a", "\nb"]);
416 	assert([1, 0, 2].splitWithPrefix(0) == [[1], [0, 2]]);
417 
418 	assert("a\r\nb".splitWithPrefix("\r\n") == ["a", "\r\nb"]);
419 	assert([1, 0, 0, 2].splitWithPrefix([0, 0]) == [[1], [0, 0, 2]]);
420 }
421 
422 /// Include delimiters in result chunks as prefix/suffix
423 S[] splitWithPrefixAndSuffix(S)(S haystack, S prefix, S suffix)
424 {
425 	S[] result;
426 	auto separator = suffix ~ prefix;
427 	while (haystack.length)
428 	{
429 		auto pos = haystack._indexOf(separator);
430 		if (pos < 0)
431 			pos = haystack.length;
432 		else
433 			pos += suffix.length;
434 		result ~= haystack[0..pos];
435 		haystack = haystack[pos..$];
436 	}
437 	return result;
438 }
439 
440 ///
441 unittest
442 {
443 	auto s = q"EOF
444 Section 1:
445 10
446 11
447 12
448 Section 2:
449 21
450 22
451 23
452 Section 3:
453 31
454 32
455 33
456 EOF";
457 	auto parts = s.splitWithPrefixAndSuffix("Section ", "\n");
458 	assert(parts.length == 3 && parts.join == s);
459 	foreach (part; parts)
460 		assert(part.startsWith("Section ") && part.endsWith("\n"));
461 }
462 
463 /// Ensure that arr is non-null if empty.
464 T[] nonNull(T)(T[] arr)
465 {
466 	if (arr !is null)
467 		return arr;
468 	return emptySlice!(typeof(arr[0]));
469 }
470 
471 /// If arr is null, return null. Otherwise, return a non-null
472 /// transformation dg over arr.
473 template mapNull(alias dg)
474 {
475 	auto mapNull(T)(T arr)
476 	{
477 		if (arr is null)
478 			return null;
479 		return dg(arr).nonNull;
480 	}
481 }
482 
483 unittest
484 {
485 	assert(string.init.mapNull!(s => s          )  is null);
486 	assert(string.init.mapNull!(s => ""         )  is null);
487 	assert(""         .mapNull!(s => s          ) !is null);
488 	assert(""         .mapNull!(s => string.init) !is null);
489 }
490 
491 /// Select and return a random element from the array.
492 auto ref sample(T)(T[] arr)
493 {
494 	import std.random;
495 	return arr[uniform(0, $)];
496 }
497 
498 unittest
499 {
500 	assert([7, 7, 7].sample == 7);
501 	auto s = ["foo", "bar"].sample(); // Issue 13807
502 	const(int)[] a2 = [5]; sample(a2);
503 }
504 
505 /// Select and return a random element from the array,
506 /// and remove it from the array.
507 T pluck(T)(ref T[] arr)
508 {
509 	import std.random;
510 	auto pos = uniform(0, arr.length);
511 	auto result = arr[pos];
512 	arr = arr.remove(pos);
513 	return result;
514 }
515 
516 unittest
517 {
518 	auto arr = [1, 2, 3];
519 	auto res = [arr.pluck, arr.pluck, arr.pluck];
520 	res.sort();
521 	assert(res == [1, 2, 3]);
522 }
523 
524 import std.functional;
525 
526 /// Sorts `arr` in-place using counting sort.
527 /// The difference between the lowest and highest element of `arr` shouldn't be too big.
528 T[] countSort(alias value = "a", T)(T[] arr)
529 {
530 	alias unaryFun!value getValue;
531 	alias typeof(getValue(arr[0])) V;
532 	if (arr.length == 0) return arr;
533 	V min = getValue(arr[0]), max = getValue(arr[0]);
534 	foreach (el; arr[1..$])
535 	{
536 		auto v = getValue(el);
537 		if (min > v)
538 			min = v;
539 		if (max < v)
540 			max = v;
541 	}
542 	auto n = max-min+1;
543 	auto counts = new size_t[n];
544 	foreach (el; arr)
545 		counts[getValue(el)-min]++;
546 	auto indices = new size_t[n];
547 	foreach (i; 1..n)
548 		indices[i] = indices[i-1] + counts[i-1];
549 	T[] result = new T[arr.length];
550 	foreach (el; arr)
551 		result[indices[getValue(el)-min]++] = el;
552 	return result;
553 }
554 
555 // ***************************************************************************
556 
557 /// Push `val` into `arr`, treating it like a stack.
558 void stackPush(T)(ref T[] arr, auto ref T val)
559 {
560 	arr ~= val;
561 }
562 
563 /// Push `val` into `arr`, treating it like a queue.
564 alias stackPush queuePush;
565 
566 /// Peek at the front of `arr`, treating it like a stack.
567 ref T stackPeek(T)(T[] arr) { return arr[$-1]; }
568 
569 /// Pop a value off the front of `arr`, treating it like a stack.
570 ref T stackPop(T)(ref T[] arr)
571 {
572 	auto ret = &arr[$-1];
573 	arr = arr[0..$-1];
574 	return *ret;
575 }
576 
577 /// Peek at the front of `arr`, treating it like a queue.
578 ref T queuePeek(T)(T[] arr) { return arr[0]; }
579 
580 /// Peek at the back of `arr`, treating it like a queue.
581 ref T queuePeekLast(T)(T[] arr) { return arr[$-1]; }
582 
583 /// Pop a value off the front of `arr`, treating it like a queue.
584 ref T queuePop(T)(ref T[] arr)
585 {
586 	auto ret = &arr[0];
587 	arr = arr[1..$];
588 	if (!arr.length) arr = null;
589 	return *ret;
590 }
591 
592 /// Remove the first element of `arr` and return it.
593 ref T shift(T)(ref T[] arr) { auto oldArr = arr; arr = arr[1..$]; return oldArr[0]; }
594 
595 /// Remove the `n` first elements of `arr` and return them.
596 T[] shift(T)(ref T[] arr, size_t n) { T[] result = arr[0..n]; arr = arr[n..$]; return result; }
597 T[N] shift(size_t N, T)(ref T[] arr) { T[N] result = cast(T[N])(arr[0..N]); arr = arr[N..$]; return result; } /// ditto
598 
599 /// Insert elements in the front of `arr`.
600 void unshift(T)(ref T[] arr, T value) { arr.insertInPlace(0, value); }
601 void unshift(T)(ref T[] arr, T[] value) { arr.insertInPlace(0, value); } /// ditto
602 
603 unittest
604 {
605 	int[] arr = [1, 2, 3];
606 	assert(arr.shift == 1);
607 	assert(arr == [2, 3]);
608 	assert(arr.shift(2) == [2, 3]);
609 	assert(arr == []);
610 
611 	arr = [3];
612 	arr.unshift([1, 2]);
613 	assert(arr == [1, 2, 3]);
614 	arr.unshift(0);
615 	assert(arr == [0, 1, 2, 3]);
616 
617 	assert(arr.shift!2 == [0, 1]);
618 	assert(arr == [2, 3]);
619 }
620 
621 /// If arr starts with prefix, slice it off and return true.
622 /// Otherwise leave arr unchaned and return false.
623 deprecated("Use std.algorithm.skipOver instead")
624 bool eat(T)(ref T[] arr, T[] prefix)
625 {
626 	if (arr.startsWith(prefix))
627 	{
628 		arr = arr[prefix.length..$];
629 		return true;
630 	}
631 	return false;
632 }
633 
634 // Overload disambiguator
635 private sizediff_t _indexOf(H, N)(H haystack, N needle)
636 {
637 	static import std.string;
638 
639 	static if (is(typeof(ae.utils.array.indexOf(haystack, needle))))
640 		alias indexOf = ae.utils.array.indexOf;
641 	else
642 	static if (is(typeof(std..string.indexOf(haystack, needle))))
643 		alias indexOf = std..string.indexOf;
644 	else
645 		static assert(false, "No suitable indexOf overload found");
646 	return indexOf(haystack, needle);
647 }
648 
649 /// Returns the slice of source up to the first occurrence of delim,
650 /// and fast-forwards source to the point after delim.
651 /// If delim is not found, the behavior depends on orUntilEnd:
652 /// - If orUntilEnd is false (default), it returns null
653 ///   and leaves source unchanged.
654 /// - If orUntilEnd is true, it returns source,
655 ///   and then sets source to null.
656 T[] skipUntil(T, D)(ref T[] source, D delim, bool orUntilEnd = false)
657 {
658 	enum bool isSlice = is(typeof(source[0..1]==delim));
659 	enum bool isElem  = is(typeof(source[0]   ==delim));
660 	static assert(isSlice || isElem, "Can't skip " ~ T.stringof ~ " until " ~ D.stringof);
661 	static assert(isSlice != isElem, "Ambiguous types for skipUntil: " ~ T.stringof ~ " and " ~ D.stringof);
662 	static if (isSlice)
663 		auto delimLength = delim.length;
664 	else
665 		enum delimLength = 1;
666 
667 	auto i = _indexOf(source, delim);
668 	if (i < 0)
669 	{
670 		if (orUntilEnd)
671 		{
672 			auto result = source;
673 			source = null;
674 			return result;
675 		}
676 		else
677 			return null;
678 	}
679 	auto result = source[0..i];
680 	source = source[i+delimLength..$];
681 	return result;
682 }
683 
684 /// Like `std.algorithm.skipOver`, but stops when
685 /// `pred(suffix)` or `pred(suffix[0])` returns false.
686 template skipWhile(alias pred)
687 {
688 	T[] skipWhile(T)(ref T[] source, bool orUntilEnd = false)
689 	{
690 		enum bool isSlice = is(typeof(pred(source[0..1])));
691 		enum bool isElem  = is(typeof(pred(source[0]   )));
692 		static assert(isSlice || isElem, "Can't skip " ~ T.stringof ~ " until " ~ pred.stringof);
693 		static assert(isSlice != isElem, "Ambiguous types for skipWhile: " ~ T.stringof ~ " and " ~ pred.stringof);
694 
695 		foreach (i; 0 .. source.length)
696 		{
697 			bool match;
698 			static if (isSlice)
699 				match = pred(source[i .. $]);
700 			else
701 				match = pred(source[i]);
702 			if (!match)
703 			{
704 				auto result = source[0..i];
705 				source = source[i .. $];
706 				return result;
707 			}
708 		}
709 
710 		if (orUntilEnd)
711 		{
712 			auto result = source;
713 			source = null;
714 			return result;
715 		}
716 		else
717 			return null;
718 	}
719 }
720 
721 unittest
722 {
723 	import std.ascii : isDigit;
724 
725 	string s = "01234abcde";
726 	auto p = s.skipWhile!isDigit;
727 	assert(p == "01234" && s == "abcde", p ~ "|" ~ s);
728 }
729 
730 deprecated("Use skipUntil instead")
731 enum OnEof { returnNull, returnRemainder, throwException }
732 
733 deprecated("Use skipUntil instead")
734 template eatUntil(OnEof onEof = OnEof.throwException)
735 {
736 	T[] eatUntil(T, D)(ref T[] source, D delim)
737 	{
738 		static if (onEof == OnEof.returnNull)
739 			return skipUntil(source, delim, false);
740 		else
741 		static if (onEof == OnEof.returnRemainder)
742 			return skipUntil(source, delim, true);
743 		else
744 			return skipUntil(source, delim, false).enforce("Delimiter not found in source");
745 	}
746 }
747 
748 deprecated unittest
749 {
750 	string s;
751 
752 	s = "Mary had a little lamb";
753 	assert(s.eatUntil(" ") == "Mary");
754 	assert(s.eatUntil(" ") == "had");
755 	assert(s.eatUntil(' ') == "a");
756 
757 	assertThrown!Exception(s.eatUntil("#"));
758 	assert(s.eatUntil!(OnEof.returnNull)("#") is null);
759 	assert(s.eatUntil!(OnEof.returnRemainder)("#") == "little lamb");
760 
761 	ubyte[] bytes = [1, 2, 0, 3, 4, 0, 0];
762 	assert(bytes.eatUntil(0) == [1, 2]);
763 	assert(bytes.eatUntil([ubyte(0), ubyte(0)]) == [3, 4]);
764 }
765 
766 // ***************************************************************************
767 
768 /// Equivalents of `array(xxx(...))`.
769 auto amap(alias pred, T)(T[] arr) { return array(map!pred(arr)); }
770 auto afilter(alias pred, T)(T[] arr) { return array(filter!pred(arr)); } /// ditto
771 auto auniq(T)(T[] arr) { return array(uniq(arr)); } /// ditto
772 auto asort(alias pred, T)(T[] arr) { sort!pred(arr); return arr; } /// ditto
773 
774 unittest
775 {
776 	assert([1, 2, 3].amap!`a*2`() == [2, 4, 6]);
777 	assert([1, 2, 3].amap!(n => n*n)() == [1, 4, 9]);
778 }
779 
780 /// Like `amap` but with a static array.
781 auto amap(alias pred, T, size_t n)(T[n] arr)
782 {
783 	alias R = typeof(unaryFun!pred(arr[0]));
784 	R[n] result;
785 	foreach (i, ref r; result)
786 		r = unaryFun!pred(arr[i]);
787 	return result;
788 }
789 
790 // ***************************************************************************
791 
792 /// Array with normalized comparison and hashing.
793 /// Params:
794 ///   T = array element type to wrap.
795 ///   normalize = function which should return a range of normalized elements.
796 struct NormalizedArray(T, alias normalize)
797 {
798 	T[] arr; /// Underlying array.
799 
800 	this(T[] arr) { this.arr = arr; } ///
801 
802 	int opCmp    (in T[]                 other) const { return std.algorithm.cmp(normalize(arr), normalize(other    ))   ; } ///
803 	int opCmp    (    const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr))   ; } ///
804 	int opCmp    (ref const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr))   ; } ///
805 	bool opEquals(in T[]                 other) const { return std.algorithm.cmp(normalize(arr), normalize(other    ))==0; } ///
806 	bool opEquals(    const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr))==0; } ///
807 	bool opEquals(ref const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr))==0; } ///
808 
809 	private hash_t toHashReal() const
810 	{
811 		import std.digest.crc;
812 		CRC32 crc;
813 		foreach (c; normalize(arr))
814 			crc.put(cast(ubyte[])((&c)[0..1]));
815 		static union Result { ubyte[4] crcResult; hash_t hash; }
816 		return Result(crc.finish()).hash;
817 	}
818 
819 	hash_t toHash() const nothrow @trusted
820 	{
821 		return (cast(hash_t delegate() nothrow @safe)&toHashReal)();
822 	} ///
823 }
824 
825 // ***************************************************************************
826 
827 /// Equivalent of PHP's `list` language construct:
828 /// http://php.net/manual/en/function.list.php
829 /// Works with arrays and tuples.
830 /// Specify `null` as an argument to ignore that index
831 /// (equivalent of `list(x, , y)` in PHP).
832 auto list(Args...)(auto ref Args args)
833 {
834 	struct List
835 	{
836 		auto dummy() { return args[0]; } // https://issues.dlang.org/show_bug.cgi?id=11886
837 		void opAssign(T)(auto ref T t)
838 		{
839 			assert(t.length == args.length,
840 				"Assigning %d elements to list with %d elements"
841 				.format(t.length, args.length));
842 			foreach (i; RangeTuple!(Args.length))
843 				static if (!is(Args[i] == typeof(null)))
844 					args[i] = t[i];
845 		}
846 	}
847 	return List();
848 }
849 
850 ///
851 unittest
852 {
853 	string name, value;
854 	list(name, null, value) = "NAME=VALUE".findSplit("=");
855 	assert(name == "NAME" && value == "VALUE");
856 }
857 
858 version(LittleEndian)
859 unittest
860 {
861 	uint onlyValue;
862 	ubyte[] data = [ubyte(42), 0, 0, 0];
863 	list(onlyValue) = cast(uint[])data;
864 	assert(onlyValue == 42);
865 }