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