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