1 module betterclist; 2 3 import std.algorithm; 4 import std.range; 5 import std.traits; 6 7 struct List(T, long N = -1) 8 { 9 alias ElementType = T; 10 11 /// Whether List has fixed size, which means it is backed by a static array 12 enum isSized = N >= 0; 13 static if (isSized) 14 { 15 /// Static array with capacity N that holds the List elements. 16 T[N] array; 17 18 /// Construct List with initial elements. 19 this(Args...)(auto ref Args args) 20 { 21 pushBack(args); 22 } 23 } 24 else 25 { 26 /// Slice with dynamic capacity that holds the List elements. 27 T[] array; 28 29 /// Construct List with backing slice and optional initial elements. 30 this(Args...)(T[] backingSlice, auto ref Args args) 31 { 32 this.array = backingSlice; 33 static if (args.length > 0) 34 { 35 pushBack(args); 36 } 37 } 38 39 /// Construct List with backing pointer and capacity and optional initial elements. 40 this(Args...)(T* backingArrayPointer, size_t capacity, auto ref Args args) 41 { 42 this(backingArrayPointer[0 .. capacity], args); 43 } 44 45 /// Construct List with backing buffer slice and optional initial elements. 46 this(Args...)(void[] backingSlice, auto ref Args args) 47 { 48 this(cast(T[]) backingSlice, args); 49 } 50 51 /// Construct List with backing buffer pointer and size and optional initial elements. 52 this(Args...)(void* backingArrayPointer, size_t bufferSize, auto ref Args args) 53 { 54 this(backingArrayPointer[0 .. bufferSize], args); 55 } 56 } 57 /// Current used length, must be less than array's length. 58 /// This is readonly accessible by the `length` property from used slice. 59 private size_t usedLength = 0; 60 61 invariant 62 { 63 assert(usedLength <= array.length); 64 } 65 66 @nogc pure 67 { 68 /// List element capacity, which is the backing array's length. 69 @property size_t capacity() const @safe nothrow 70 { 71 return array.length; 72 } 73 74 /// Available capacity for inserting elements. 75 @property size_t availableCapacity() const @safe nothrow 76 { 77 return capacity - usedLength; 78 } 79 80 /// Returns whether there are no elements in List. 81 @property bool empty() const @safe nothrow 82 { 83 return usedLength == 0; 84 } 85 86 /// Returns whether List is full, that is, has no more available capacity. 87 @property bool full() const @safe nothrow 88 { 89 return usedLength == capacity; 90 } 91 92 /// Get a slice for the used elements. 93 auto usedSlice() inout 94 { 95 return array[0 .. usedLength]; 96 } 97 /// Get a slice for the remaining elements. 98 private auto remainingSlice() inout 99 { 100 return array[usedLength .. capacity]; 101 } 102 103 /// Get a slice for the used elements. 104 auto opIndex() inout 105 { 106 return usedSlice; 107 } 108 /// Index the slice of used elements. 109 auto ref opIndex(const size_t index) inout 110 in { assert(index < usedLength); } 111 do 112 { 113 return usedSlice[index]; 114 } 115 /// Alias this allows operations to target used slice by default. 116 alias usedSlice this; 117 } 118 119 /++ 120 + Push a value in the end of List. 121 + Returns: 0 if value was successfully inserted, 1 otherwise. 122 +/ 123 long pushBack(U)(auto ref U value) 124 if (!isInputRange!U) 125 { 126 if (full) 127 { 128 return 1; 129 } 130 array[usedLength] = value; 131 usedLength++; 132 return 0; 133 } 134 135 /++ 136 + Push a range of values to the end of List. 137 + Returns: number of values not inserted into List: 138 + 0 if all values were inserted successfully, 139 + positive number if the number of remaining elements is known, 140 + -1 if the number of remaining elements is not know. 141 +/ 142 long pushBack(R)(auto ref R range) 143 if (isInputRange!R) 144 { 145 static if (hasLength!R) 146 { 147 auto rangeLength = range.length; 148 if (rangeLength > availableCapacity) 149 { 150 range.take(availableCapacity).copy(remainingSlice); 151 usedLength = capacity; 152 return cast(typeof(return))(range.length - availableCapacity); 153 } 154 range.copy(remainingSlice); 155 usedLength += rangeLength; 156 return 0; 157 } 158 else 159 { 160 while (!range.empty && usedLength < capacity) 161 { 162 array[usedLength] = range.front; 163 usedLength++; 164 range.popFront; 165 } 166 return range.empty ? 0 : -1; 167 } 168 } 169 170 /// Ditto 171 long pushBack(Args...)(auto ref Args args) 172 if (args.length > 1) 173 { 174 return pushBack(only(args)); 175 } 176 alias push = pushBack; 177 178 /// Push back elements on `list ~= args` 179 auto opOpAssign(string op : "~", Args...)(auto ref Args args) 180 { 181 return pushBack(args); 182 } 183 184 /++ 185 + Pop the last element from List. 186 + If element type has elaborate destructor, popped slot is reinitialized to `T.init`. 187 +/ 188 void popBack() 189 { 190 if (!empty) 191 { 192 usedLength--; 193 static if (hasElaborateDestructor!T) 194 { 195 array[usedLength] = T.init; 196 } 197 } 198 } 199 /++ 200 + Pop `count` elements from the back of the List. 201 + If element type has elaborate destructor, popped slots are reinitialized to `T.init`. 202 +/ 203 void popBack(const size_t count) 204 { 205 auto minCount = min(count, usedLength); 206 static if (hasElaborateDestructor!T) 207 { 208 usedSlice[$ - minCount .. $] = T.init; 209 } 210 usedLength -= minCount; 211 } 212 alias pop = popBack; 213 214 /++ 215 + Clear all elements of List. 216 + If element type has elaborate destructor, popped slots are reinitialized to `T.init`. 217 +/ 218 void clear() 219 out { assert(length == 0); assert(empty); } 220 do 221 { 222 static if (hasElaborateDestructor!T) 223 { 224 usedSlice[] = T.init; 225 } 226 usedLength = 0; 227 } 228 } 229 230 /// Construct List from static array, inferring element type. 231 List!(T, N) list(T, uint N)(const auto ref T[N] values) 232 { 233 return typeof(return)(values[]); 234 } 235 /// 236 unittest 237 { 238 int[3] values = [1, 2, 3]; 239 assert(list(values) == values); 240 } 241 242 /// Construct List from slice. 243 List!(T) list(T)(auto ref T[] array) 244 { 245 typeof(return) l = array; 246 l.usedLength = array.length; 247 return l; 248 } 249 /// 250 unittest 251 { 252 auto v = list([1, 2, 3]); 253 assert(v == [1, 2, 3]); 254 } 255 256 /// Construct List from elements, specifying element type. 257 List!(U, Args.length) list(U, Args...)(const auto ref Args args) 258 { 259 return typeof(return)(args); 260 } 261 /// 262 unittest 263 { 264 const auto v = list!double(1, 2, 3); 265 assert(is(Unqual!(typeof(v)) == List!(double, 3))); 266 assert(v == [1.0, 2.0, 3.0]); 267 } 268 269 /// Construct List from elements, inferring element type. 270 auto list(Args...)(const auto ref Args args) 271 if (!is(CommonType!Args == void)) 272 { 273 return .list!(CommonType!Args)(args); 274 } 275 /// 276 unittest 277 { 278 const auto v = list(1f, 2, 3); 279 assert(is(Unqual!(typeof(v)) == List!(float, 3))); 280 assert(v == [1f, 2f, 3f]); 281 } 282 283 /// Construct List with the specified length and type from Input Range. 284 List!(U, N) list(U, size_t N, T)(scope T range) 285 if (isInputRange!T) 286 { 287 return typeof(return)(range.take(N)); 288 } 289 /// 290 unittest 291 { 292 const auto l = list!(double, 4)(repeat(42)); 293 assert(l == [42.0, 42.0, 42.0, 42.0]); 294 } 295 296 /// Construct List with the specified length from Input Range, inferring element type. 297 auto list(size_t N, T)(scope T range) 298 if (isInputRange!T) 299 { 300 return .list!(ElementType!T, N)(range); 301 } 302 /// 303 unittest 304 { 305 const auto l = list!4(repeat(42)); 306 assert(l == [42, 42, 42, 42]); 307 } 308 309 /// Construct List from Input Range at compile time. 310 auto list(alias values)() 311 if (isInputRange!(typeof(values))) 312 { 313 enum l = .list!(size_t(values.length))(values); 314 return l; 315 } 316 /// 317 unittest 318 { 319 auto l = list!(iota(4).map!"a * a"); 320 assert(l == [0, 1, 4, 9]); 321 l = list!(repeat(42).take(4)); 322 assert(l == [42, 42, 42, 42]); 323 } 324 325 unittest 326 { 327 List!(int, 8) l; 328 assert(l.length == 0); 329 assert(l[] == null); 330 assert(l == null); 331 332 assert(l.pushBack(5) == 0); 333 assert(l.length == 1); 334 assert(l[0] == 5); 335 assert(l == [5]); 336 assert(l[0 .. $] == [5]); 337 338 l[0] = 10; 339 assert(l.length == 1); 340 assert(l[0] == 10); 341 assert(l == [10]); 342 assert(l[0 .. $] == [10]); 343 344 assert(l.pushBack(iota(4)) == 0); 345 assert(l.length == 5); 346 assert(l == [10, 0, 1, 2, 3]); 347 assert(l[].sum == 16); 348 assert(l[$-2 .. $] == [2, 3]); 349 350 int five() { return 5; } 351 assert(l.pushBack(generate!five) < 0); 352 assert(l.full); 353 assert(l == [10, 0, 1, 2, 3, 5, 5, 5]); 354 355 assert(l.pushBack(500) > 0); 356 assert(l.back == 5); 357 l.popBack(); 358 assert(l.length == l.capacity - 1); 359 assert((l ~= 500) == 0); 360 assert(l.full); 361 assert(l.back == 500); 362 363 l.popBack(2); 364 assert(l.length == l.capacity - 2); 365 l.clear(); 366 assert(l.empty); 367 l.popBack(42); 368 } 369 370 unittest 371 { 372 int[10] array = 42; 373 auto l = List!(int)(array[]); 374 assert(l.capacity == 10); 375 assert(l.length == 0); 376 assert(l[] == null); 377 assert(l == null); 378 379 auto l2 = List!(int)(array[], 0, 1); 380 assert(l2.capacity == 10); 381 assert(l2.length == 2); 382 assert(l2[] == [0, 1]); 383 assert(l2 == [0, 1]); 384 } 385 386 unittest 387 { 388 import std.experimental.allocator.mallocator : Mallocator; 389 void[] buffer = Mallocator.instance.allocate(8 * int.sizeof); 390 391 auto l = List!(int)(buffer); 392 assert(l.capacity == 8); 393 394 Mallocator.instance.deallocate(buffer); 395 } 396 397 unittest 398 { 399 import betterclist : List; 400 401 // Lists may be backed by static arrays by passing the capacity to template 402 List!(int, 16) list; 403 assert(list.capacity == 16); 404 405 // Lists track their own element usage 406 assert(list.length == 0); 407 assert(list.empty); 408 409 list.pushBack(0); // pushBack inserts an element in the end of the used slice 410 assert(list.length == 1); 411 assert(list[0] == 0); // opIndex(size_t) indexes used elements slice, `list[1]` is out of bounds here 412 list[0] = 1; // assigning to index works, if your List is mutable 413 414 list.push(2, 3); // push is an alias to pushBack. Several elements may be pushed at once 415 assert(list[] == [1, 2, 3]); // opIndex() returns the used slice 416 assert(list.availableCapacity == 13); 417 418 list ~= 4; // operator ~= also calls pushBack 419 assert(list == [1, 2, 3, 4]); // Lists are aliased to the used slice by `alias this` 420 assert(list[2 .. $] == [3, 4]); // this means stuff like opDollar, slicing and length just works 421 // so does foreach 422 import std.stdio : writeln; 423 foreach (value; list) 424 { 425 writeln(value); 426 } 427 // so does importing common range stuff 428 import std.range; 429 assert(list.front == 1); 430 assert(list.back == 4); 431 432 list.popBack(); // pops the last value. If element type has elaborate detructors, reinitializes element to it's `.init` value 433 assert(list == [1, 2, 3]); 434 list.popBack(2); // pops the last N values. The same caveats apply 435 assert(list == [1]); 436 list.pop(); // pop is an alias to popBack 437 assert(list == null); 438 list.pop(42); // popping more items than there are is safe 439 list.clear(); // pops all elements from List 440 assert(list.empty); 441 442 list.push(iota(16)); // ranges can be pushed at once 443 assert(list.full); 444 auto result = list.push(1, 2); // Trying to push more items than capacity permits is an error, but no exception is thrown... 445 assert(result == 2); // ...rather pushBack returns the number of items that were not inserted 446 list.clear(); 447 result = list.push(1, 1, 2, 3, 5); 448 assert(result == 0); // if all items were inserted, pushBack returns 0 449 result = list.pushBack(repeat(42)); // if range has no known length and not all elements were inserted... 450 assert(result < 0); // ...pushBack returns a negative value 451 452 // Lists can also be backed by a slice 453 import core.stdc.stdlib : malloc, free; 454 alias IntList = List!int; 455 456 enum bufferSize = 8 * int.sizeof; 457 void* buffer = malloc(bufferSize); 458 459 // Construction using void[] 460 auto sliceList = IntList(buffer[0 .. bufferSize]); 461 assert(sliceList.capacity == 8); 462 463 // Construction using element type slice 464 sliceList = IntList(cast(int[]) buffer[0 .. bufferSize]); 465 assert(sliceList.capacity == 8); 466 467 // Construction using void pointer and explicit buffer size (be careful!) 468 sliceList = IntList(buffer, bufferSize); 469 assert(sliceList.capacity == 8); 470 471 // Construction using element type pointer and explicit capacity (be careful!) 472 sliceList = IntList(cast(int*) buffer, 8); 473 assert(sliceList.capacity == 8); 474 475 // Lists backed by slices do not manage their own memory (TODO: memory mamaged (noGC, but destructor/auto resize) List type) 476 free(buffer); 477 }