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