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