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