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