├── .editorconfig ├── .github └── workflows │ └── CI.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── doc-src └── index.ddoc ├── dub.sdl ├── meson.build ├── src └── containers │ ├── cyclicbuffer.d │ ├── dynamicarray.d │ ├── hashmap.d │ ├── hashset.d │ ├── immutablehashset.d │ ├── internal │ ├── backwards.d │ ├── element_type.d │ ├── hash.d │ ├── mixins.d │ ├── node.d │ └── storage_type.d │ ├── openhashset.d │ ├── package.d │ ├── simdset.d │ ├── slist.d │ ├── treemap.d │ ├── ttree.d │ └── unrolledlist.d ├── test ├── compile_test.d ├── external_allocator_test.d ├── hashmap_gc_test.d ├── looptest.d └── makefile └── times.png /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | insert_final_newline = true 4 | indent_size = 4 5 | tab_width = 4 6 | trim_trailing_whitespace = true 7 | 8 | [*.d] 9 | indent_style = tab 10 | 11 | [*.{json,sdl}] 12 | 13 | [*.yml] 14 | indent_style = space 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | Test: 10 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: 16 | - ubuntu-20.04 17 | - windows-2019 18 | - macos-10.15 19 | d: 20 | - "ldc-1.27.1" 21 | - "dmd-2.097.2" 22 | meson: 23 | - 0.59.1 24 | ninja: 25 | - 1.10.2 26 | steps: 27 | - uses: actions/checkout@v2 28 | with: 29 | submodules: recursive 30 | 31 | # Cache 32 | - name: Cache 33 | uses: actions/cache@v2 34 | with: 35 | path: | 36 | ~/.dub 37 | ~/AppData/Local/dub 38 | ~/.cache/pip 39 | ~/AppData/Local/pip/cache 40 | key: "containers-cache-OS:${{ matrix.os }}-D:${{ matrix.d }}-${{ matrix.meson }}-${{ matrix.ninja }}-deps:${{ hashFiles('./meson.build') }}-${{ hashFiles('./dub.sdl') }}" 41 | 42 | # Setup compilers and tools 43 | 44 | - name: Setup D 45 | uses: dlang-community/setup-dlang@v1 46 | with: 47 | compiler: ${{ matrix.d }} 48 | 49 | - uses: actions/setup-python@v2 50 | with: 51 | python-version: '3.x' 52 | 53 | - name: Setup Meson 54 | run: pip install meson==${{ matrix.meson }} 55 | 56 | - name: Setup Ninja 57 | uses: aminya/install-cmake@new-versions-and-arch 58 | with: 59 | cmake: false 60 | ninja: ${{ matrix.ninja }} 61 | 62 | # Build and Test 63 | 64 | - name: Build documentation 65 | run: | 66 | dub build --build=ddox 67 | dub build --build=docs 68 | 69 | - name: Build 70 | run: | 71 | meson setup ./build 72 | meson compile -C ./build 73 | 74 | - name: Install gcc-multilib 75 | if: contains(matrix.os, 'ubuntu') 76 | run: sudo apt-get install -y gcc-multilib 77 | 78 | - name: Make Test 79 | run: | 80 | make -B -C test/ 81 | 82 | # TODO it fails to run the UnrolledList test on ldc 83 | - name: Dub Test 84 | run: | 85 | dub test 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.a 3 | *.lst 4 | *.s 5 | test/tests 6 | test/tests_32 7 | test/looptest 8 | perf.data 9 | doc/ 10 | .dub/ 11 | dub.selections.json 12 | test/compile_test 13 | test/compile_test_32 14 | test/external_allocator_test 15 | test/external_allocator_test_32 16 | test/openhashset 17 | test/hashmap_gc_test 18 | .gdb_history 19 | __test__*__ 20 | *.exe 21 | .directory 22 | *.userprefs 23 | .vscode 24 | build/ 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Containers [![CI status](https://travis-ci.org/dlang-community/containers.svg?branch=master)](https://travis-ci.org/dlang-community/containers/) 2 | ========== 3 | 4 | Containers backed by std.experimental.allocator 5 | 6 | # Documentation 7 | Documentation is available at http://dlang-community.github.io/containers/index.html 8 | 9 | # Example 10 | 11 | ```d 12 | /+dub.sdl: 13 | dependency "emsi_containers" version="~>0.6" 14 | +/ 15 | import std.stdio; 16 | void main(string[] args) 17 | { 18 | import containers; 19 | DynamicArray!int arr; 20 | arr ~= 1; 21 | foreach (e; arr) 22 | e.writeln; 23 | } 24 | ``` 25 | 26 | [![Open on run.dlang.io](https://img.shields.io/badge/run.dlang.io-open-blue.svg)](https://run.dlang.io/is/8GYopZ) 27 | 28 | # Insertion Speed Benchmark 29 | ![Benchmark](times.png) 30 | 31 | Measurements taken on a `Intel(R) Core(TM) i5-4250U CPU @ 1.30GHz` with 8GB of memory. 32 | Compiled with dmd-2.068.0 using `-O -release -inline` flags. 33 | 34 | ### Code 35 | 36 | ```d 37 | import containers.ttree; 38 | import std.container.rbtree; 39 | import containers.slist; 40 | import std.container.slist; 41 | import containers.unrolledlist; 42 | import std.experimental.allocator; 43 | import std.experimental.allocator.building_blocks.allocator_list; 44 | import std.experimental.allocator.building_blocks.region; 45 | import std.experimental.allocator.mallocator; 46 | import std.datetime; 47 | import std.stdio; 48 | 49 | // For fun: change this number and watch the effect it has on the execution time 50 | alias Allocator = AllocatorList!(a => Region!Mallocator(1024 * 16), Mallocator); 51 | 52 | enum NUMBER_OF_ITEMS = 500_000; 53 | 54 | void testEMSIContainer(alias Container, string ContainerName)() 55 | { 56 | Allocator allocator; 57 | auto c = Container!(int, typeof(&allocator))(&allocator); 58 | StopWatch sw = StopWatch(AutoStart.yes); 59 | foreach (i; 0 .. NUMBER_OF_ITEMS) 60 | c.insert(i); 61 | sw.stop(); 62 | writeln("Inserts for ", ContainerName, " finished in ", 63 | sw.peek().to!("msecs", float), " milliseconds."); 64 | } 65 | 66 | void testPhobosContainer(alias Container, string ContainerName)() 67 | { 68 | static if (is(Container!int == class)) 69 | auto c = new Container!int(); 70 | else 71 | Container!int c; 72 | StopWatch sw = StopWatch(AutoStart.yes); 73 | foreach (i; 0 .. NUMBER_OF_ITEMS) 74 | c.insert(i); 75 | sw.stop(); 76 | writeln("Inserts for ", ContainerName, " finished in ", 77 | sw.peek().to!("msecs", float), " milliseconds."); 78 | } 79 | 80 | void main() 81 | { 82 | testEMSIContainer!(TTree, "TTree")(); 83 | testPhobosContainer!(RedBlackTree, "RedBlackTree")(); 84 | 85 | testPhobosContainer!(std.container.slist.SList, "Phobos SList")(); 86 | testEMSIContainer!(containers.slist.SList, "EMSI SList")(); 87 | 88 | testEMSIContainer!(UnrolledList, "UnrolledList")(); 89 | } 90 | ``` 91 | -------------------------------------------------------------------------------- /doc-src/index.ddoc: -------------------------------------------------------------------------------- 1 | This page contains documentation for the EMSI containers library. 2 | 3 | Use the package index on the left to explore the documentation. 4 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "emsi_containers" 2 | description "Containers that use Phobos' experimental allocators" 3 | homepage "https://github.com/dlang-community/containers" 4 | authors "EMSI" "DLang Community" 5 | license "BSL-1.0" 6 | targetPath "build" 7 | buildType "unittest" { 8 | versions "emsi_containers_unittest" 9 | buildOptions "debugMode" "debugInfo" "unittests" 10 | } 11 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('dcontainers', 'd', 2 | meson_version: '>=0.52', 3 | license: 'BSL-1.0', 4 | version: '0.8.0' 5 | ) 6 | 7 | project_soversion = '0' 8 | 9 | # 10 | # Sources 11 | # 12 | dcontainers_src = [ 13 | 'src/containers/cyclicbuffer.d', 14 | 'src/containers/dynamicarray.d', 15 | 'src/containers/hashmap.d', 16 | 'src/containers/hashset.d', 17 | 'src/containers/immutablehashset.d', 18 | 'src/containers/internal/backwards.d', 19 | 'src/containers/internal/element_type.d', 20 | 'src/containers/internal/hash.d', 21 | 'src/containers/internal/mixins.d', 22 | 'src/containers/internal/node.d', 23 | 'src/containers/internal/storage_type.d', 24 | 'src/containers/openhashset.d', 25 | 'src/containers/package.d', 26 | 'src/containers/simdset.d', 27 | 'src/containers/slist.d', 28 | 'src/containers/treemap.d', 29 | 'src/containers/ttree.d', 30 | 'src/containers/unrolledlist.d' 31 | ] 32 | 33 | dc = meson.get_compiler('d') 34 | src_dir = include_directories('src/') 35 | 36 | # 37 | # Targets 38 | # 39 | dcontainers_lib = static_library('dcontainers', 40 | [dcontainers_src], 41 | include_directories: [src_dir], 42 | install: true, 43 | ) 44 | 45 | pkgc = import('pkgconfig') 46 | pkgc.generate(name: 'dcontainers', 47 | libraries: [dcontainers_lib], 48 | subdirs: 'd/containers', 49 | version: meson.project_version(), 50 | description: 'Containers backed by std.experimental.allocator.' 51 | ) 52 | 53 | # for use by others which embed this as subproject 54 | dcontainers_dep = declare_dependency( 55 | link_with: [dcontainers_lib], 56 | include_directories: [src_dir], 57 | ) 58 | 59 | # 60 | # Tests 61 | # 62 | if dc.get_id() == 'gcc' 63 | test_link_args = [] 64 | test_main_file = configure_file(capture: true, 65 | command: ['echo', 'int main(string[] args) { return 0; }'], 66 | output: 'unittest_main.d' 67 | ) 68 | else 69 | test_link_args = ['-main'] 70 | test_main_file = [] 71 | endif 72 | 73 | dcontainers_test_exe = executable('test_dcontainers', 74 | [dcontainers_src, 75 | 'test/compile_test.d', 76 | 'test/external_allocator_test.d', 77 | test_main_file], 78 | include_directories: [src_dir], 79 | d_unittest: true, 80 | link_args: test_link_args 81 | ) 82 | test('test_dcontainers', dcontainers_test_exe) 83 | 84 | # the looptest is a manual test, so we don't run it and only compile it 85 | looptest_test_exe = executable('test_looptest', 86 | ['test/looptest.d'], 87 | dependencies: [dcontainers_dep] 88 | ) 89 | 90 | # 91 | # Install 92 | # 93 | install_subdir('src/containers/', install_dir: 'include/d/containers/') 94 | -------------------------------------------------------------------------------- /src/containers/cyclicbuffer.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Cyclic Buffer 3 | * Copyright: © 2016 Economic Modeling Specialists, Intl. 4 | * Authors: Nickolay Bukreyev 5 | * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 | */ 7 | 8 | module containers.cyclicbuffer; 9 | 10 | private import core.exception : onRangeError; 11 | private import std.experimental.allocator.mallocator : Mallocator; 12 | private import std.range.primitives : empty, front, back, popFront, popBack; 13 | private import containers.internal.node : shouldAddGCRange; 14 | 15 | /** 16 | * Array that provides constant time (amortized) appending and popping 17 | * at either end, as well as random access to the elements. 18 | * 19 | * Params: 20 | * T = the array element type 21 | * Allocator = the allocator to use. Defaults to `Mallocator`. 22 | * supportGC = true if the container should support holding references to GC-allocated memory. 23 | */ 24 | struct CyclicBuffer(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T) 25 | { 26 | @disable this(this); 27 | 28 | private import std.conv : emplace; 29 | private import std.experimental.allocator.common : stateSize; 30 | private import std.traits : isImplicitlyConvertible, hasElaborateDestructor; 31 | 32 | static if (stateSize!Allocator != 0) 33 | { 34 | /// No default construction if an allocator must be provided. 35 | @disable this(); 36 | 37 | /** 38 | * Use the given `allocator` for allocations. 39 | */ 40 | this(Allocator allocator) nothrow pure @safe @nogc 41 | in 42 | { 43 | static if (is(typeof(allocator is null))) 44 | assert(allocator !is null, "Allocator must not be null"); 45 | } 46 | do 47 | { 48 | this.allocator = allocator; 49 | } 50 | } 51 | 52 | ~this() 53 | { 54 | clear(); 55 | static if (useGC) 56 | { 57 | import core.memory : GC; 58 | GC.removeRange(storage.ptr); 59 | } 60 | allocator.deallocate(storage); 61 | } 62 | 63 | /** 64 | * Removes all contents from the buffer. 65 | */ 66 | void clear() 67 | { 68 | if (!empty) 69 | { 70 | static if (hasElaborateDestructor!T) 71 | { 72 | if (start <= end) 73 | foreach (ref item; storage[start .. end + 1]) 74 | .destroy(item); 75 | else 76 | { 77 | foreach (ref item; storage[start .. $]) 78 | .destroy(item); 79 | foreach (ref item; storage[0 .. end + 1]) 80 | .destroy(item); 81 | } 82 | } 83 | start = (end + 1) % capacity; 84 | _length = 0; 85 | } 86 | } 87 | 88 | /** 89 | * Ensures capacity is at least as large as specified. 90 | */ 91 | size_t reserve(size_t newCapacity) 92 | { 93 | immutable oldCapacity = capacity; 94 | if (newCapacity <= oldCapacity) 95 | return oldCapacity; 96 | auto old = storage; 97 | if (oldCapacity == 0) 98 | storage = cast(typeof(storage)) allocator.allocate(newCapacity * T.sizeof); 99 | else 100 | { 101 | auto a = cast(void[]) old; 102 | allocator.reallocate(a, newCapacity * T.sizeof); 103 | storage = cast(typeof(storage)) a; 104 | } 105 | static if (useGC) 106 | { 107 | import core.memory : GC; 108 | //Add, then remove. Exactly in that order. 109 | GC.addRange(storage.ptr, newCapacity * T.sizeof); 110 | GC.removeRange(old.ptr); 111 | } 112 | if (empty) 113 | end = (start - 1 + capacity) % capacity; 114 | else if (start > end) 115 | { 116 | //The buffer was wrapped around prior to reallocation. 117 | 118 | //`moveEmplaceAll` is only available in 2.069+, so use a low level alternative. 119 | //Even more, we don't have to .init the moved away data, because we don't .destroy it. 120 | import core.stdc.string : memcpy, memmove; 121 | immutable prefix = end + 1; 122 | immutable suffix = oldCapacity - start; 123 | if (prefix <= suffix) 124 | { 125 | //The prefix is being moved right behind of suffix. 126 | immutable space = newCapacity - oldCapacity; 127 | if (space >= prefix) 128 | { 129 | memcpy(storage.ptr + oldCapacity, storage.ptr, prefix * T.sizeof); 130 | end += oldCapacity; 131 | } 132 | else 133 | { 134 | //There is not enough space, so move what we can, 135 | //and shift the rest to the start of the buffer. 136 | memcpy(storage.ptr + oldCapacity, storage.ptr, space * T.sizeof); 137 | end -= space; 138 | memmove(storage.ptr, storage.ptr + space, (end + 1) * T.sizeof); 139 | } 140 | } 141 | else 142 | { 143 | //The suffix is being moved forward, to the end of the buffer. 144 | //Due to the fact that these locations may overlap, use `memmove`. 145 | memmove(storage.ptr + newCapacity - suffix, storage.ptr + start, suffix * T.sizeof); 146 | start = newCapacity - suffix; 147 | } 148 | //Ensure everything is still alright. 149 | if (start <= end) 150 | assert(end + 1 - start == length); 151 | else 152 | assert(end + 1 + (newCapacity - start) == length); 153 | } 154 | return capacity; 155 | } 156 | 157 | /** 158 | * Inserts the given item into the start of the buffer. 159 | */ 160 | void insertFront(U)(U value) if (isImplicitlyConvertible!(U, T)) 161 | { 162 | if (empty) 163 | reserve(4); 164 | else if ((end + 1) % capacity == start) 165 | reserve(capacity >= 65_536 ? capacity + 65_536 : capacity * 2); 166 | start = (start - 1 + capacity) % capacity; 167 | _length++; 168 | emplace(&storage[start], value); 169 | } 170 | 171 | /** 172 | * Inserts the given item into the end of the buffer. 173 | */ 174 | void insertBack(U)(U value) if (isImplicitlyConvertible!(U, T)) 175 | { 176 | if (empty) 177 | reserve(4); 178 | else if ((end + 1) % capacity == start) 179 | reserve(capacity >= 65_536 ? capacity + 65_536 : capacity * 2); 180 | end = (end + 1) % capacity; 181 | _length++; 182 | emplace(&storage[end], value); 183 | } 184 | 185 | /// ditto 186 | alias insert = insertBack; 187 | 188 | /// ditto 189 | alias insertAnywhere = insertBack; 190 | 191 | /// ditto 192 | alias put = insertBack; 193 | 194 | /** 195 | * Removes the item at the start of the buffer. 196 | */ 197 | void removeFront() 198 | { 199 | version (assert) if (empty) onRangeError(); 200 | size_t pos = start; 201 | start = (start + 1) % capacity; 202 | _length--; 203 | static if (hasElaborateDestructor!T) 204 | .destroy(storage[pos]); 205 | } 206 | 207 | /// ditto 208 | alias popFront = removeFront; 209 | 210 | /** 211 | * Removes the item at the end of the buffer. 212 | */ 213 | void removeBack() 214 | { 215 | version (assert) if (empty) onRangeError(); 216 | size_t pos = end; 217 | end = (end - 1 + capacity) % capacity; 218 | _length--; 219 | static if (hasElaborateDestructor!T) 220 | .destroy(storage[pos]); 221 | } 222 | 223 | /// ditto 224 | alias popBack = removeBack; 225 | 226 | /// Accesses to the item at the start of the buffer. 227 | auto ref front(this This)() nothrow pure @property @safe 228 | { 229 | version (assert) if (empty) onRangeError(); 230 | alias ET = ContainerElementType!(This, T, true); 231 | return cast(ET) storage[start]; 232 | } 233 | 234 | /// Accesses to the item at the end of the buffer. 235 | auto ref back(this This)() nothrow pure @property @safe 236 | { 237 | version (assert) if (empty) onRangeError(); 238 | alias ET = ContainerElementType!(This, T, true); 239 | return cast(ET) storage[end]; 240 | } 241 | 242 | /// buffer[i] 243 | auto ref opIndex(this This)(size_t i) nothrow pure @safe 244 | { 245 | version (assert) if (i >= length) onRangeError(); 246 | alias ET = ContainerElementType!(This, T, true); 247 | return cast(ET) storage[(start + i) % $]; 248 | } 249 | 250 | /// buffer[] 251 | Range!This opIndex(this This)() nothrow pure @safe @nogc 252 | { 253 | if (empty) 254 | return typeof(return)(storage[0 .. 0], storage[0 .. 0]); 255 | if (start <= end) 256 | return typeof(return)(storage[start .. end + 1], storage[0 .. 0]); 257 | return typeof(return)(storage[start .. $], storage[0 .. end + 1]); 258 | } 259 | 260 | /// buffer[i .. j] 261 | size_t[2] opSlice(size_t k: 0)(size_t i, size_t j) const nothrow pure @safe @nogc 262 | { 263 | return [i, j]; 264 | } 265 | 266 | /// ditto 267 | Range!This opIndex(this This)(size_t[2] indices) nothrow pure @safe 268 | { 269 | size_t i = indices[0], j = indices[1]; 270 | version (assert) 271 | { 272 | if (i > j) onRangeError(); 273 | if (j > length) onRangeError(); 274 | } 275 | if (i == j) 276 | return typeof(return)(storage[0 .. 0], storage[0 .. 0]); 277 | i = (start + i) % capacity; 278 | j = (start + j) % capacity; 279 | if (i < j) 280 | return typeof(return)(storage[i .. j], storage[0 .. 0]); 281 | return typeof(return)(storage[i .. $], storage[0 .. j]); 282 | } 283 | 284 | static struct Range(ThisT) 285 | { 286 | private 287 | { 288 | static if (is(ThisT == immutable)) 289 | { 290 | alias SliceT = immutable(ContainerStorageType!T)[]; 291 | } 292 | else static if (is(ThisT == const)) 293 | { 294 | alias SliceT = const(ContainerStorageType!T)[]; 295 | } 296 | else 297 | { 298 | alias SliceT = ContainerStorageType!T[]; 299 | } 300 | } 301 | 302 | @disable this(); 303 | 304 | this(SliceT a, SliceT b) nothrow pure @safe @nogc 305 | { 306 | head = a; 307 | tail = b; 308 | } 309 | 310 | This save(this This)() nothrow pure @property @safe @nogc 311 | { 312 | return this; 313 | } 314 | 315 | bool empty() const nothrow pure @property @safe @nogc 316 | { 317 | return head.empty && tail.empty; 318 | } 319 | 320 | size_t length() const nothrow pure @property @safe @nogc 321 | { 322 | return head.length + tail.length; 323 | } 324 | 325 | alias opDollar = length; 326 | 327 | auto ref front(this This)() nothrow pure @property @safe 328 | { 329 | if (!head.empty) 330 | return cast(ET) head.front; 331 | return cast(ET) tail.front; 332 | } 333 | 334 | auto ref back(this This)() nothrow pure @property @safe 335 | { 336 | if (!tail.empty) 337 | return cast(ET) tail.back; 338 | return cast(ET) head.back; 339 | } 340 | 341 | void popFront() nothrow pure @safe 342 | { 343 | if (head.empty) 344 | { 345 | import std.algorithm.mutation : swap; 346 | //Always try to keep `head` non-empty. 347 | swap(head, tail); 348 | } 349 | head.popFront(); 350 | } 351 | 352 | void popBack() nothrow pure @safe 353 | { 354 | if (!tail.empty) 355 | tail.popBack(); 356 | else 357 | head.popBack(); 358 | } 359 | 360 | /// range[i] 361 | auto ref opIndex(this This)(size_t i) nothrow pure @safe 362 | { 363 | return cast(ET) (i < head.length ? head[i] : tail[i - head.length]); 364 | } 365 | 366 | /// range[] 367 | This opIndex(this This)() nothrow pure @safe @nogc 368 | { 369 | return this.save; 370 | } 371 | 372 | /// range[i .. j] 373 | size_t[2] opSlice(size_t k: 0)(size_t i, size_t j) const nothrow pure @safe @nogc 374 | { 375 | return [i, j]; 376 | } 377 | 378 | /// ditto 379 | This opIndex(this This)(size_t[2] indices) nothrow pure @safe 380 | { 381 | size_t i = indices[0], j = indices[1]; 382 | version (assert) 383 | { 384 | if (i > j) onRangeError(); 385 | if (j > length) onRangeError(); 386 | } 387 | if (i >= head.length) 388 | return typeof(return)(tail[i - head.length .. j - head.length], tail[0 .. 0]); 389 | if (j <= head.length) 390 | return typeof(return)(head[i .. j], head[0 .. 0]); 391 | return typeof(return)(head[i .. $], tail[0 .. j - head.length]); 392 | } 393 | 394 | /// range[...]++ 395 | auto ref opUnary(string op)() nothrow pure @safe @nogc 396 | if (op == "++" || op == "--") 397 | { 398 | mixin(op ~ "head[];"); 399 | mixin(op ~ "tail[];"); 400 | return this; 401 | } 402 | 403 | /// range[...] = value 404 | auto ref opAssign(U)(const auto ref U value) nothrow pure @safe @nogc 405 | { 406 | head[] = value; 407 | tail[] = value; 408 | return this; 409 | } 410 | 411 | /// range[...] += value 412 | auto ref opOpAssign(string op, U)(const auto ref U value) nothrow pure @safe @nogc 413 | { 414 | mixin("head[] " ~ op ~ "= value;"); 415 | mixin("tail[] " ~ op ~ "= value;"); 416 | return this; 417 | } 418 | 419 | private: 420 | 421 | alias ET = ContainerElementType!(ThisT, T); 422 | 423 | SliceT head, tail; 424 | } 425 | 426 | /// Returns: the number of items in the buffer. 427 | size_t length() const nothrow pure @property @safe @nogc { return _length; } 428 | 429 | /// ditto 430 | alias opDollar = length; 431 | 432 | /// Returns: maximal number of items the buffer can hold without reallocation. 433 | size_t capacity() const nothrow pure @property @safe @nogc { return storage.length; } 434 | 435 | /// Returns: whether or not the CyclicBuffer is empty. 436 | bool empty() const nothrow pure @property @safe @nogc { return length == 0; } 437 | 438 | private: 439 | 440 | import containers.internal.storage_type : ContainerStorageType; 441 | import containers.internal.element_type : ContainerElementType; 442 | import containers.internal.mixins : AllocatorState; 443 | 444 | enum bool useGC = supportGC && shouldAddGCRange!T; 445 | mixin AllocatorState!Allocator; 446 | ContainerStorageType!T[] storage; 447 | size_t start, end, _length; 448 | } 449 | 450 | version(emsi_containers_unittest) private 451 | { 452 | import std.algorithm.comparison : equal; 453 | import std.experimental.allocator.gc_allocator : GCAllocator; 454 | import std.experimental.allocator.building_blocks.free_list : FreeList; 455 | import std.range : iota, lockstep, StoppingPolicy; 456 | 457 | struct S 458 | { 459 | int* a; 460 | 461 | ~this() 462 | { 463 | (*a)++; 464 | } 465 | } 466 | 467 | class C 468 | { 469 | int* a; 470 | 471 | this(int* a) 472 | { 473 | this.a = a; 474 | } 475 | 476 | ~this() 477 | { 478 | (*a)++; 479 | } 480 | } 481 | } 482 | 483 | version(emsi_containers_unittest) unittest 484 | { 485 | static void test(int size) 486 | { 487 | { 488 | CyclicBuffer!int b; 489 | assert(b.empty); 490 | foreach (i; 0 .. size) 491 | { 492 | assert(b.length == i); 493 | b.insertBack(i); 494 | assert(b.back == i); 495 | } 496 | assert(b.length == size); 497 | foreach (i; 0 .. size) 498 | { 499 | assert(b.length == size - i); 500 | assert(b.front == i); 501 | b.removeFront(); 502 | } 503 | assert(b.empty); 504 | } 505 | { 506 | CyclicBuffer!int b; 507 | foreach (i; 0 .. size) 508 | { 509 | assert(b.length == i); 510 | b.insertFront(i); 511 | assert(b.front == i); 512 | } 513 | assert(b.length == size); 514 | foreach (i; 0 .. size) 515 | { 516 | assert(b.length == size - i); 517 | assert(b.back == i); 518 | b.removeBack(); 519 | } 520 | assert(b.empty); 521 | } 522 | } 523 | 524 | foreach (size; [1, 2, 3, 4, 5, 7, 8, 9, 512, 520, 0x10000, 0x10001, 0x20000]) 525 | test(size); 526 | } 527 | 528 | version(emsi_containers_unittest) unittest 529 | { 530 | static void test(int prefix, int suffix, int newSize) 531 | { 532 | CyclicBuffer!int b; 533 | foreach_reverse (i; 0 .. suffix) 534 | b.insertFront(i); 535 | foreach (i; suffix .. suffix + prefix) 536 | b.insertBack(i); 537 | assert(b.length == prefix + suffix); 538 | b.reserve(newSize); 539 | assert(b.length == prefix + suffix); 540 | assert(equal(b[], iota(prefix + suffix))); 541 | } 542 | 543 | immutable prefixes = [2, 3, 3, 4, 4]; 544 | immutable suffixes = [3, 2, 4, 3, 4]; 545 | immutable sizes = [16, 16, 9, 9, 9]; 546 | 547 | foreach (a, b, c; lockstep(prefixes, suffixes, sizes, StoppingPolicy.requireSameLength)) 548 | test(a, b, c); 549 | } 550 | 551 | version(emsi_containers_unittest) unittest 552 | { 553 | int* a = new int; 554 | { 555 | CyclicBuffer!S b; 556 | { 557 | S s = { a }; 558 | foreach (i; 0 .. 5) 559 | b.insertBack(s); 560 | assert(*a == 5); 561 | foreach (i; 0 .. 5) 562 | b.insertBack(S(a)); 563 | assert(*a == 10); 564 | foreach (i; 0 .. 5) 565 | { 566 | b.removeBack(); 567 | b.removeFront(); 568 | } 569 | assert(*a == 20); 570 | } 571 | assert(*a == 21); 572 | } 573 | assert(*a == 21); 574 | } 575 | 576 | version(emsi_containers_unittest) unittest 577 | { 578 | int* a = new int; 579 | CyclicBuffer!C b; 580 | { 581 | C c = new C(a); 582 | foreach (i; 0 .. 10) 583 | b.insertBack(c); 584 | assert(*a == 0); 585 | foreach (i; 0 .. 5) 586 | { 587 | b.removeBack(); 588 | b.removeFront(); 589 | } 590 | foreach (i; 0 .. b.capacity) 591 | b.insertFront(null); 592 | assert(*a == 0); 593 | } 594 | string s = ""; 595 | foreach (i; 0 .. 1_000) 596 | s = s ~ 'a'; 597 | s = ""; 598 | import core.memory : GC; 599 | GC.collect(); 600 | assert(*a == 0 || *a == 1); 601 | } 602 | 603 | version(emsi_containers_unittest) unittest 604 | { 605 | CyclicBuffer!int b; 606 | b.insertFront(10); 607 | assert(b[0] == 10); 608 | b.insertFront(20); 609 | assert(b[0] == 20); 610 | assert(b[1] == 10); 611 | b.insertFront(30); 612 | assert(b[0] == 30); 613 | assert(b[1] == 20); 614 | assert(b[2] == 10); 615 | b.insertBack(5); 616 | assert(b[0] == 30); 617 | assert(b[1] == 20); 618 | assert(b[2] == 10); 619 | assert(b[3] == 5); 620 | b.back = 7; 621 | assert(b[3] == 7); 622 | } 623 | 624 | version(emsi_containers_unittest) unittest 625 | { 626 | import std.range : isInputRange, isForwardRange, isBidirectionalRange, isRandomAccessRange; 627 | CyclicBuffer!int b; 628 | static assert(isInputRange!(typeof(b[]))); 629 | static assert(isForwardRange!(typeof(b[]))); 630 | static assert(isBidirectionalRange!(typeof(b[]))); 631 | static assert(isRandomAccessRange!(typeof(b[]))); 632 | } 633 | 634 | version(emsi_containers_unittest) unittest 635 | { 636 | CyclicBuffer!int b; 637 | assert(b[].empty); 638 | } 639 | 640 | version(emsi_containers_unittest) unittest 641 | { 642 | FreeList!(Mallocator, 0, 64) alloc; 643 | FreeList!(GCAllocator, 0, 64) alloc2; 644 | auto b = CyclicBuffer!(int, typeof(&alloc))(&alloc); 645 | auto b2 = CyclicBuffer!(int, typeof(&alloc2))(&alloc2); 646 | auto b3 = CyclicBuffer!(int, GCAllocator)(); 647 | } 648 | 649 | version(emsi_containers_unittest) unittest 650 | { 651 | static void testConst(const ref CyclicBuffer!int b, int x) 652 | { 653 | assert(b[0] == x); 654 | assert(b.front == x); 655 | static assert(!__traits(compiles, { ++b[0]; } )); 656 | assert(equal(b[], [x])); 657 | } 658 | 659 | CyclicBuffer!int b; 660 | b.insertFront(0); 661 | assert(b.front == 0); 662 | b.front++; 663 | assert(b[0] == 1); 664 | b[0]++; 665 | ++b[0]; 666 | assert(b.front == 3); 667 | assert(!b.empty); 668 | b[0] *= 2; 669 | assert(b[0] == 6); 670 | testConst(b, 6); 671 | b[]++; 672 | assert(equal(b[], [7])); 673 | b[0] = 5; 674 | assert(b[0] == 5); 675 | assert(b.front == 5); 676 | testConst(b, 5); 677 | assert(b[][0] == 5); 678 | } 679 | 680 | version(emsi_containers_unittest) unittest 681 | { 682 | int* a = new int; 683 | { 684 | CyclicBuffer!S b; 685 | foreach (i; 0 .. 5) 686 | b.insertBack(S(a)); 687 | assert(*a == 5); 688 | } 689 | assert(*a == 10); 690 | *a = 0; 691 | { 692 | CyclicBuffer!S b; 693 | foreach (i; 0 .. 4) 694 | b.insertBack(S(a)); 695 | assert(*a == 4); 696 | b.removeFront(); 697 | assert(*a == 5); 698 | b.insertBack(S(a)); 699 | assert(*a == 6); 700 | } 701 | assert(*a == 10); 702 | } 703 | 704 | version(emsi_containers_unittest) unittest 705 | { 706 | CyclicBuffer!int b; 707 | foreach (i; 0 .. 4) 708 | b.insertBack(i); 709 | b.removeFront(); 710 | b.removeFront(); 711 | b.insertBack(4); 712 | b.insertBack(5); 713 | assert(equal(b[], [2, 3, 4, 5])); 714 | b.reserve(5); 715 | assert(equal(b[], [2, 3, 4, 5])); 716 | } 717 | 718 | version(emsi_containers_unittest) unittest 719 | { 720 | CyclicBuffer!int b; 721 | foreach (i; 0 .. 4) 722 | b.insertBack(i); 723 | b.removeFront(); 724 | b.removeFront(); 725 | b.removeFront(); 726 | b.insertBack(4); 727 | b.insertBack(5); 728 | b.insertBack(6); 729 | assert(equal(b[], [3, 4, 5, 6])); 730 | b.reserve(5); 731 | assert(equal(b[], [3, 4, 5, 6])); 732 | } 733 | 734 | version(emsi_containers_unittest) unittest 735 | { 736 | static void test(ref CyclicBuffer!int b) 737 | { 738 | assert(equal(b[], [4, 5, 6, 7, 8, 9, 10, 11])); 739 | assert(b[3 .. 3].empty); 740 | auto slice = b[1 .. 6]; 741 | assert(equal(slice, [5, 6, 7, 8, 9])); 742 | slice[3 .. 5] = 0; 743 | assert(equal(b[], [4, 5, 6, 7, 0, 0, 10, 11])); 744 | slice[0 .. 2] += 1; 745 | assert(equal(b[], [4, 6, 7, 7, 0, 0, 10, 11])); 746 | slice[0 .. 2]--; 747 | assert(equal(b[], [4, 5, 6, 7, 0, 0, 10, 11])); 748 | auto copy = slice.save; 749 | assert(equal(slice, copy)); 750 | assert(equal(slice, copy[])); 751 | assert(slice.back == 0); 752 | slice.popBack(); 753 | assert(equal(slice, [5, 6, 7, 0])); 754 | assert(slice.back == 0); 755 | slice.popBack(); 756 | assert(equal(slice, [5, 6, 7])); 757 | assert(slice.back == 7); 758 | slice.popBack(); 759 | assert(equal(slice, [5, 6])); 760 | assert(equal(copy, [5, 6, 7, 0, 0])); 761 | slice[1] = 10; 762 | assert(-copy[1] == -10); 763 | copy[1] *= 2; 764 | assert(slice[1] == 20); 765 | assert(b[2] == 20); 766 | auto copy2 = copy[0 .. $]; 767 | assert(equal(copy, copy2)); 768 | } 769 | 770 | { 771 | CyclicBuffer!int b; 772 | foreach (i; 4 .. 12) 773 | b.insertBack(i); 774 | test(b); 775 | } 776 | { 777 | CyclicBuffer!int b; 778 | foreach (i; 0 .. 8) 779 | b.insertBack(i); 780 | foreach (i; 0 .. 4) 781 | b.removeFront(); 782 | foreach (i; 8 .. 12) 783 | b.insertBack(i); 784 | test(b); 785 | } 786 | } 787 | 788 | version(emsi_containers_unittest) unittest 789 | { 790 | CyclicBuffer!int b; 791 | foreach (i; 0 .. 10) 792 | b.insertBack(i); 793 | assert(b.capacity >= 10); 794 | b.reserve(12); 795 | assert(b.capacity >= 12); 796 | } 797 | 798 | version(emsi_containers_unittest) unittest 799 | { 800 | CyclicBuffer!int b; 801 | foreach (i; 0 .. 6) 802 | b.insertBack(i); 803 | foreach (i; 6 .. 8) 804 | b.insertFront(i); 805 | assert(equal(b[], [7, 6, 0, 1, 2, 3, 4, 5])); 806 | b.reserve(b.capacity + 1); 807 | assert(equal(b[], [7, 6, 0, 1, 2, 3, 4, 5])); 808 | } 809 | 810 | version(emsi_containers_unittest) unittest 811 | { 812 | static class Foo 813 | { 814 | string name; 815 | } 816 | 817 | CyclicBuffer!Foo b; 818 | } 819 | -------------------------------------------------------------------------------- /src/containers/dynamicarray.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Dynamic Array 3 | * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 | * Authors: Brian Schott 5 | * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 | */ 7 | 8 | module containers.dynamicarray; 9 | 10 | private import core.lifetime : move, moveEmplace, copyEmplace, emplace; 11 | private import std.traits : isCopyable; 12 | private import containers.internal.node : shouldAddGCRange; 13 | private import std.experimental.allocator.mallocator : Mallocator; 14 | 15 | /** 16 | * Array that is able to grow itself when items are appended to it. Uses 17 | * malloc/free/realloc to manage its storage. 18 | * 19 | * Params: 20 | * T = the array element type 21 | * Allocator = the allocator to use. Defaults to `Mallocator`. 22 | * supportGC = true if the container should support holding references to 23 | * GC-allocated memory. 24 | */ 25 | struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T) 26 | { 27 | this(this) @disable; 28 | 29 | private import std.experimental.allocator.common : stateSize; 30 | 31 | static if (is(typeof((T[] a, const T[] b) => a[0 .. b.length] = b[0 .. $]))) 32 | { 33 | /// Either `const(T)` or `T`. 34 | alias AppendT = const(T); 35 | 36 | /// Either `const(typeof(this))` or `typeof(this)`. 37 | alias AppendTypeOfThis = const(typeof(this)); 38 | } 39 | else 40 | { 41 | alias AppendT = T; 42 | alias AppendTypeOfThis = typeof(this); 43 | } 44 | 45 | static if (stateSize!Allocator != 0) 46 | { 47 | /// No default construction if an allocator must be provided. 48 | this() @disable; 49 | 50 | /** 51 | * Use the given `allocator` for allocations. 52 | */ 53 | this(Allocator allocator) 54 | in 55 | { 56 | static if (is(typeof(allocator is null))) 57 | assert(allocator !is null, "Allocator must not be null"); 58 | } 59 | do 60 | { 61 | this.allocator = allocator; 62 | } 63 | } 64 | 65 | ~this() 66 | { 67 | import std.experimental.allocator.mallocator : Mallocator; 68 | import containers.internal.node : shouldAddGCRange; 69 | 70 | if (arr is null) 71 | return; 72 | 73 | static if ((is(T == struct) || is(T == union)) 74 | && __traits(hasMember, T, "__xdtor")) 75 | { 76 | foreach (ref item; arr[0 .. l]) 77 | { 78 | item.__xdtor(); 79 | } 80 | } 81 | static if (useGC) 82 | { 83 | import core.memory : GC; 84 | GC.removeRange(arr.ptr); 85 | } 86 | allocator.deallocate(arr); 87 | } 88 | 89 | /// Slice operator overload 90 | pragma(inline, true) 91 | auto opSlice(this This)() @nogc 92 | { 93 | return opSlice!(This)(0, l); 94 | } 95 | 96 | /// ditto 97 | pragma(inline, true) 98 | auto opSlice(this This)(size_t a, size_t b) @nogc 99 | { 100 | alias ET = ContainerElementType!(This, T); 101 | return cast(ET[]) arr[a .. b]; 102 | } 103 | 104 | /// Index operator overload 105 | pragma(inline, true) 106 | ref auto opIndex(this This)(size_t i) @nogc 107 | { 108 | return opSlice!(This)(i, i + 1)[0]; 109 | } 110 | 111 | /** 112 | * Inserts the given value into the end of the array. 113 | */ 114 | void insertBack(T value) 115 | { 116 | import std.experimental.allocator.mallocator : Mallocator; 117 | import containers.internal.node : shouldAddGCRange; 118 | 119 | if (arr.length == 0) 120 | { 121 | arr = cast(typeof(arr)) allocator.allocate(T.sizeof * 4); 122 | static if (useGC) 123 | { 124 | import core.memory: GC; 125 | GC.addRange(arr.ptr, arr.length * T.sizeof); 126 | } 127 | } 128 | else if (l >= arr.length) 129 | { 130 | immutable size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1; 131 | static if (useGC) 132 | void* oldPtr = arr.ptr; 133 | void[] a = cast(void[]) arr; 134 | import std.experimental.allocator.common : reallocate; 135 | allocator.reallocate(a, c * T.sizeof); 136 | arr = cast(typeof(arr)) a; 137 | static if (useGC) 138 | { 139 | import core.memory: GC; 140 | GC.removeRange(oldPtr); 141 | GC.addRange(arr.ptr, arr.length * T.sizeof); 142 | } 143 | } 144 | moveEmplace(*cast(ContainerStorageType!T*)&value, arr[l++]); 145 | } 146 | 147 | /// ditto 148 | alias insert = insertBack; 149 | 150 | /// ditto 151 | alias insertAnywhere = insertBack; 152 | 153 | /// ditto 154 | alias put = insertBack; 155 | 156 | /** 157 | * ~= operator overload 158 | */ 159 | scope ref typeof(this) opOpAssign(string op)(T value) if (op == "~") 160 | { 161 | insert(value); 162 | return this; 163 | } 164 | 165 | /** 166 | * ~= operator overload for an array of items 167 | */ 168 | scope ref typeof(this) opOpAssign(string op, bool checkForOverlap = true)(AppendT[] rhs) 169 | if (op == "~" && !is(T == AppendT[])) 170 | { 171 | // Disabling checkForOverlap when this function is called from opBinary!"~" 172 | // is not just for efficiency, but to avoid circular function calls that 173 | // would prevent inference of @nogc, etc. 174 | static if (checkForOverlap) 175 | if ((() @trusted => arr.ptr <= rhs.ptr && arr.ptr + arr.length > rhs.ptr)()) 176 | { 177 | // Special case where rhs is a slice of this array. 178 | this = this ~ rhs; 179 | return this; 180 | } 181 | reserve(l + rhs.length); 182 | import std.traits: hasElaborateAssign, hasElaborateDestructor; 183 | static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) 184 | { 185 | foreach (ref value; rhs) 186 | copyEmplace(value, arr[l++]); 187 | } 188 | else 189 | { 190 | arr[l .. l + rhs.length] = rhs[0 .. rhs.length]; 191 | l += rhs.length; 192 | } 193 | return this; 194 | } 195 | 196 | /// ditto 197 | scope ref typeof(this) opOpAssign(string op)(ref AppendTypeOfThis rhs) 198 | if (op == "~") 199 | { 200 | return this ~= rhs.arr[0 .. rhs.l]; 201 | } 202 | 203 | /** 204 | * ~ operator overload 205 | */ 206 | typeof(this) opBinary(string op)(ref AppendTypeOfThis other) if (op == "~") 207 | { 208 | typeof(this) ret; 209 | ret.reserve(l + other.l); 210 | ret.opOpAssign!("~", false)(arr[0 .. l]); 211 | ret.opOpAssign!("~", false)(other.arr[0 .. other.l]); 212 | return ret; 213 | } 214 | 215 | /// ditto 216 | typeof(this) opBinary(string op)(AppendT[] values) if (op == "~") 217 | { 218 | typeof(this) ret; 219 | ret.reserve(l + values.length); 220 | ret.opOpAssign!("~", false)(arr[0 .. l]); 221 | ret.opOpAssign!("~", false)(values); 222 | return ret; 223 | } 224 | 225 | /** 226 | * Ensures sufficient capacity to accommodate `n` elements. 227 | */ 228 | void reserve(size_t n) 229 | { 230 | if (arr.length >= n) 231 | return; 232 | if (arr.ptr is null) 233 | { 234 | size_t c = 4; 235 | if (c < n) 236 | c = n; 237 | arr = cast(typeof(arr)) allocator.allocate(T.sizeof * c); 238 | static if (useGC) 239 | { 240 | import core.memory: GC; 241 | GC.addRange(arr.ptr, arr.length * T.sizeof); 242 | } 243 | } 244 | else 245 | { 246 | size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1; 247 | if (c < n) 248 | c = n; 249 | static if (useGC) 250 | void* oldPtr = arr.ptr; 251 | void[] a = cast(void[]) arr; 252 | import std.experimental.allocator.common : reallocate; 253 | allocator.reallocate(a, c * T.sizeof); 254 | arr = cast(typeof(arr)) a; 255 | static if (useGC) 256 | { 257 | import core.memory: GC; 258 | GC.removeRange(oldPtr); 259 | GC.addRange(arr.ptr, arr.length * T.sizeof); 260 | } 261 | } 262 | } 263 | 264 | /** 265 | * Change the array length. 266 | * When growing, initialize new elements to the default value. 267 | */ 268 | static if (is(typeof({static T value;}))) // default construction is allowed 269 | void resize(size_t n) 270 | { 271 | import std.traits: hasElaborateAssign, hasElaborateDestructor; 272 | auto toFill = resizeStorage(n); 273 | static if (is(T == struct) && hasElaborateDestructor!T) 274 | { 275 | foreach (ref target; toFill) 276 | emplace(&target); 277 | } 278 | else 279 | toFill[] = T.init; 280 | } 281 | 282 | /** 283 | * Change the array length. 284 | * When growing, initialize new elements to the given value. 285 | */ 286 | static if (isCopyable!T) 287 | void resize(size_t n, T value) 288 | { 289 | import std.traits: hasElaborateAssign, hasElaborateDestructor; 290 | auto toFill = resizeStorage(n); 291 | static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) 292 | { 293 | foreach (ref target; toFill) 294 | copyEmplace(value, target); 295 | } 296 | else 297 | toFill[] = value; 298 | } 299 | 300 | // Resizes storage only, and returns slice of new memory to fill. 301 | private ContainerStorageType!T[] resizeStorage(size_t n) 302 | { 303 | ContainerStorageType!T[] toFill = null; 304 | 305 | if (arr.length < n) 306 | reserve(n); 307 | 308 | if (l < n) // Growing? 309 | { 310 | toFill = arr[l..n]; 311 | } 312 | else 313 | { 314 | static if ((is(T == struct) || is(T == union)) 315 | && __traits(hasMember, T, "__xdtor")) 316 | { 317 | foreach (i; n..l) 318 | arr[i].__xdtor(); 319 | } 320 | } 321 | 322 | l = n; 323 | return toFill; 324 | } 325 | 326 | /** 327 | * Remove the item at the given index from the array. 328 | */ 329 | void remove(const size_t i) 330 | { 331 | if (i < this.l) 332 | { 333 | auto next = i + 1; 334 | while (next < this.l) 335 | { 336 | move(arr[next], arr[next - 1]); 337 | ++next; 338 | } 339 | 340 | --l; 341 | static if ((is(T == struct) || is(T == union)) 342 | && __traits(hasMember, T, "__xdtor")) 343 | { 344 | arr[l].__xdtor(); 345 | } 346 | } 347 | else 348 | { 349 | import core.exception : RangeError; 350 | throw new RangeError("Out of range index used to remove element"); 351 | } 352 | } 353 | 354 | /** 355 | * Removes the last element from the array. 356 | */ 357 | void removeBack() 358 | { 359 | this.remove(this.length - 1); 360 | } 361 | 362 | /// Index assignment support 363 | void opIndexAssign(T value, size_t i) @nogc 364 | { 365 | arr[i] = move(*cast(ContainerStorageType!T*)&value); 366 | } 367 | 368 | /// Slice assignment support 369 | static if (isCopyable!T) 370 | void opSliceAssign(T value) @nogc 371 | { 372 | arr[0 .. l] = value; 373 | } 374 | 375 | /// ditto 376 | static if (isCopyable!T) 377 | void opSliceAssign(T value, size_t i, size_t j) @nogc 378 | { 379 | arr[i .. j] = value; 380 | } 381 | 382 | /// ditto 383 | static if (isCopyable!T) 384 | void opSliceAssign(T[] values) @nogc 385 | { 386 | arr[0 .. l] = values[]; 387 | } 388 | 389 | /// ditto 390 | static if (isCopyable!T) 391 | void opSliceAssign(T[] values, size_t i, size_t j) @nogc 392 | { 393 | arr[i .. j] = values[]; 394 | } 395 | 396 | /// Returns: the number of items in the array 397 | size_t length() const nothrow pure @property @safe @nogc { return l; } 398 | 399 | /// Ditto 400 | alias opDollar = length; 401 | 402 | /// Returns: whether or not the DynamicArray is empty. 403 | bool empty() const nothrow pure @property @safe @nogc { return l == 0; } 404 | 405 | /** 406 | * Returns: a slice to the underlying array. 407 | * 408 | * As the memory of the array may be freed, access to this array is 409 | * highly unsafe. 410 | */ 411 | auto ptr(this This)() @nogc @property 412 | { 413 | alias ET = ContainerElementType!(This, T); 414 | return cast(ET*) arr.ptr; 415 | } 416 | 417 | /// Returns: the front element of the DynamicArray. 418 | auto ref T front() pure @property 419 | { 420 | return arr[0]; 421 | } 422 | 423 | /// Returns: the back element of the DynamicArray. 424 | auto ref T back() pure @property 425 | { 426 | return arr[l - 1]; 427 | } 428 | 429 | private: 430 | 431 | import containers.internal.storage_type : ContainerStorageType; 432 | import containers.internal.element_type : ContainerElementType; 433 | import containers.internal.mixins : AllocatorState; 434 | 435 | enum bool useGC = supportGC && shouldAddGCRange!T; 436 | mixin AllocatorState!Allocator; 437 | ContainerStorageType!(T)[] arr; 438 | size_t l; 439 | } 440 | 441 | version(emsi_containers_unittest) unittest 442 | { 443 | import std.algorithm : equal; 444 | import std.range : iota; 445 | DynamicArray!int ints; 446 | assert(ints.empty); 447 | foreach (i; 0 .. 100) 448 | { 449 | ints.insert(i); 450 | assert(ints.front == 0); 451 | assert(ints.back == i); 452 | } 453 | 454 | assert (equal(ints[], iota(100))); 455 | assert (ints.length == 100); 456 | ints[0] = 100; 457 | assert (ints[0] == 100); 458 | ints[0 .. 5] = 20; 459 | foreach (i; ints[0 .. 5]) 460 | assert (i == 20); 461 | ints[] = 432; 462 | foreach (i; ints[]) 463 | assert (i == 432); 464 | 465 | auto arr = ints.ptr; 466 | arr[0] = 1337; 467 | assert(arr[0] == 1337); 468 | assert(ints[0] == 1337); 469 | } 470 | 471 | version(emsi_containers_unittest) 472 | { 473 | class Cls 474 | { 475 | int* a; 476 | 477 | this(int* a) 478 | { 479 | this.a = a; 480 | } 481 | 482 | ~this() 483 | { 484 | ++(*a); 485 | } 486 | } 487 | } 488 | 489 | version(emsi_containers_unittest) unittest 490 | { 491 | int* a = new int; 492 | { 493 | DynamicArray!(Cls) arr; 494 | arr.insert(new Cls(a)); 495 | } 496 | assert(*a == 0); // Destructor not called. 497 | } 498 | 499 | version(emsi_containers_unittest) unittest 500 | { 501 | import std.exception : assertThrown; 502 | import core.exception : RangeError; 503 | DynamicArray!int empty; 504 | assertThrown!RangeError(empty.remove(1337)); 505 | assert(empty.length == 0); 506 | 507 | DynamicArray!int one; 508 | one.insert(0); 509 | assert(one.length == 1); 510 | assertThrown!RangeError(one.remove(1337)); 511 | assert(one.length == 1); 512 | one.remove(0); 513 | assert(one.length == 0); 514 | 515 | DynamicArray!int two; 516 | two.insert(0); 517 | two.insert(1); 518 | assert(two.length == 2); 519 | assertThrown!RangeError(two.remove(1337)); 520 | assert(two.length == 2); 521 | two.remove(0); 522 | assert(two.length == 1); 523 | assert(two[0] == 1); 524 | two.remove(0); 525 | assert(two.length == 0); 526 | 527 | two.insert(0); 528 | two.insert(1); 529 | assert(two.length == 2); 530 | 531 | two.remove(1); 532 | assert(two.length == 1); 533 | assert(two[0] == 0); 534 | assertThrown!RangeError(two.remove(1)); 535 | assert(two.length == 1); 536 | assert(two[0] == 0); 537 | two.remove(0); 538 | assert(two.length == 0); 539 | } 540 | 541 | version(emsi_containers_unittest) unittest 542 | { 543 | int* a = new int; 544 | DynamicArray!(Cls, Mallocator, true) arr; 545 | arr.insert(new Cls(a)); 546 | 547 | arr.remove(0); 548 | assert(*a == 0); // Destructor not called. 549 | } 550 | 551 | version(emsi_containers_unittest) unittest 552 | { 553 | DynamicArray!(int*, Mallocator, true) arr; 554 | 555 | foreach (i; 0 .. 4) 556 | arr.insert(new int(i)); 557 | 558 | assert (arr.length == 4); 559 | 560 | int*[] slice = arr[1 .. $ - 1]; 561 | assert (slice.length == 2); 562 | assert (*slice[0] == 1); 563 | assert (*slice[1] == 2); 564 | } 565 | 566 | version(emsi_containers_unittest) unittest 567 | { 568 | import std.format : format; 569 | 570 | DynamicArray!int arr; 571 | foreach (int i; 0 .. 10) 572 | arr ~= i; 573 | assert(arr.length == 10, "arr.length = %d".format(arr.length)); 574 | 575 | auto arr2 = arr ~ arr; 576 | assert(arr2.length == 20); 577 | auto arr3 = arr2 ~ [100, 99, 98]; 578 | assert(arr3.length == 23); 579 | 580 | while(!arr3.empty) 581 | arr3.removeBack(); 582 | assert(arr3.empty); 583 | 584 | } 585 | 586 | version(emsi_containers_unittest) @system unittest 587 | { 588 | DynamicArray!int a; 589 | a.reserve(1000); 590 | assert(a.length == 0); 591 | assert(a.empty); 592 | assert(a.arr.length >= 1000); 593 | int* p = a[].ptr; 594 | foreach (i; 0 .. 1000) 595 | { 596 | a.insert(i); 597 | } 598 | assert(p is a[].ptr); 599 | } 600 | 601 | version(emsi_containers_unittest) unittest 602 | { 603 | // Ensure that Array.insert doesn't call the destructor for 604 | // a struct whose state is uninitialized memory. 605 | static struct S 606 | { 607 | int* a; 608 | ~this() @nogc nothrow 609 | { 610 | if (a !is null) 611 | ++(*a); 612 | } 613 | } 614 | int* a = new int; 615 | { 616 | DynamicArray!S arr; 617 | // This next line may segfault if destructors are called 618 | // on structs in invalid states. 619 | arr.insert(S(a)); 620 | } 621 | assert(*a == 1); 622 | } 623 | 624 | version(emsi_containers_unittest) @nogc unittest 625 | { 626 | struct HStorage 627 | { 628 | import containers.dynamicarray: DynamicArray; 629 | DynamicArray!int storage; 630 | } 631 | auto hs = HStorage(); 632 | } 633 | 634 | version(emsi_containers_unittest) @nogc unittest 635 | { 636 | DynamicArray!char a; 637 | const DynamicArray!char b = a ~ "def"; 638 | a ~= "abc"; 639 | a ~= b; 640 | assert(a[] == "abcdef"); 641 | a ~= a; 642 | assert(a[] == "abcdefabcdef"); 643 | } 644 | 645 | version(emsi_containers_unittest) unittest 646 | { 647 | enum initialValue = 0x69FF5705DAD1AB6CUL; 648 | enum payloadValue = 0x495343303356D18CUL; 649 | 650 | static struct S 651 | { 652 | ulong value = initialValue; 653 | @nogc: 654 | @disable this(); 655 | this(ulong value) { this.value = value; } 656 | ~this() { assert(value == initialValue || value == payloadValue); } 657 | } 658 | 659 | auto s = S(payloadValue); 660 | 661 | DynamicArray!S arr; 662 | arr.insertBack(s); 663 | arr ~= [s]; 664 | } 665 | 666 | version(emsi_containers_unittest) @nogc unittest 667 | { 668 | DynamicArray!int a; 669 | a.resize(5, 42); 670 | assert(a.length == 5); 671 | assert(a[2] == 42); 672 | a.resize(3, 17); 673 | assert(a.length == 3); 674 | assert(a[2] == 42); 675 | 676 | struct Counter 677 | { 678 | @nogc: 679 | static int count; 680 | @disable this(); 681 | this(int) { count++; } 682 | this(this) { count++; } 683 | ~this() { count--; } 684 | } 685 | 686 | DynamicArray!Counter b; 687 | assert(Counter.count == 0); 688 | static assert(!is(typeof(b.resize(5)))); 689 | b.resize(5, Counter(0)); 690 | assert(Counter.count == 5); 691 | b.resize(3, Counter(0)); 692 | assert(Counter.count == 3); 693 | } 694 | 695 | version(emsi_containers_unittest) @nogc unittest 696 | { 697 | struct S { int i = 42; @disable this(this); } 698 | DynamicArray!S a; 699 | a.resize(1); 700 | assert(a[0].i == 42); 701 | } 702 | 703 | version(emsi_containers_unittest) unittest 704 | { 705 | import std.experimental.allocator.building_blocks.region : Region; 706 | auto region = Region!Mallocator(1024); 707 | 708 | auto arr = DynamicArray!(int, Region!(Mallocator)*, true)(®ion); 709 | // reserve and insert back call the common form of reallocate 710 | arr.reserve(10); 711 | arr.insertBack(1); 712 | assert(arr[0] == 1); 713 | } 714 | 715 | version(emsi_containers_unittest) unittest 716 | { 717 | auto arr = DynamicArray!int(); 718 | arr.resize(5); 719 | arr[] = [1, 2, 3, 4, 5]; 720 | arr[1 .. 4] = [12, 13, 14]; 721 | assert(arr[] == [1, 12, 13, 14, 5]); 722 | } 723 | 724 | version(emsi_containers_unittest) unittest 725 | { 726 | import std.experimental.allocator : RCIAllocator; 727 | auto a = DynamicArray!(int, RCIAllocator)(RCIAllocator.init); 728 | } 729 | -------------------------------------------------------------------------------- /src/containers/hashmap.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Hash Map 3 | * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 | * Authors: Brian Schott 5 | * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 | */ 7 | 8 | module containers.hashmap; 9 | 10 | private import core.lifetime : move; 11 | private import containers.internal.hash; 12 | private import containers.internal.node : shouldAddGCRange; 13 | private import std.experimental.allocator.mallocator : Mallocator; 14 | private import std.traits : isBasicType, Unqual; 15 | 16 | /** 17 | * Associative array / hash map. 18 | * Params: 19 | * K = the key type 20 | * V = the value type 21 | * Allocator = the allocator type to use. Defaults to `Mallocator` 22 | * hashFunction = the hash function to use on the keys 23 | * supportGC = true if the container should support holding references to 24 | * GC-allocated memory. 25 | */ 26 | struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K, 27 | bool supportGC = shouldAddGCRange!K || shouldAddGCRange!V, 28 | bool storeHash = true) 29 | { 30 | this(this) @disable; 31 | 32 | private import std.experimental.allocator.common : stateSize; 33 | 34 | static if (stateSize!Allocator != 0) 35 | { 36 | this() @disable; 37 | 38 | /** 39 | * Use the given `allocator` for allocations. 40 | */ 41 | this(Allocator allocator) pure nothrow @nogc @safe 42 | in 43 | { 44 | static if (is(typeof(allocator is null))) 45 | assert(allocator !is null, "Allocator must not be null"); 46 | } 47 | do 48 | { 49 | this.allocator = allocator; 50 | } 51 | 52 | /** 53 | * Constructs an HashMap with an initial bucket count of bucketCount. bucketCount 54 | * must be a power of two. 55 | */ 56 | this(size_t bucketCount, Allocator allocator) 57 | in 58 | { 59 | static if (is(typeof(allocator is null))) 60 | assert(allocator !is null, "Allocator must not be null"); 61 | assert((bucketCount & (bucketCount - 1)) == 0, "bucketCount must be a power of two"); 62 | } 63 | do 64 | { 65 | this.allocator = allocator; 66 | initialize(bucketCount); 67 | } 68 | 69 | invariant 70 | { 71 | static if (is(typeof(allocator is null))) 72 | assert(allocator !is null, "Allocator must not be null"); 73 | } 74 | } 75 | else 76 | { 77 | /** 78 | * Constructs an HashMap with an initial bucket count of bucketCount. bucketCount 79 | * must be a power of two. 80 | */ 81 | this(size_t bucketCount) 82 | in 83 | { 84 | assert((bucketCount & (bucketCount - 1)) == 0, "bucketCount must be a power of two"); 85 | } 86 | do 87 | { 88 | initialize(bucketCount); 89 | } 90 | } 91 | 92 | ~this() nothrow 93 | { 94 | scope (failure) assert(false, "HashMap destructor threw an exception"); 95 | clear(); 96 | } 97 | 98 | /** 99 | * Removes all items from the map 100 | */ 101 | void clear() 102 | { 103 | import std.experimental.allocator : dispose; 104 | 105 | // always remove ranges from GC first before disposing of buckets, to 106 | // prevent segfaults when the GC collects at an unfortunate time 107 | static if (useGC) 108 | GC.removeRange(buckets.ptr); 109 | allocator.dispose(buckets); 110 | 111 | buckets = null; 112 | _length = 0; 113 | } 114 | 115 | /** 116 | * Supports `aa[key]` syntax. 117 | */ 118 | ref opIndex(this This)(K key) 119 | { 120 | import std.conv : text; 121 | import std.exception : enforce; 122 | 123 | alias CET = ContainerElementType!(This, V, true); 124 | size_t i; 125 | auto n = find(key, i); 126 | enforce(n !is null, "'" ~ text(key) ~ "' not found in HashMap"); 127 | return *cast(CET*) &n.value; 128 | } 129 | 130 | /** 131 | * Returns: `true` if there is an entry in this map for the given `key`, 132 | * false otherwise. 133 | */ 134 | bool containsKey(this This)(K key) inout 135 | { 136 | size_t i; 137 | return find(key, i) !is null; 138 | } 139 | 140 | /** 141 | * Gets the value for the given key, or returns `defaultValue` if the given 142 | * key is not present. 143 | * 144 | * Params: 145 | * key = the key to look up 146 | * defaultValue = the default value 147 | * Returns: the value indexed by `key`, if present, or `defaultValue` otherwise. 148 | */ 149 | auto get(this This)(K key, lazy V defaultValue) 150 | { 151 | alias CET = ContainerElementType!(This, V); 152 | 153 | size_t i; 154 | auto n = find(key, i); 155 | if (n is null) 156 | return defaultValue; 157 | return cast(CET) n.value; 158 | } 159 | 160 | /** 161 | * If the given key does not exist in the HashMap, adds it with 162 | * the value `defaultValue`. 163 | * 164 | * Params: 165 | * key = the key to look up 166 | * defaultValue = the default value 167 | * Returns: a pointer to the value stored in the HashMap with the given key. 168 | * The pointer is guaranteed to be valid only until the next HashMap 169 | * modification. 170 | */ 171 | auto getOrAdd(this This)(K key, lazy V defaultValue) 172 | { 173 | alias CET = ContainerElementType!(This, V); 174 | 175 | size_t i; 176 | auto n = find(key, i); 177 | if (n is null) 178 | return cast(CET*) &insert(key, defaultValue).value; 179 | else 180 | return cast(CET*) &n.value; 181 | } 182 | 183 | /** 184 | * Supports $(B aa[key] = value;) syntax. 185 | */ 186 | void opIndexAssign(V value, const K key) 187 | { 188 | insert(key, move(mutable(value))); 189 | } 190 | 191 | /** 192 | * Supports $(B key in aa) syntax. 193 | * 194 | * Returns: pointer to the value corresponding to the given key, 195 | * or null if the key is not present in the HashMap. 196 | */ 197 | inout(V)* opBinaryRight(string op)(const K key) inout nothrow @trusted if (op == "in") 198 | { 199 | size_t i; 200 | auto n = find(key, i); 201 | if (n is null) 202 | return null; 203 | return &(cast(inout) n).value; 204 | } 205 | 206 | /** 207 | * Removes the value associated with the given key 208 | * Returns: true if a value was actually removed. 209 | */ 210 | bool remove(K key) 211 | { 212 | size_t i; 213 | auto n = find(key, i); 214 | if (n is null) 215 | return false; 216 | static if (storeHash) 217 | auto node = Node(n.hash, n.key); 218 | else 219 | auto node = Node(n.key); 220 | immutable bool removed = buckets[i].remove(node); 221 | if (removed) 222 | _length--; 223 | return removed; 224 | } 225 | 226 | /** 227 | * Returns: the number of key/value pairs in this container. 228 | */ 229 | size_t length() const nothrow pure @property @safe @nogc 230 | { 231 | return _length; 232 | } 233 | 234 | /** 235 | * Returns: `true` if there are no items in this container. 236 | */ 237 | bool empty() const nothrow pure @property @safe @nogc 238 | { 239 | return _length == 0; 240 | } 241 | 242 | /** 243 | * Returns: a range of the keys in this map. 244 | */ 245 | auto byKey(this This)() inout @trusted 246 | { 247 | return MapRange!(This, IterType.key)(cast(Unqual!(This)*) &this); 248 | } 249 | 250 | /** 251 | * Returns: a GC-allocated array filled with the keys contained in this map. 252 | */ 253 | K[] keys() const @property 254 | out(result) 255 | { 256 | assert (result.length == _length, "Length mismatch"); 257 | } 258 | do 259 | { 260 | import std.array : appender; 261 | auto app = appender!(K[])(); 262 | foreach (ref const bucket; buckets) 263 | { 264 | foreach (ref item; bucket) 265 | app.put(cast(K) item.key); 266 | } 267 | return app.data; 268 | } 269 | 270 | 271 | /** 272 | * Returns: a range of the values in this map. 273 | */ 274 | auto byValue(this This)() inout @trusted 275 | { 276 | return MapRange!(This, IterType.value)(cast(Unqual!(This)*) &this); 277 | } 278 | 279 | /// ditto 280 | alias opSlice = byValue; 281 | 282 | /** 283 | * Returns: a GC-allocated array containing the values contained in this map. 284 | */ 285 | auto values(this This)() const @property 286 | out(result) 287 | { 288 | assert (result.length == _length, "Length mismatch"); 289 | } 290 | do 291 | { 292 | import std.array : appender; 293 | auto app = appender!(ContainerElementType!(This, V)[])(); 294 | foreach (ref const bucket; buckets) 295 | { 296 | foreach (item; bucket) 297 | app.put(cast(ContainerElementType!(This, V)) item.value); 298 | } 299 | return app.data; 300 | } 301 | 302 | /** 303 | * Returns: a range of the kev/value pairs in this map. The element type of 304 | * this range is a struct with `key` and `value` fields. 305 | */ 306 | auto byKeyValue(this This)() inout @trusted 307 | { 308 | return MapRange!(This, IterType.both)(cast(Unqual!(This)*) &this); 309 | } 310 | 311 | /** 312 | * Support for $(D foreach(key, value; aa) { ... }) syntax; 313 | */ 314 | int opApply(int delegate(const ref K, ref V) del) 315 | { 316 | int result = 0; 317 | foreach (ref bucket; buckets) 318 | foreach (ref node; bucket[]) 319 | if ((result = del(*cast(K*)&node.key, *cast(V*)&node.value)) != 0) 320 | return result; 321 | return result; 322 | } 323 | 324 | /// ditto 325 | int opApply(int delegate(const ref K, const ref V) del) const 326 | { 327 | int result = 0; 328 | foreach (const ref bucket; buckets) 329 | foreach (const ref node; bucket[]) 330 | if ((result = del(*cast(K*)&node.key, *cast(V*)&node.value)) != 0) 331 | return result; 332 | return result; 333 | } 334 | 335 | /// ditto 336 | int opApply(int delegate(ref V) del) 337 | { 338 | int result = 0; 339 | foreach (ref bucket; buckets) 340 | foreach (ref node; bucket[]) 341 | if ((result = del(*cast(V*)&node.value)) != 0) 342 | return result; 343 | return result; 344 | } 345 | 346 | /// ditto 347 | int opApply(int delegate(const ref V) del) const 348 | { 349 | int result = 0; 350 | foreach (const ref bucket; buckets) 351 | foreach (const ref node; bucket[]) 352 | if ((result = del(*cast(V*)&node.value)) != 0) 353 | return result; 354 | return result; 355 | } 356 | 357 | mixin AllocatorState!Allocator; 358 | 359 | private: 360 | 361 | import std.experimental.allocator : make, makeArray; 362 | import containers.unrolledlist : UnrolledList; 363 | import containers.internal.storage_type : ContainerStorageType; 364 | import containers.internal.element_type : ContainerElementType; 365 | import containers.internal.mixins : AllocatorState; 366 | import core.memory : GC; 367 | 368 | enum bool useGC = supportGC && (shouldAddGCRange!K || shouldAddGCRange!V); 369 | alias Hash = typeof({ K k = void; return hashFunction(k); }()); 370 | 371 | static ref ContainerStorageType!T mutable(T)(ref T value) { return *cast(ContainerStorageType!T*)&value; } 372 | 373 | enum IterType: ubyte 374 | { 375 | key, value, both 376 | } 377 | 378 | static struct MapRange(MapType, IterType Type) 379 | { 380 | static if (Type == IterType.both) 381 | { 382 | struct FrontType 383 | { 384 | ContainerElementType!(MapType, K) key; 385 | ContainerElementType!(MapType, V) value; 386 | } 387 | } 388 | else static if (Type == IterType.value) 389 | alias FrontType = ContainerElementType!(MapType, V); 390 | else static if (Type == IterType.key) 391 | alias FrontType = ContainerElementType!(MapType, K); 392 | else 393 | static assert(false); 394 | 395 | FrontType front() 396 | { 397 | static if (Type == IterType.both) 398 | return FrontType(cast(ContainerElementType!(MapType, K)) bucketRange.front.key, 399 | cast(ContainerElementType!(MapType, V)) bucketRange.front.value); 400 | else static if (Type == IterType.value) 401 | return cast(ContainerElementType!(MapType, V)) bucketRange.front.value; 402 | else static if (Type == IterType.key) 403 | return cast(ContainerElementType!(MapType, K)) bucketRange.front.key; 404 | else 405 | static assert(false); 406 | } 407 | 408 | bool empty() const pure nothrow @nogc @property 409 | { 410 | return _empty; 411 | } 412 | 413 | void popFront() pure nothrow @nogc 414 | { 415 | bucketRange.popFront(); 416 | if (bucketRange.empty) 417 | { 418 | while (bucketRange.empty) 419 | { 420 | bucketIndex++; 421 | if (bucketIndex >= hm.buckets.length) 422 | { 423 | _empty = true; 424 | break; 425 | } 426 | else 427 | bucketRange = hm.buckets[bucketIndex][]; 428 | } 429 | } 430 | } 431 | 432 | private: 433 | 434 | this(Unqual!(MapType)* hm) 435 | { 436 | this.hm = hm; 437 | this.bucketIndex = 0; 438 | bucketRange = typeof(bucketRange).init; 439 | this._empty = false; 440 | 441 | while (true) 442 | { 443 | if (bucketIndex >= hm.buckets.length) 444 | { 445 | _empty = true; 446 | break; 447 | } 448 | bucketRange = hm.buckets[bucketIndex][]; 449 | if (bucketRange.empty) 450 | bucketIndex++; 451 | else 452 | break; 453 | } 454 | } 455 | 456 | Unqual!(MapType)* hm; 457 | size_t bucketIndex; 458 | typeof(hm.buckets[0].opSlice()) bucketRange; 459 | bool _empty; 460 | } 461 | 462 | void initialize(size_t bucketCount = DEFAULT_BUCKET_COUNT) 463 | { 464 | import std.conv : emplace; 465 | assert((bucketCount & (bucketCount - 1)) == 0, "bucketCount must be a power of two"); 466 | 467 | buckets = makeArray!Bucket(allocator, bucketCount); 468 | static if (useGC) 469 | GC.addRange(buckets.ptr, buckets.length * Bucket.sizeof); 470 | foreach (ref bucket; buckets) 471 | { 472 | static if (stateSize!Allocator == 0) 473 | emplace(&bucket); 474 | else 475 | emplace(&bucket, allocator); 476 | } 477 | } 478 | 479 | Node* insert(const K key, V value) 480 | { 481 | return insert(key, move(mutable(value)), hashFunction(key)); 482 | } 483 | 484 | Node* insert(const K key, V value, const Hash hash, const bool modifyLength = true) 485 | { 486 | if (buckets.length == 0) 487 | initialize(); 488 | immutable size_t index = hashToIndex(hash, buckets.length); 489 | foreach (ref item; buckets[index]) 490 | { 491 | if (item.hash == hash && item.key == key) 492 | { 493 | item.value = move(mutable(value)); 494 | return &item; 495 | } 496 | } 497 | static if (storeHash) 498 | Node node = Node(hash, cast(ContainerStorageType!K) key, move(mutable(value))); 499 | else 500 | Node node = Node(cast(ContainerStorageType!K) key, move(mutable(value))); 501 | Node* n = buckets[index].insertAnywhere(move(node)); 502 | if (modifyLength) 503 | _length++; 504 | if (shouldRehash()) 505 | { 506 | rehash(); 507 | immutable newIndex = hashToIndex(hash, buckets.length); 508 | foreach (ref item; buckets[newIndex]) 509 | { 510 | if (item.hash == hash && item.key == key) 511 | return &item; 512 | } 513 | assert(false, "Inserted item not found after rehash"); 514 | } 515 | else 516 | return n; 517 | } 518 | 519 | /** 520 | * Returns: true if the load factor has been exceeded 521 | */ 522 | bool shouldRehash() const pure nothrow @safe @nogc 523 | { 524 | // We let this be greater than one because each bucket is an unrolled 525 | // list that has more than one element per linked list node. 526 | return (float(_length) / float(buckets.length)) > 1.33f; 527 | } 528 | 529 | /** 530 | * Rehash the map. 531 | */ 532 | void rehash() @trusted 533 | { 534 | import std.conv : emplace; 535 | immutable size_t newLength = buckets.length << 1; 536 | immutable size_t newSize = newLength * Bucket.sizeof; 537 | Bucket[] oldBuckets = buckets; 538 | buckets = cast(Bucket[]) allocator.allocate(newSize); 539 | if (newLength) 540 | assert (buckets, "Bucket reallocation failed"); 541 | static if (useGC) 542 | GC.addRange(buckets.ptr, buckets.length * Bucket.sizeof); 543 | assert (buckets.length == newLength, "Bucket reallocation length mismatch"); 544 | foreach (ref bucket; buckets) 545 | { 546 | static if (stateSize!Allocator == 0) 547 | emplace(&bucket); 548 | else 549 | emplace(&bucket, allocator); 550 | } 551 | 552 | foreach (ref bucket; oldBuckets) 553 | { 554 | foreach (ref node; bucket) 555 | insert(cast(K) node.key, move(node.value), node.hash, false); 556 | typeid(typeof(bucket)).destroy(&bucket); 557 | } 558 | static if (useGC) 559 | GC.removeRange(oldBuckets.ptr); 560 | allocator.deallocate(cast(void[]) oldBuckets); 561 | } 562 | 563 | inout(Node)* find(const K key, ref size_t index) inout 564 | { 565 | return find(key, index, hashFunction(key)); 566 | } 567 | 568 | inout(Node)* find(const K key, ref size_t index, const Hash hash) inout 569 | { 570 | import std.array : empty; 571 | 572 | if (buckets.empty) 573 | return null; 574 | index = hashToIndex(hash, buckets.length); 575 | foreach (ref r; buckets[index]) 576 | { 577 | if (r.hash == hash && r.key == key) 578 | return cast(inout(Node)*) &r; 579 | } 580 | return null; 581 | } 582 | 583 | struct Node 584 | { 585 | bool opEquals(ref const K key) const 586 | { 587 | return key == this.key; 588 | } 589 | 590 | bool opEquals(ref const Node n) const 591 | { 592 | return this.hash == n.hash && this.key == n.key; 593 | } 594 | 595 | static if (storeHash) 596 | Hash hash; 597 | else 598 | @property Hash hash() const { return hashFunction(key); } 599 | 600 | ContainerStorageType!K key; 601 | ContainerStorageType!V value; 602 | } 603 | 604 | alias Bucket = UnrolledList!(Node, Allocator, useGC); 605 | Bucket[] buckets; 606 | size_t _length; 607 | } 608 | 609 | /// 610 | unittest 611 | { 612 | import std.uuid : randomUUID; 613 | import std.range.primitives : walkLength; 614 | 615 | auto hm = HashMap!(string, int)(16); 616 | assert (hm.length == 0); 617 | assert (!hm.remove("abc")); 618 | hm["answer"] = 42; 619 | assert (hm.length == 1); 620 | assert ("answer" in hm); 621 | assert (hm.containsKey("answer")); 622 | hm.remove("answer"); 623 | assert (hm.length == 0); 624 | assert ("answer" !in hm); 625 | assert (hm.get("answer", 1000) == 1000); 626 | assert (!hm.containsKey("answer")); 627 | hm["one"] = 1; 628 | hm["one"] = 1; 629 | assert (hm.length == 1); 630 | assert (hm["one"] == 1); 631 | hm["one"] = 2; 632 | assert(hm["one"] == 2); 633 | foreach (i; 0 .. 1000) 634 | { 635 | hm[randomUUID().toString] = i; 636 | } 637 | assert (hm.length == 1001); 638 | assert (hm.keys().length == hm.length); 639 | assert (hm.values().length == hm.length); 640 | () @nogc { 641 | assert (hm.byKey().walkLength == hm.length); 642 | assert (hm.byValue().walkLength == hm.length); 643 | assert (hm[].walkLength == hm.length); 644 | assert (hm.byKeyValue().walkLength == hm.length); 645 | }(); 646 | foreach (v; hm) {} 647 | 648 | auto hm2 = HashMap!(char, char)(4); 649 | hm2['a'] = 'a'; 650 | 651 | HashMap!(int, int) hm3; 652 | assert (hm3.get(100, 20) == 20); 653 | hm3[100] = 1; 654 | assert (hm3.get(100, 20) == 1); 655 | auto pValue = 100 in hm3; 656 | assert(*pValue == 1); 657 | } 658 | 659 | version(emsi_containers_unittest) unittest 660 | { 661 | static class Foo 662 | { 663 | string name; 664 | } 665 | 666 | void someFunc(const scope ref HashMap!(string,Foo) map) @safe 667 | { 668 | foreach (kv; map.byKeyValue()) 669 | { 670 | assert (kv.key == "foo"); 671 | assert (kv.value.name == "Foo"); 672 | } 673 | } 674 | 675 | auto hm = HashMap!(string, Foo)(16); 676 | auto f = new Foo; 677 | f.name = "Foo"; 678 | hm.insert("foo", f); 679 | assert("foo" in hm); 680 | } 681 | 682 | // Issue #54 683 | version(emsi_containers_unittest) unittest 684 | { 685 | HashMap!(string, int) map; 686 | map.insert("foo", 0); 687 | map.insert("bar", 0); 688 | 689 | foreach (key; map.keys()) 690 | map[key] = 1; 691 | foreach (key; map.byKey()) 692 | map[key] = 1; 693 | 694 | foreach (value; map.byValue()) 695 | assert(value == 1); 696 | foreach (value; map.values()) 697 | assert(value == 1); 698 | } 699 | 700 | version(emsi_containers_unittest) unittest 701 | { 702 | HashMap!(int, int, Mallocator, (int i) => i) map; 703 | auto p = map.getOrAdd(1, 1); 704 | assert(*p == 1); 705 | *p = 2; 706 | assert(map[1] == 2); 707 | } 708 | 709 | debug (EMSI_CONTAINERS) version(emsi_containers_unittest) unittest 710 | { 711 | import std.uuid : randomUUID; 712 | import std.algorithm.iteration : walkLength; 713 | import std.stdio; 714 | 715 | auto hm = HashMap!(string, int)(16); 716 | foreach (i; 0 .. 1_000_000) 717 | { 718 | auto str = randomUUID().toString; 719 | //writeln("Inserting ", str); 720 | hm[str] = i; 721 | //if (i > 0 && i % 100 == 0) 722 | //writeln(i); 723 | } 724 | writeln(hm.buckets.length); 725 | 726 | import std.algorithm.sorting:sort; 727 | ulong[ulong] counts; 728 | foreach (i, ref bucket; hm.buckets[]) 729 | counts[bucket.length]++; 730 | foreach (k; counts.keys.sort()) 731 | writeln(k, "=>", counts[k]); 732 | } 733 | 734 | // #74 735 | version(emsi_containers_unittest) unittest 736 | { 737 | HashMap!(string, size_t) aa; 738 | aa["b"] = 0; 739 | ++aa["b"]; 740 | assert(aa["b"] == 1); 741 | } 742 | 743 | // storeHash == false 744 | version(emsi_containers_unittest) unittest 745 | { 746 | static struct S { size_t v; } 747 | HashMap!(S, S, Mallocator, (S s) { return s.v; }, false, false) aa; 748 | static assert(aa.Node.sizeof == 2 * S.sizeof); 749 | } 750 | 751 | version(emsi_containers_unittest) unittest 752 | { 753 | auto hm = HashMap!(string, int)(16); 754 | 755 | foreach (v; hm) {} 756 | foreach (ref v; hm) {} 757 | foreach (int v; hm) {} 758 | foreach (ref int v; hm) {} 759 | foreach (const ref int v; hm) {} 760 | 761 | foreach (k, v; hm) {} 762 | foreach (k, ref v; hm) {} 763 | foreach (k, int v; hm) {} 764 | foreach (k, ref int v; hm) {} 765 | foreach (k, const ref int v; hm) {} 766 | 767 | foreach (ref k, v; hm) {} 768 | foreach (ref k, ref v; hm) {} 769 | foreach (ref k, int v; hm) {} 770 | foreach (ref k, ref int v; hm) {} 771 | foreach (ref k, const ref int v; hm) {} 772 | 773 | foreach (const string k, v; hm) {} 774 | foreach (const string k, ref v; hm) {} 775 | foreach (const string k, int v; hm) {} 776 | foreach (const string k, ref int v; hm) {} 777 | foreach (const string k, const ref int v; hm) {} 778 | 779 | foreach (const ref string k, v; hm) {} 780 | foreach (const ref string k, ref v; hm) {} 781 | foreach (const ref string k, int v; hm) {} 782 | foreach (const ref string k, ref int v; hm) {} 783 | foreach (const ref string k, const ref int v; hm) {} 784 | 785 | hm["a"] = 1; 786 | foreach (k, ref v; hm) { v++; } 787 | assert(hm["a"] == 2); 788 | } 789 | 790 | version(emsi_containers_unittest) unittest 791 | { 792 | static struct S { @disable this(this); } 793 | alias HM = HashMap!(int, S); 794 | } 795 | 796 | version(emsi_containers_unittest) unittest 797 | { 798 | struct S { int* a; } 799 | alias HM = HashMap!(S, int); 800 | } 801 | -------------------------------------------------------------------------------- /src/containers/hashset.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Hash Set 3 | * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 | * Authors: Brian Schott 5 | * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 | */ 7 | 8 | module containers.hashset; 9 | 10 | private import containers.internal.hash : generateHash, hashToIndex; 11 | private import containers.internal.node : shouldAddGCRange; 12 | private import std.experimental.allocator.mallocator : Mallocator; 13 | private import std.traits : isBasicType; 14 | 15 | /** 16 | * Hash Set. 17 | * Params: 18 | * T = the element type 19 | * Allocator = the allocator to use. Defaults to `Mallocator`. 20 | * hashFunction = the hash function to use on the elements 21 | * supportGC = true if the container should support holding references to 22 | * GC-allocated memory. 23 | */ 24 | struct HashSet(T, Allocator = Mallocator, alias hashFunction = generateHash!T, 25 | bool supportGC = shouldAddGCRange!T, 26 | bool storeHash = !isBasicType!T) 27 | { 28 | this(this) @disable; 29 | 30 | private import std.experimental.allocator.common : stateSize; 31 | 32 | static if (stateSize!Allocator != 0) 33 | { 34 | this() @disable; 35 | 36 | /** 37 | * Use the given `allocator` for allocations. 38 | */ 39 | this(Allocator allocator) 40 | in 41 | { 42 | static if (is(typeof(allocator is null))) 43 | assert(allocator !is null, "Allocator must not be null"); 44 | } 45 | do 46 | { 47 | this.allocator = allocator; 48 | } 49 | 50 | /** 51 | * Constructs a HashSet with an initial bucket count of bucketCount. 52 | * bucketCount must be a power of two. 53 | */ 54 | this(size_t bucketCount, Allocator allocator) 55 | in 56 | { 57 | static if (is(typeof(allocator is null))) 58 | assert(allocator !is null, "Allocator must not be null"); 59 | assert ((bucketCount & (bucketCount - 1)) == 0, "bucketCount must be a power of two"); 60 | } 61 | do 62 | { 63 | this.allocator = allocator; 64 | initialize(bucketCount); 65 | } 66 | } 67 | else 68 | { 69 | /** 70 | * Constructs a HashSet with an initial bucket count of bucketCount. 71 | * bucketCount must be a power of two. 72 | */ 73 | this(size_t bucketCount) 74 | in 75 | { 76 | assert ((bucketCount & (bucketCount - 1)) == 0, "bucketCount must be a power of two"); 77 | } 78 | do 79 | { 80 | initialize(bucketCount); 81 | } 82 | 83 | } 84 | 85 | ~this() 86 | { 87 | import std.experimental.allocator : dispose; 88 | import core.memory : GC; 89 | static if (useGC) 90 | GC.removeRange(buckets.ptr); 91 | allocator.dispose(buckets); 92 | } 93 | 94 | /** 95 | * Removes all items from the set 96 | */ 97 | void clear() 98 | { 99 | foreach (ref bucket; buckets) 100 | { 101 | destroy(bucket); 102 | bucket = Bucket.init; 103 | } 104 | _length = 0; 105 | } 106 | 107 | /** 108 | * Removes the given item from the set. 109 | * Returns: false if the value was not present 110 | */ 111 | bool remove(T value) 112 | { 113 | if (buckets.length == 0) 114 | return false; 115 | immutable Hash hash = hashFunction(value); 116 | immutable size_t index = hashToIndex(hash, buckets.length); 117 | static if (storeHash) 118 | immutable bool removed = buckets[index].remove(ItemNode(hash, value)); 119 | else 120 | immutable bool removed = buckets[index].remove(ItemNode(value)); 121 | if (removed) 122 | --_length; 123 | return removed; 124 | } 125 | 126 | /** 127 | * Returns: true if value is contained in the set. 128 | */ 129 | bool contains(T value) inout 130 | { 131 | return (value in this) !is null; 132 | } 133 | 134 | /** 135 | * Supports $(B a in b) syntax 136 | */ 137 | inout(T)* opBinaryRight(string op)(T value) inout if (op == "in") 138 | { 139 | if (buckets.length == 0 || _length == 0) 140 | return null; 141 | immutable Hash hash = hashFunction(value); 142 | immutable index = hashToIndex(hash, buckets.length); 143 | return buckets[index].get(value, hash); 144 | } 145 | 146 | /** 147 | * Inserts the given item into the set. 148 | * Params: value = the value to insert 149 | * Returns: true if the value was actually inserted, or false if it was 150 | * already present. 151 | */ 152 | bool insert(T value) 153 | { 154 | if (buckets.length == 0) 155 | initialize(4); 156 | Hash hash = hashFunction(value); 157 | immutable size_t index = hashToIndex(hash, buckets.length); 158 | static if (storeHash) 159 | auto r = buckets[index].insert(ItemNode(hash, value)); 160 | else 161 | auto r = buckets[index].insert(ItemNode(value)); 162 | if (r) 163 | ++_length; 164 | if (shouldRehash) 165 | rehash(); 166 | return r; 167 | } 168 | 169 | /// ditto 170 | bool opOpAssign(string op)(T item) if (op == "~") 171 | { 172 | return insert(item); 173 | } 174 | 175 | /// ditto 176 | alias put = insert; 177 | 178 | /// ditto 179 | alias insertAnywhere = insert; 180 | 181 | /** 182 | * Returns: true if the set has no items 183 | */ 184 | bool empty() const nothrow pure @nogc @safe @property 185 | { 186 | return _length == 0; 187 | } 188 | 189 | /** 190 | * Returns: the number of items in the set 191 | */ 192 | size_t length() const nothrow pure @nogc @safe @property 193 | { 194 | return _length; 195 | } 196 | 197 | /** 198 | * Forward range interface 199 | */ 200 | auto opSlice(this This)() nothrow @nogc @trusted 201 | { 202 | return Range!(This)(&this); 203 | } 204 | 205 | private: 206 | 207 | import containers.internal.element_type : ContainerElementType; 208 | import containers.internal.mixins : AllocatorState; 209 | import containers.internal.node : shouldAddGCRange, FatNodeInfo; 210 | import containers.internal.storage_type : ContainerStorageType; 211 | import std.traits : isPointer; 212 | 213 | alias LengthType = ubyte; 214 | alias N = FatNodeInfo!(ItemNode.sizeof, 1, 64, LengthType.sizeof); 215 | enum ITEMS_PER_NODE = N[0]; 216 | static assert(LengthType.max > ITEMS_PER_NODE); 217 | enum bool useGC = supportGC && shouldAddGCRange!T; 218 | alias Hash = typeof({ T v = void; return hashFunction(v); }()); 219 | 220 | void initialize(size_t bucketCount) 221 | { 222 | import core.memory : GC; 223 | import std.experimental.allocator : makeArray; 224 | 225 | makeBuckets(bucketCount); 226 | static if (useGC) 227 | GC.addRange(buckets.ptr, buckets.length * Bucket.sizeof); 228 | } 229 | 230 | static struct Range(ThisT) 231 | { 232 | this(ThisT* t) 233 | { 234 | foreach (i, ref bucket; t.buckets) 235 | { 236 | bucketIndex = i; 237 | if (bucket.root !is null) 238 | { 239 | currentNode = cast(Bucket.BucketNode*) bucket.root; 240 | break; 241 | } 242 | } 243 | this.t = t; 244 | } 245 | 246 | bool empty() const nothrow @safe @nogc @property 247 | { 248 | return currentNode is null; 249 | } 250 | 251 | ET front() nothrow @safe @nogc @property 252 | { 253 | return cast(ET) currentNode.items[nodeIndex].value; 254 | } 255 | 256 | void popFront() nothrow @trusted @nogc 257 | { 258 | if (nodeIndex + 1 < currentNode.l) 259 | { 260 | ++nodeIndex; 261 | return; 262 | } 263 | else 264 | { 265 | nodeIndex = 0; 266 | if (currentNode.next is null) 267 | { 268 | ++bucketIndex; 269 | while (bucketIndex < t.buckets.length && t.buckets[bucketIndex].root is null) 270 | ++bucketIndex; 271 | if (bucketIndex < t.buckets.length) 272 | currentNode = cast(Bucket.BucketNode*) t.buckets[bucketIndex].root; 273 | else 274 | currentNode = null; 275 | } 276 | else 277 | { 278 | currentNode = currentNode.next; 279 | assert(currentNode.l > 0, "Empty node"); 280 | } 281 | } 282 | } 283 | 284 | private: 285 | alias ET = ContainerElementType!(ThisT, T); 286 | ThisT* t; 287 | Bucket.BucketNode* currentNode; 288 | size_t bucketIndex; 289 | size_t nodeIndex; 290 | } 291 | 292 | void makeBuckets(size_t bucketCount) 293 | { 294 | import std.experimental.allocator : makeArray; 295 | 296 | static if (stateSize!Allocator == 0) 297 | buckets = allocator.makeArray!Bucket(bucketCount); 298 | else 299 | { 300 | import std.conv:emplace; 301 | 302 | buckets = cast(Bucket[]) allocator.allocate(Bucket.sizeof * bucketCount); 303 | foreach (ref bucket; buckets) 304 | emplace!Bucket(&bucket, allocator); 305 | } 306 | } 307 | 308 | bool shouldRehash() const pure nothrow @safe @nogc 309 | { 310 | immutable float numberOfNodes = cast(float) _length / cast(float) ITEMS_PER_NODE; 311 | return (numberOfNodes / cast(float) buckets.length) > 0.75f; 312 | } 313 | 314 | void rehash() @trusted 315 | { 316 | import std.experimental.allocator : makeArray, dispose; 317 | import core.memory : GC; 318 | 319 | immutable size_t newLength = buckets.length << 1; 320 | Bucket[] oldBuckets = buckets; 321 | makeBuckets(newLength); 322 | assert (buckets, "Bucket reallocation failed"); 323 | assert (buckets.length == newLength, "Bucket reallocation size mismatch"); 324 | static if (useGC) 325 | GC.addRange(buckets.ptr, buckets.length * Bucket.sizeof); 326 | foreach (ref const bucket; oldBuckets) 327 | { 328 | for (Bucket.BucketNode* node = cast(Bucket.BucketNode*) bucket.root; node !is null; node = node.next) 329 | { 330 | for (size_t i = 0; i < node.l; ++i) 331 | { 332 | static if (storeHash) 333 | { 334 | immutable Hash hash = node.items[i].hash; 335 | size_t index = hashToIndex(hash, buckets.length); 336 | buckets[index].insert(ItemNode(hash, node.items[i].value)); 337 | } 338 | else 339 | { 340 | immutable Hash hash = hashFunction(node.items[i].value); 341 | size_t index = hashToIndex(hash, buckets.length); 342 | buckets[index].insert(ItemNode(node.items[i].value)); 343 | } 344 | } 345 | } 346 | } 347 | static if (useGC) 348 | GC.removeRange(oldBuckets.ptr); 349 | allocator.dispose(oldBuckets); 350 | } 351 | 352 | static struct Bucket 353 | { 354 | this(this) @disable; 355 | 356 | static if (stateSize!Allocator != 0) 357 | { 358 | this(Allocator allocator) 359 | { 360 | this.allocator = allocator; 361 | } 362 | this() @disable; 363 | } 364 | 365 | ~this() 366 | { 367 | import core.memory : GC; 368 | import std.experimental.allocator : dispose; 369 | 370 | BucketNode* current = root; 371 | BucketNode* previous; 372 | while (true) 373 | { 374 | if (previous !is null) 375 | { 376 | static if (useGC) 377 | GC.removeRange(previous); 378 | allocator.dispose(previous); 379 | } 380 | previous = current; 381 | if (current is null) 382 | break; 383 | current = current.next; 384 | } 385 | } 386 | 387 | static struct BucketNode 388 | { 389 | ContainerStorageType!(T)* get(ItemNode n) 390 | { 391 | foreach (ref item; items[0 .. l]) 392 | { 393 | static if (storeHash) 394 | { 395 | static if (isPointer!T) 396 | { 397 | if (item.hash == n.hash && *item.value == *n.value) 398 | return &item.value; 399 | } 400 | else 401 | { 402 | if (item.hash == n.hash && item.value == n.value) 403 | return &item.value; 404 | } 405 | } 406 | else 407 | { 408 | static if (isPointer!T) 409 | { 410 | if (*item.value == *n.value) 411 | return &item.value; 412 | } 413 | else 414 | { 415 | if (item.value == n.value) 416 | return &item.value; 417 | } 418 | } 419 | } 420 | return null; 421 | } 422 | 423 | void insert(ItemNode n) 424 | { 425 | items[l] = n; 426 | ++l; 427 | } 428 | 429 | bool remove(ItemNode n) 430 | { 431 | import std.algorithm : SwapStrategy, remove; 432 | 433 | foreach (size_t i, ref node; items[0 .. l]) 434 | { 435 | static if (storeHash) 436 | { 437 | static if (isPointer!T) 438 | immutable bool matches = node.hash == n.hash && *node.value == *n.value; 439 | else 440 | immutable bool matches = node.hash == n.hash && node.value == n.value; 441 | } 442 | else 443 | { 444 | static if (isPointer!T) 445 | immutable bool matches = *node.value == *n.value; 446 | else 447 | immutable bool matches = node.value == n.value; 448 | } 449 | if (matches) 450 | { 451 | items[0 .. l].remove!(SwapStrategy.unstable)(i); 452 | l--; 453 | return true; 454 | } 455 | } 456 | return false; 457 | } 458 | 459 | BucketNode* next; 460 | LengthType l; 461 | ItemNode[ITEMS_PER_NODE] items; 462 | } 463 | 464 | bool insert(ItemNode n) 465 | { 466 | import core.memory : GC; 467 | import std.experimental.allocator : make; 468 | 469 | BucketNode* hasSpace = null; 470 | for (BucketNode* current = root; current !is null; current = current.next) 471 | { 472 | if (current.get(n) !is null) 473 | return false; 474 | if (current.l < current.items.length) 475 | hasSpace = current; 476 | } 477 | if (hasSpace !is null) 478 | hasSpace.insert(n); 479 | else 480 | { 481 | BucketNode* newNode = make!BucketNode(allocator); 482 | static if (useGC) 483 | GC.addRange(newNode, BucketNode.sizeof); 484 | newNode.insert(n); 485 | newNode.next = root; 486 | root = newNode; 487 | } 488 | return true; 489 | } 490 | 491 | bool remove(ItemNode n) 492 | { 493 | import core.memory : GC; 494 | import std.experimental.allocator : dispose; 495 | 496 | BucketNode* current = root; 497 | BucketNode* previous; 498 | while (current !is null) 499 | { 500 | immutable removed = current.remove(n); 501 | if (removed) 502 | { 503 | if (current.l == 0) 504 | { 505 | if (previous !is null) 506 | previous.next = current.next; 507 | else 508 | root = current.next; 509 | static if (useGC) 510 | GC.removeRange(current); 511 | allocator.dispose(current); 512 | } 513 | return true; 514 | } 515 | previous = current; 516 | current = current.next; 517 | } 518 | return false; 519 | } 520 | 521 | inout(T)* get(T value, Hash hash) inout 522 | { 523 | for (BucketNode* current = cast(BucketNode*) root; current !is null; current = current.next) 524 | { 525 | static if (storeHash) 526 | { 527 | auto v = current.get(ItemNode(hash, value)); 528 | } 529 | else 530 | { 531 | auto v = current.get(ItemNode(value)); 532 | } 533 | 534 | if (v !is null) 535 | return cast(typeof(return)) v; 536 | } 537 | return null; 538 | } 539 | 540 | BucketNode* root; 541 | mixin AllocatorState!Allocator; 542 | } 543 | 544 | static struct ItemNode 545 | { 546 | bool opEquals(ref const T v) const 547 | { 548 | static if (isPointer!T) 549 | return *v == *value; 550 | else 551 | return v == value; 552 | } 553 | 554 | bool opEquals(ref const ItemNode other) const 555 | { 556 | static if (storeHash) 557 | if (other.hash != hash) 558 | return false; 559 | static if (isPointer!T) 560 | return *other.value == *value; 561 | else 562 | return other.value == value; 563 | } 564 | 565 | static if (storeHash) 566 | Hash hash; 567 | ContainerStorageType!T value; 568 | 569 | static if (storeHash) 570 | { 571 | this(Z)(Hash nh, Z nv) 572 | { 573 | this.hash = nh; 574 | this.value = nv; 575 | } 576 | } 577 | else 578 | { 579 | this(Z)(Z nv) 580 | { 581 | this.value = nv; 582 | } 583 | } 584 | } 585 | 586 | mixin AllocatorState!Allocator; 587 | Bucket[] buckets; 588 | size_t _length; 589 | } 590 | 591 | /// 592 | version(emsi_containers_unittest) unittest 593 | { 594 | import std.algorithm : canFind; 595 | import std.array : array; 596 | import std.range : walkLength; 597 | import std.uuid : randomUUID; 598 | 599 | auto s = HashSet!string(16); 600 | assert(s.remove("DoesNotExist") == false); 601 | assert(!s.contains("nonsense")); 602 | assert(s.put("test")); 603 | assert(s.contains("test")); 604 | assert(!s.put("test")); 605 | assert(s.contains("test")); 606 | assert(s.length == 1); 607 | assert(!s.contains("nothere")); 608 | s.put("a"); 609 | s.put("b"); 610 | s.put("c"); 611 | s.put("d"); 612 | string[] strings = s[].array; 613 | assert(strings.canFind("a")); 614 | assert(strings.canFind("b")); 615 | assert(strings.canFind("c")); 616 | assert(strings.canFind("d")); 617 | assert(strings.canFind("test")); 618 | assert(*("a" in s) == "a"); 619 | assert(*("b" in s) == "b"); 620 | assert(*("c" in s) == "c"); 621 | assert(*("d" in s) == "d"); 622 | assert(*("test" in s) == "test"); 623 | assert(strings.length == 5); 624 | assert(s.remove("test")); 625 | assert(s.length == 4); 626 | s.clear(); 627 | assert(s.length == 0); 628 | assert(s.empty); 629 | s.put("abcde"); 630 | assert(s.length == 1); 631 | foreach (i; 0 .. 10_000) 632 | { 633 | s.put(randomUUID().toString); 634 | } 635 | assert(s.length == 10_001); 636 | 637 | // Make sure that there's no range violation slicing an empty set 638 | HashSet!int e; 639 | foreach (i; e[]) 640 | assert(i > 0); 641 | 642 | enum MAGICAL_NUMBER = 600_000; 643 | 644 | HashSet!int f; 645 | foreach (i; 0 .. MAGICAL_NUMBER) 646 | assert(f.insert(i)); 647 | assert(f.length == f[].walkLength); 648 | foreach (i; 0 .. MAGICAL_NUMBER) 649 | assert(i in f); 650 | foreach (i; 0 .. MAGICAL_NUMBER) 651 | assert(f.remove(i)); 652 | foreach (i; 0 .. MAGICAL_NUMBER) 653 | assert(!f.remove(i)); 654 | 655 | HashSet!int g; 656 | foreach (i; 0 .. MAGICAL_NUMBER) 657 | assert(g.insert(i)); 658 | 659 | static struct AStruct 660 | { 661 | int a; 662 | int b; 663 | } 664 | 665 | HashSet!(AStruct*, Mallocator, a => a.a) fred; 666 | fred.insert(new AStruct(10, 10)); 667 | auto h = new AStruct(10, 10); 668 | assert(h in fred); 669 | } 670 | 671 | version(emsi_containers_unittest) unittest 672 | { 673 | static class Foo 674 | { 675 | string name; 676 | } 677 | 678 | hash_t stringToHash(string str) @safe pure nothrow @nogc 679 | { 680 | hash_t hash = 5381; 681 | return hash; 682 | } 683 | 684 | hash_t FooToHash(Foo e) pure @safe nothrow @nogc 685 | { 686 | return stringToHash(e.name); 687 | } 688 | 689 | HashSet!(Foo, Mallocator, FooToHash) hs; 690 | auto f = new Foo; 691 | hs.insert(f); 692 | assert(f in hs); 693 | auto r = hs[]; 694 | } 695 | 696 | version(emsi_containers_unittest) unittest 697 | { 698 | static class Foo 699 | { 700 | string name; 701 | this(string n) { this.name = n; } 702 | bool opEquals(const Foo of) const { 703 | if(of !is null) { return this.name == of.name; } 704 | else { return false; } 705 | } 706 | } 707 | 708 | hash_t stringToHash(string str) @safe pure nothrow @nogc 709 | { 710 | hash_t hash = 5381; 711 | return hash; 712 | } 713 | 714 | hash_t FooToHash(in Foo e) pure @safe nothrow @nogc 715 | { 716 | return stringToHash(e.name); 717 | } 718 | 719 | string foo = "foo"; 720 | HashSet!(const(Foo), Mallocator, FooToHash) hs; 721 | const(Foo) f = new const Foo(foo); 722 | hs.insert(f); 723 | assert(f in hs); 724 | auto r = hs[]; 725 | assert(!r.empty); 726 | auto fro = r.front; 727 | assert(fro.name == foo); 728 | } 729 | 730 | version(emsi_containers_unittest) unittest 731 | { 732 | hash_t maxCollision(ulong x) 733 | { 734 | return 0; 735 | } 736 | 737 | HashSet!(ulong, Mallocator, maxCollision) set; 738 | auto ipn = set.ITEMS_PER_NODE; // Need this info to trigger this bug, so I made it public 739 | assert(ipn > 1); // Won't be able to trigger this bug if there's only 1 item per node 740 | 741 | foreach (i; 0 .. 2 * ipn - 1) 742 | set.insert(i); 743 | 744 | assert(0 in set); // OK 745 | bool ret = set.insert(0); // 0 should be already in the set 746 | assert(!ret); // Fails 747 | assert(set.length == 2 * ipn - 1); // Fails 748 | } 749 | 750 | version(emsi_containers_unittest) unittest 751 | { 752 | import std.experimental.allocator.showcase; 753 | auto allocator = mmapRegionList(1024); 754 | auto set = HashSet!(ulong, typeof(&allocator))(0x1000, &allocator); 755 | set.insert(124); 756 | } 757 | -------------------------------------------------------------------------------- /src/containers/immutablehashset.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Immutable hash set. 3 | * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 | * Authors: Brian Schott 5 | * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 | */ 7 | 8 | module containers.immutablehashset; 9 | 10 | /** 11 | * The immutable hash set is useful for constructing a read-only collection that 12 | * supports quickly determining if an element is present. 13 | * 14 | * Because the set does not support inserting, it only takes up as much memory 15 | * as is necessary to contain the elements provided at construction. Memory is 16 | * managed by malloc/free. 17 | */ 18 | struct ImmutableHashSet(T, alias hashFunction) 19 | { 20 | /// 21 | @disable this(); 22 | /// 23 | @disable this(this); 24 | 25 | /** 26 | * Constructs an immutable hash set from the given values. The values must 27 | * not have any duplicates. 28 | */ 29 | this(const T[] values) immutable 30 | in 31 | { 32 | import std.algorithm : sort, uniq; 33 | import std.array : array; 34 | assert (values.dup.sort().uniq().array().length == values.length, "Set values are not unique"); 35 | } 36 | do 37 | { 38 | empty = values.length == 0; 39 | length = values.length; 40 | if (empty) 41 | return; 42 | immutable float a = (cast(float) values.length) / .75f; 43 | size_t bucketCount = 1; 44 | while (bucketCount <= cast(size_t) a) 45 | bucketCount <<= 1; 46 | Node[][] mutableBuckets = cast(Node[][]) Mallocator.instance.allocate((Node[]).sizeof * bucketCount); 47 | Node[] mutableNodes = cast(Node[]) Mallocator.instance.allocate(Node.sizeof * values.length); 48 | 49 | size_t[] lengths = cast(size_t[]) Mallocator.instance.allocate(size_t.sizeof * bucketCount); 50 | lengths[] = 0; 51 | scope(exit) Mallocator.instance.deallocate(lengths); 52 | 53 | size_t[] indexes = cast(size_t[]) Mallocator.instance.allocate(size_t.sizeof * values.length); 54 | scope(exit) Mallocator.instance.deallocate(indexes); 55 | 56 | size_t[] hashes = cast(size_t[]) Mallocator.instance.allocate(size_t.sizeof * values.length); 57 | scope(exit) Mallocator.instance.deallocate(hashes); 58 | 59 | foreach (i, ref value; values) 60 | { 61 | hashes[i] = hashFunction(value); 62 | indexes[i] = hashes[i] & (mutableBuckets.length - 1); 63 | lengths[indexes[i]]++; 64 | } 65 | 66 | size_t j = 0; 67 | foreach (i, l; lengths) 68 | { 69 | mutableBuckets[i] = mutableNodes[j .. j + l]; 70 | j += l; 71 | } 72 | 73 | lengths[] = 0; 74 | foreach (i; 0 .. values.length) 75 | { 76 | immutable l = lengths[indexes[i]]; 77 | static if (hasMember!(Node, "hash")) 78 | mutableBuckets[indexes[i]][l].hash = hashes[i]; 79 | mutableBuckets[indexes[i]][l].value = values[i]; 80 | lengths[indexes[i]]++; 81 | } 82 | buckets = cast(immutable) mutableBuckets; 83 | nodes = cast(immutable) mutableNodes; 84 | static if (shouldAddGCRange!T) 85 | { 86 | GC.addRange(buckets.ptr, buckets.length * (Node[]).sizeof); 87 | foreach (ref b; buckets) 88 | GC.addRange(b.ptr, b.length * Node.sizeof); 89 | GC.addRange(nodes.ptr, nodes.length * Node.sizeof); 90 | } 91 | } 92 | 93 | ~this() 94 | { 95 | static if (shouldAddGCRange!T) 96 | { 97 | GC.removeRange(buckets.ptr); 98 | foreach (ref b; buckets) 99 | GC.removeRange(b.ptr); 100 | GC.removeRange(nodes.ptr); 101 | } 102 | Mallocator.instance.deallocate(cast(void[]) buckets); 103 | Mallocator.instance.deallocate(cast(void[]) nodes); 104 | } 105 | 106 | /** 107 | * Returns: A GC-allocated array containing the contents of this set. 108 | */ 109 | immutable(T)[] opSlice() immutable @safe 110 | { 111 | if (empty) 112 | return []; 113 | T[] values = new T[](nodes.length); 114 | foreach (i, ref v; values) 115 | { 116 | v = nodes[i].value; 117 | } 118 | return values; 119 | } 120 | 121 | /** 122 | * Returns: true if this set contains the given value. 123 | */ 124 | bool contains(T value) immutable 125 | { 126 | if (empty) 127 | return false; 128 | immutable size_t hash = hashFunction(value); 129 | immutable size_t index = hash & (buckets.length - 1); 130 | if (buckets[index].length == 0) 131 | return false; 132 | foreach (ref node; buckets[index]) 133 | { 134 | static if (hasMember!(Node, "hash")) 135 | if (hash != node.hash) 136 | continue; 137 | if (node.value != value) 138 | continue; 139 | return true; 140 | } 141 | return false; 142 | } 143 | 144 | /** 145 | * The number of items in the set. 146 | */ 147 | immutable size_t length; 148 | 149 | /** 150 | * True if the set is empty. 151 | */ 152 | immutable bool empty; 153 | 154 | private: 155 | 156 | import std.experimental.allocator.mallocator : Mallocator; 157 | import std.traits : isBasicType, hasMember; 158 | import containers.internal.node : shouldAddGCRange; 159 | import core.memory : GC; 160 | 161 | static struct Node 162 | { 163 | T value; 164 | static if (!isBasicType!T) 165 | size_t hash; 166 | } 167 | 168 | Node[][] buckets; 169 | Node[] nodes; 170 | } 171 | 172 | /// 173 | version(emsi_containers_unittest) unittest 174 | { 175 | auto ihs1 = immutable ImmutableHashSet!(int, a => a)([1, 3, 5, 19, 31, 40, 17]); 176 | assert (ihs1.contains(1)); 177 | assert (ihs1.contains(3)); 178 | assert (ihs1.contains(5)); 179 | assert (ihs1.contains(19)); 180 | assert (ihs1.contains(31)); 181 | assert (ihs1.contains(40)); 182 | assert (ihs1.contains(17)); 183 | assert (!ihs1.contains(100)); 184 | assert (ihs1[].length == 7); 185 | 186 | auto ihs2 = immutable ImmutableHashSet!(int, a => a)([]); 187 | assert (ihs2.length == 0); 188 | assert (ihs2.empty); 189 | assert (ihs2[].length == 0); 190 | assert (!ihs2.contains(42)); 191 | } 192 | -------------------------------------------------------------------------------- /src/containers/internal/backwards.d: -------------------------------------------------------------------------------- 1 | module containers.internal.backwards; 2 | 3 | static if (__VERSION__ < 2071) 4 | { 5 | /// 64-bit popcnt 6 | int popcnt(ulong v) pure nothrow @nogc @safe 7 | { 8 | import core.bitop : popcnt; 9 | 10 | return popcnt(cast(uint) v) + popcnt(cast(uint)(v >>> 32)); 11 | } 12 | 13 | version (X86_64) 14 | public import core.bitop : bsf; 15 | else 16 | { 17 | /// Allow 64-bit bsf on old compilers 18 | int bsf(ulong v) pure nothrow @nogc @safe 19 | { 20 | import core.bitop : bsf; 21 | 22 | immutable uint lower = cast(uint) v; 23 | immutable uint upper = cast(uint)(v >>> 32); 24 | return lower == 0 ? bsf(upper) + 32 : bsf(lower); 25 | } 26 | } 27 | } 28 | else 29 | public import core.bitop : bsf, popcnt; 30 | -------------------------------------------------------------------------------- /src/containers/internal/element_type.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Templates for determining range return types. 3 | * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 | * Authors: Brian Schott 5 | * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 | */ 7 | 8 | module containers.internal.element_type; 9 | 10 | /** 11 | * Figures out the types that should be shown to users of the containers when 12 | * functions such as front and opIndex are called. 13 | * 14 | * Params: 15 | * ContainerType = The container type, usually taken from a `this This` 16 | * template argument 17 | * ElementType = The type of the elements of the container. 18 | * isRef = If the type is being determined for a function that returns `ref` 19 | */ 20 | template ContainerElementType(ContainerType, ElementType, bool isRef = false) 21 | { 22 | import std.traits : isMutable, hasIndirections, PointerTarget, isPointer, Unqual; 23 | 24 | template ET(bool isConst, T) 25 | { 26 | static if (isPointer!ElementType) 27 | { 28 | enum PointerIsConst = is(ElementType == const); 29 | enum PointerIsImmutable = is(ElementType == immutable); 30 | enum DataIsConst = is(PointerTarget!(ElementType) == const); 31 | enum DataIsImmutable = is(PointerTarget!(ElementType) == immutable); 32 | static if (isConst) 33 | { 34 | static if (PointerIsConst) 35 | alias ET = ElementType; 36 | else static if (PointerIsImmutable) 37 | alias ET = ElementType; 38 | else 39 | alias ET = const(PointerTarget!(ElementType))*; 40 | } 41 | else 42 | { 43 | static assert(DataIsImmutable, "An immutable container cannot reference const or mutable data"); 44 | static if (PointerIsConst) 45 | alias ET = immutable(PointerTarget!ElementType)*; 46 | else 47 | alias ET = ElementType; 48 | } 49 | } 50 | else 51 | { 52 | static if (isConst) 53 | { 54 | static if (is(ElementType == immutable)) 55 | alias ET = ElementType; 56 | else 57 | alias ET = const(Unqual!ElementType); 58 | } 59 | else 60 | alias ET = immutable(Unqual!ElementType); 61 | } 62 | } 63 | 64 | static if (isMutable!ContainerType) 65 | { 66 | alias ContainerElementType = ElementType; 67 | } 68 | else 69 | { 70 | static if (isRef || hasIndirections!ElementType) 71 | alias ContainerElementType = ET!(is(ContainerType == const), ElementType); 72 | else 73 | alias ContainerElementType = ElementType; 74 | } 75 | } 76 | 77 | unittest 78 | { 79 | static struct Container {} 80 | static struct Data1 { int* x; } 81 | static class Data2 { int* x; } 82 | 83 | static assert(is(ContainerElementType!(Container, int) == int)); 84 | static assert(is(ContainerElementType!(Container, const int) == const int)); 85 | static assert(is(ContainerElementType!(Container, immutable int) == immutable int)); 86 | static assert(is(ContainerElementType!(const Container, int) == int)); 87 | static assert(is(ContainerElementType!(const Container, const int) == const int)); 88 | static assert(is(ContainerElementType!(const Container, immutable int) == immutable int)); 89 | static assert(is(ContainerElementType!(immutable Container, int) == int)); 90 | static assert(is(ContainerElementType!(immutable Container, const int) == const int)); 91 | static assert(is(ContainerElementType!(immutable Container, immutable int) == immutable int)); 92 | 93 | static assert(is(ContainerElementType!(Container, Data1) == Data1)); 94 | static assert(is(ContainerElementType!(Container, const Data1) == const Data1)); 95 | static assert(is(ContainerElementType!(Container, immutable Data1) == immutable Data1)); 96 | static assert(is(ContainerElementType!(const Container, Data1) == const Data1)); 97 | static assert(is(ContainerElementType!(const Container, const Data1) == const Data1)); 98 | static assert(is(ContainerElementType!(const Container, immutable Data1) == immutable Data1)); 99 | static assert(is(ContainerElementType!(immutable Container, Data1) == immutable Data1)); 100 | static assert(is(ContainerElementType!(immutable Container, const Data1) == immutable Data1)); 101 | static assert(is(ContainerElementType!(immutable Container, immutable Data1) == immutable Data1)); 102 | 103 | static assert(is(ContainerElementType!(Container, Data2) == Data2)); 104 | static assert(is(ContainerElementType!(Container, const Data2) == const Data2)); 105 | static assert(is(ContainerElementType!(Container, immutable Data2) == immutable Data2)); 106 | static assert(is(ContainerElementType!(const Container, Data2) == const Data2)); 107 | static assert(is(ContainerElementType!(const Container, const Data2) == const Data2)); 108 | static assert(is(ContainerElementType!(const Container, immutable Data2) == immutable Data2)); 109 | static assert(is(ContainerElementType!(immutable Container, Data2) == immutable Data2)); 110 | static assert(is(ContainerElementType!(immutable Container, const Data2) == immutable Data2)); 111 | static assert(is(ContainerElementType!(immutable Container, immutable Data2) == immutable Data2)); 112 | 113 | static assert(is(ContainerElementType!(Container, int*) == int*)); 114 | static assert(is(ContainerElementType!(Container, const int*) == const int*)); 115 | static assert(is(ContainerElementType!(Container, const(int)*) == const(int)*)); 116 | static assert(is(ContainerElementType!(Container, immutable int*) == immutable int*)); 117 | static assert(is(ContainerElementType!(Container, immutable(int)*) == immutable(int)*)); 118 | 119 | static assert(is(ContainerElementType!(Container, Data1*) == Data1*)); 120 | static assert(is(ContainerElementType!(Container, const Data1*) == const Data1*)); 121 | static assert(is(ContainerElementType!(Container, const(Data1)*) == const(Data1)*)); 122 | static assert(is(ContainerElementType!(Container, immutable Data1*) == immutable Data1*)); 123 | static assert(is(ContainerElementType!(Container, immutable(Data1)*) == immutable(Data1)*)); 124 | 125 | static assert(is(ContainerElementType!(const Container, int*) == const(int)*)); 126 | static assert(is(ContainerElementType!(const Container, const int*) == const int*)); 127 | static assert(is(ContainerElementType!(const Container, const(int)*) == const(int)*)); 128 | static assert(is(ContainerElementType!(const Container, immutable int*) == immutable int*)); 129 | static assert(is(ContainerElementType!(const Container, immutable(int)*) == immutable(int)*)); 130 | 131 | static assert(!__traits(compiles, ContainerElementType!(immutable Container, int*))); 132 | static assert(!__traits(compiles, ContainerElementType!(immutable Container, const int*))); 133 | 134 | static assert(is(ContainerElementType!(immutable Container, immutable int*) == immutable int*)); 135 | static assert(is(ContainerElementType!(immutable Container, immutable(int)*) == immutable(int)*)); 136 | } 137 | -------------------------------------------------------------------------------- /src/containers/internal/hash.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Templates for hashing types. 3 | * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 | * Authors: Brian Schott 5 | * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 | */ 7 | module containers.internal.hash; 8 | 9 | static if (hash_t.sizeof == 4) 10 | { 11 | hash_t generateHash(T)(const T value) 12 | { 13 | return hashOf(value); 14 | } 15 | } 16 | else 17 | { 18 | hash_t generateHash(T)(const T value) if (!is(T == string)) 19 | { 20 | return hashOf(value); 21 | } 22 | 23 | /** 24 | * A variant of the FNV-1a (64) hashing algorithm. 25 | */ 26 | hash_t generateHash(T)(T value) pure nothrow @nogc @trusted if (is(T == string)) 27 | { 28 | hash_t h = 0xcbf29ce484222325; 29 | foreach (const ubyte c; cast(ubyte[]) value) 30 | { 31 | h ^= ((c - ' ') * 13); 32 | h *= 0x100000001b3; 33 | } 34 | return h; 35 | } 36 | } 37 | 38 | /** 39 | * Convert a hash code into a valid array index. 40 | * 41 | * Prams: 42 | * hash = the hash code to be mapped 43 | * len = the length of the array that backs the hash container. 44 | */ 45 | size_t hashToIndex(const size_t hash, const size_t len) pure nothrow @nogc @safe 46 | { 47 | // This magic number taken from 48 | // https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/ 49 | // 50 | // It's amazing how much faster this makes the hash data structures 51 | // when faced with low quality hash functions. 52 | static if (size_t.sizeof == 8) 53 | enum ulong magic = 11_400_714_819_323_198_485UL; 54 | else 55 | enum uint magic = 2_654_435_769U; 56 | 57 | if (len <= 1) 58 | return 0; 59 | version(LDC) 60 | { 61 | import ldc.intrinsics : llvm_cttz; 62 | return (hash * magic) >>> ((size_t.sizeof * 8) - llvm_cttz(len, true)); 63 | } 64 | else 65 | { 66 | import core.bitop : bsf; 67 | return (hash * magic) >>> ((size_t.sizeof * 8) - bsf(len)); 68 | } 69 | } 70 | 71 | enum size_t DEFAULT_BUCKET_COUNT = 8; 72 | -------------------------------------------------------------------------------- /src/containers/internal/mixins.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Container mixins 3 | * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 | * Authors: Brian Schott 5 | * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 | */ 7 | module containers.internal.mixins; 8 | 9 | mixin template AllocatorState(Allocator) 10 | { 11 | static if (stateSize!Allocator == 0) 12 | alias allocator = Allocator.instance; 13 | else 14 | Allocator allocator; 15 | } 16 | -------------------------------------------------------------------------------- /src/containers/internal/node.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Templates for container node types. 3 | * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 | * Authors: Brian Schott 5 | * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 | */ 7 | module containers.internal.node; 8 | 9 | template FatNodeInfo(size_t bytesPerItem, size_t pointerCount, size_t cacheLineSize = 64, 10 | size_t extraSpace = size_t.max) 11 | { 12 | import std.meta : AliasSeq; 13 | import std.format : format; 14 | template fatNodeCapacity(alias L, bool CheckLength = true) 15 | { 16 | enum size_t optimistic = (cacheLineSize 17 | - ((void*).sizeof * pointerCount) - L) / bytesPerItem; 18 | static if (optimistic > 0) 19 | { 20 | enum fatNodeCapacity = optimistic; 21 | static if (CheckLength) 22 | { 23 | static assert(optimistic <= L * 8, ("%d bits required for bookkeeping" 24 | ~ " but only %d are possible. Try reducing the cache line size argument.") 25 | .format(optimistic, L * 8)); 26 | } 27 | } 28 | else 29 | enum fatNodeCapacity = 1; 30 | } 31 | static if (extraSpace == size_t.max) 32 | { 33 | static if (__traits(compiles, fatNodeCapacity!(ubyte.sizeof))) 34 | alias FatNodeInfo = AliasSeq!(fatNodeCapacity!(ubyte.sizeof), ubyte); 35 | else static if (__traits(compiles, fatNodeCapacity!(ushort.sizeof))) 36 | alias FatNodeInfo = AliasSeq!(fatNodeCapacity!(ushort.sizeof), ushort); 37 | else static if (__traits(compiles, fatNodeCapacity!(uint.sizeof))) 38 | alias FatNodeInfo = AliasSeq!(fatNodeCapacity!(uint.sizeof), uint); 39 | else static if (__traits(compiles, fatNodeCapacity!(ulong.sizeof))) 40 | alias FatNodeInfo = AliasSeq!(fatNodeCapacity!(ulong.sizeof), ulong); 41 | else static assert(false, "No type big enough to store " ~ extraSpace.stringof); 42 | } 43 | else 44 | alias FatNodeInfo = AliasSeq!(fatNodeCapacity!(extraSpace, false), void); 45 | } 46 | 47 | // Double linked fat node of int with bookkeeping in a uint should be able to 48 | // hold 11 ints per node. 49 | // 64 - 16 - 4 = 4 * 11 50 | version (X86_64) 51 | static assert (FatNodeInfo!(int.sizeof, 2)[0] == 11); 52 | 53 | template shouldNullSlot(T) 54 | { 55 | import std.traits; 56 | enum shouldNullSlot = isPointer!T || is (T == class) || is (T == interface) || isDynamicArray!T 57 | || is(T == delegate); // closures or class method shoulde be null for GC recycle 58 | } 59 | 60 | template shouldAddGCRange(T) 61 | { 62 | import std.traits; 63 | enum shouldAddGCRange = hasIndirections!T; 64 | } 65 | 66 | static assert (shouldAddGCRange!string); 67 | static assert (!shouldAddGCRange!int); 68 | 69 | template fullBits(T, size_t n, size_t c = 0) 70 | { 71 | static if (c >= (n - 1)) 72 | enum T fullBits = (T(1) << c); 73 | else 74 | enum T fullBits = (T(1) << c) | fullBits!(T, n, c + 1); 75 | } 76 | 77 | static assert (fullBits!(ushort, 1) == 1); 78 | static assert (fullBits!(ushort, 2) == 3); 79 | static assert (fullBits!(ushort, 3) == 7); 80 | static assert (fullBits!(ushort, 4) == 15); 81 | -------------------------------------------------------------------------------- /src/containers/internal/storage_type.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Templates for determining data storage types. 3 | * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 | * Authors: Brian Schott 5 | * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 | */ 7 | module containers.internal.storage_type; 8 | 9 | /** 10 | * This template is used to determine the type that a container should contain 11 | * a given type T. 12 | * 13 | * $(P In many cases it is not possible for a Container!(T) to hold T if T is 14 | * $(B const) or $(B immutable). For instance, a binary heap will need to 15 | * rearrange items in its internal data storage, or an unrolled list will need 16 | * to move items within a node.) 17 | * 18 | * $(P All containers must only return const references or const copies of data 19 | * stored within them if the contained type is const. This template exists 20 | * because containers may need to move entire items but not change internal 21 | * state of those items and D's type system would otherwise not allow this.) 22 | * 23 | * $(UL 24 | * $(LI If $(B T) is mutable (i.e. not $(B immutable) or $(B const)), this 25 | * template aliases itself to T.) 26 | * $(LI $(B Class and Interface types:) Rebindable is used. Using this properly 27 | * in a container requires that $(LINK2 https://issues.dlang.org/show_bug.cgi?id=8663, 28 | * issue 8663) be fixed. Without this fix it is not possible to implement 29 | * containers such as a binary heap that must compare container elements 30 | * using $(B opCmp())) 31 | * $(LI $(B Struct and Union types:) 32 | * $(UL 33 | * $(LI Stuct and union types that have elaborate constructors, 34 | * elaboriate opAssign, or a destructor cannot be stored in containers 35 | * because there may be user-visible effects of discarding $(B const) 36 | * or $(B immutable) on these struct types.) 37 | * $(LI Other struct and union types will be stored as non-const 38 | * versions of themselves.) 39 | * )) 40 | * $(LI $(B Basic types:) Basic types will have their const or immutable status 41 | * removed.) 42 | * $(LI $(B Pointers:) Pointers will have their type constructors shifted. E.g. 43 | * const(int*) becomes const(int)*) 44 | * ) 45 | */ 46 | template ContainerStorageType(T) 47 | { 48 | import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor, 49 | hasElaborateAssign, isBasicType, isDynamicArray, isPointer, Unqual; 50 | import std.typecons : Rebindable; 51 | static if (is (T == const) || is (T == immutable)) 52 | { 53 | static if (isBasicType!T || isDynamicArray!T || isPointer!T) 54 | alias ContainerStorageType = Unqual!T; 55 | else static if (is (T == class) || is (T == interface)) 56 | alias ContainerStorageType = Rebindable!T; 57 | else static if (is (T == struct)) 58 | { 59 | alias U = Unqual!T; 60 | static if (hasElaborateAssign!U || hasElaborateCopyConstructor!U || hasElaborateDestructor!U) 61 | static assert (false, "Cannot store " ~ T.stringof ~ " because of postblit, opAssign, or ~this"); 62 | else 63 | alias ContainerStorageType = U; 64 | } 65 | else 66 | static assert (false, "Don't know how to handle type " ~ T.stringof); 67 | } 68 | else 69 | alias ContainerStorageType = T; 70 | } 71 | 72 | /// 73 | unittest 74 | { 75 | static assert (is (ContainerStorageType!(int) == int)); 76 | static assert (is (ContainerStorageType!(const int) == int)); 77 | } 78 | 79 | /// 80 | unittest 81 | { 82 | import std.typecons : Rebindable; 83 | static assert (is (ContainerStorageType!(Object) == Object)); 84 | static assert (is (ContainerStorageType!(const(Object)) == Rebindable!(const(Object)))); 85 | } 86 | 87 | /// 88 | unittest 89 | { 90 | struct A { int foo; } 91 | struct B { void opAssign(typeof(this)) { this.foo *= 2; } int foo;} 92 | 93 | // A can be stored easily because it is plain data 94 | static assert (is (ContainerStorageType!(A) == A)); 95 | static assert (is (ContainerStorageType!(const(A)) == A)); 96 | 97 | // const(B) cannot be stored in the container because of its 98 | // opAssign. Casting away const could lead to some very unexpected 99 | // behavior. 100 | static assert (!is (typeof(ContainerStorageType!(const(B))))); 101 | // Mutable B is not a problem 102 | static assert (is (ContainerStorageType!(B) == B)); 103 | 104 | // Arrays can be stored because the entire pointer-length pair is moved as 105 | // a unit. 106 | static assert (is (ContainerStorageType!(const(int[])) == const(int)[])); 107 | } 108 | 109 | /// 110 | unittest 111 | { 112 | static assert (is (ContainerStorageType!(const(int*)) == const(int)*)); 113 | } 114 | -------------------------------------------------------------------------------- /src/containers/openhashset.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Open-Addressed Hash Set 3 | * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 | * Authors: Brian Schott 5 | * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 | */ 7 | module containers.openhashset; 8 | 9 | private import containers.internal.hash; 10 | private import containers.internal.node : shouldAddGCRange; 11 | private import std.experimental.allocator.common : stateSize; 12 | private import std.experimental.allocator.mallocator : Mallocator; 13 | 14 | /** 15 | * Simple open-addressed hash set that uses linear probing to resolve sollisions. 16 | * 17 | * Params: 18 | * T = the element type of the hash set 19 | * Allocator = the allocator to use. Defaults to `Mallocator`. 20 | * hashFunction = the hash function to use 21 | * supportGC = if true, calls to GC.addRange and GC.removeRange will be used 22 | * to ensure that the GC does not accidentally free memory owned by this 23 | * container. 24 | */ 25 | struct OpenHashSet(T, Allocator = Mallocator, 26 | alias hashFunction = generateHash!T, bool supportGC = shouldAddGCRange!T) 27 | { 28 | /** 29 | * Disallow copy construction 30 | */ 31 | this(this) @disable; 32 | 33 | static if (stateSize!Allocator != 0) 34 | { 35 | this() @disable; 36 | 37 | /** 38 | * Use the given `allocator` for allocations. 39 | */ 40 | this(Allocator allocator) 41 | in 42 | { 43 | static if (is(typeof(allocator is null))) 44 | assert(allocator !is null, "Allocator must not be null"); 45 | } 46 | do 47 | { 48 | this.allocator = allocator; 49 | } 50 | 51 | /** 52 | * Initializes the hash set with the given initial capacity. 53 | * 54 | * Params: 55 | * initialCapacity = the initial capacity for the hash set 56 | * allocator = allocator to use for allocations 57 | */ 58 | this(size_t initialCapacity, Allocator allocator) 59 | in 60 | { 61 | static if (is(typeof(allocator is null))) 62 | assert(allocator !is null, "Allocator must not be null"); 63 | assert ((initialCapacity & initialCapacity - 1) == 0, "initialCapacity must be a power of 2"); 64 | } 65 | do 66 | { 67 | this.allocator = allocator; 68 | initialize(initialCapacity); 69 | } 70 | } 71 | else 72 | { 73 | /** 74 | * Initializes the hash set with the given initial capacity. 75 | * 76 | * Params: 77 | * initialCapacity = the initial capacity for the hash set 78 | */ 79 | this(size_t initialCapacity) 80 | in 81 | { 82 | assert ((initialCapacity & initialCapacity - 1) == 0, "initialCapacity must be a power of 2"); 83 | } 84 | do 85 | { 86 | initialize(initialCapacity); 87 | } 88 | } 89 | 90 | ~this() nothrow 91 | { 92 | static if (useGC) 93 | GC.removeRange(nodes.ptr); 94 | allocator.deallocate(nodes); 95 | } 96 | 97 | /** 98 | * Removes all items from the hash set. 99 | */ 100 | void clear() 101 | { 102 | if (empty) 103 | return; 104 | foreach (ref node; nodes) 105 | { 106 | typeid(typeof(node)).destroy(&node); 107 | node.used = false; 108 | } 109 | _length = 0; 110 | } 111 | 112 | /// 113 | bool empty() const pure nothrow @nogc @safe @property 114 | { 115 | return _length == 0; 116 | } 117 | 118 | /// 119 | size_t length() const pure nothrow @nogc @safe @property 120 | { 121 | return _length; 122 | } 123 | 124 | /** 125 | * Returns: 126 | * $(B true) if the hash set contains the given item, false otherwise. 127 | */ 128 | bool contains(T item) const 129 | { 130 | if (empty) 131 | return false; 132 | immutable size_t hash = hashFunction(item); 133 | size_t index = toIndex(nodes, item, hash); 134 | if (index == size_t.max) 135 | return false; 136 | return nodes[index].hash == hash && nodes[index].data == item; 137 | } 138 | 139 | /// ditto 140 | bool opBinaryRight(string op)(T item) inout if (op == "in") 141 | { 142 | return contains(item); 143 | } 144 | 145 | /** 146 | * Inserts the given item into the set. 147 | * 148 | * Returns: 149 | * $(B true) if the item was inserted, false if it was already present. 150 | */ 151 | bool insert(T item) 152 | { 153 | if (nodes.length == 0) 154 | initialize(DEFAULT_BUCKET_COUNT); 155 | immutable size_t hash = hashFunction(item); 156 | size_t index = toIndex(nodes, item, hash); 157 | if (index == size_t.max) 158 | { 159 | grow(); 160 | index = toIndex(nodes, item, hash); 161 | } 162 | else if (nodes[index].used && nodes[index].hash == hash && nodes[index].data == item) 163 | return false; 164 | nodes[index].used = true; 165 | nodes[index].hash = hash; 166 | nodes[index].data = item; 167 | _length++; 168 | return true; 169 | } 170 | 171 | /// ditto 172 | alias put = insert; 173 | 174 | /// ditto 175 | alias insertAnywhere = insert; 176 | 177 | /// ditto 178 | bool opOpAssign(string op)(T item) if (op == "~") 179 | { 180 | return insert(item); 181 | } 182 | 183 | /** 184 | * Params: 185 | * item = the item to remove 186 | * Returns: 187 | * $(B true) if the item was removed, $(B false) if it was not present 188 | */ 189 | bool remove(T item) 190 | { 191 | if (empty) 192 | return false; 193 | immutable size_t hash = hashFunction(item); 194 | size_t index = toIndex(nodes, item, hash); 195 | if (index == size_t.max) 196 | return false; 197 | nodes[index].used = false; 198 | destroy(nodes[index].data); 199 | _length--; 200 | return true; 201 | } 202 | 203 | /** 204 | * Returns: 205 | * A range over the set. 206 | */ 207 | auto opSlice(this This)() nothrow pure @nogc @safe 208 | { 209 | return Range!(This)(nodes); 210 | } 211 | 212 | mixin AllocatorState!Allocator; 213 | 214 | private: 215 | 216 | import containers.internal.element_type : ContainerElementType; 217 | import containers.internal.mixins : AllocatorState; 218 | import containers.internal.storage_type : ContainerStorageType; 219 | import core.memory : GC; 220 | 221 | enum bool useGC = supportGC && shouldAddGCRange!T; 222 | 223 | static struct Range(ThisT) 224 | { 225 | ET front() 226 | { 227 | return cast(typeof(return)) nodes[index].data; 228 | } 229 | 230 | bool empty() const pure nothrow @safe @nogc @property 231 | { 232 | return index >= nodes.length; 233 | } 234 | 235 | void popFront() pure nothrow @safe @nogc 236 | { 237 | index++; 238 | while (index < nodes.length && !nodes[index].used) 239 | index++; 240 | } 241 | 242 | private: 243 | 244 | alias ET = ContainerElementType!(ThisT, T); 245 | 246 | this(const Node[] nodes) 247 | { 248 | this.nodes = nodes; 249 | while (true) 250 | { 251 | if (index >= nodes.length || nodes[index].used) 252 | break; 253 | index++; 254 | } 255 | } 256 | 257 | size_t index; 258 | const Node[] nodes; 259 | } 260 | 261 | void grow() 262 | { 263 | immutable size_t newCapacity = nodes.length << 1; 264 | Node[] newNodes = (cast (Node*) allocator.allocate(newCapacity * Node.sizeof)) 265 | [0 .. newCapacity]; 266 | newNodes[] = Node.init; 267 | static if (useGC) 268 | GC.addRange(newNodes.ptr, newNodes.length * Node.sizeof, typeid(typeof(nodes))); 269 | foreach (ref node; nodes) 270 | { 271 | immutable size_t newIndex = toIndex(newNodes, node.data, node.hash); 272 | newNodes[newIndex] = node; 273 | } 274 | static if (useGC) 275 | GC.removeRange(nodes.ptr); 276 | allocator.deallocate(nodes); 277 | nodes = newNodes; 278 | } 279 | 280 | void initialize(size_t nodeCount) 281 | { 282 | nodes = (cast (Node*) allocator.allocate(nodeCount * Node.sizeof))[0 .. nodeCount]; 283 | static if (useGC) 284 | GC.addRange(nodes.ptr, nodes.length * Node.sizeof, typeid(typeof(nodes))); 285 | nodes[] = Node.init; 286 | _length = 0; 287 | } 288 | 289 | // Returns: size_t.max if the item was not found 290 | static size_t toIndex(const Node[] n, T item, size_t hash) 291 | { 292 | assert (n.length > 0, "Empty node"); 293 | immutable size_t index = hashToIndex(hash, n.length); 294 | size_t i = index; 295 | immutable bucketMask = n.length - 1; 296 | while (n[i].used && n[i].data != item) 297 | { 298 | i = (i + 1) & bucketMask; 299 | if (i == index) 300 | return size_t.max; 301 | } 302 | return i; 303 | } 304 | 305 | Node[] nodes; 306 | size_t _length; 307 | 308 | static struct Node 309 | { 310 | ContainerStorageType!T data; 311 | bool used; 312 | size_t hash; 313 | } 314 | } 315 | 316 | version(emsi_containers_unittest) unittest 317 | { 318 | import std.string : format; 319 | import std.algorithm : equal, sort; 320 | import std.range : iota; 321 | import std.array : array; 322 | OpenHashSet!int ints; 323 | assert (ints.empty); 324 | assert (equal(ints[], cast(int[]) [])); 325 | ints.clear(); 326 | ints.insert(10); 327 | assert (!ints.empty); 328 | assert (ints.length == 1); 329 | assert (equal(ints[], [10])); 330 | assert (ints.contains(10)); 331 | ints.clear(); 332 | assert (ints.length == 0); 333 | assert (ints.empty); 334 | ints ~= 0; 335 | assert (!ints.empty); 336 | assert (ints.length == 1); 337 | assert (equal(ints[], [0])); 338 | ints.clear(); 339 | assert (ints.length == 0); 340 | assert (ints.empty); 341 | foreach (i; 0 .. 100) 342 | ints ~= i; 343 | assert (ints.length == 100, "%d".format(ints.length)); 344 | assert (!ints.empty); 345 | foreach (i; 0 .. 100) 346 | assert (i in ints); 347 | assert (equal(ints[].array().sort(), iota(0, 100))); 348 | assert (ints.insert(10) == false); 349 | auto ohs = OpenHashSet!int(8); 350 | assert (!ohs.remove(1000)); 351 | assert (ohs.contains(99) == false); 352 | assert (ohs.insert(10) == true); 353 | assert (ohs.insert(10) == false); 354 | foreach (i; 0 .. 7) 355 | ohs.insert(i); 356 | assert (ohs.contains(6)); 357 | assert (!ohs.contains(100)); 358 | assert (!ohs.remove(9999)); 359 | assert (ohs.remove(0)); 360 | assert (ohs.remove(1)); 361 | } 362 | 363 | version(emsi_containers_unittest) unittest 364 | { 365 | static class Foo 366 | { 367 | string name; 368 | 369 | override bool opEquals(Object other) const @safe pure nothrow @nogc 370 | { 371 | Foo f = cast(Foo)other; 372 | return f !is null && f.name == this.name; 373 | } 374 | } 375 | 376 | hash_t stringToHash(string str) @safe pure nothrow @nogc 377 | { 378 | hash_t hash = 5381; 379 | return hash; 380 | } 381 | 382 | hash_t FooToHash(Foo e) pure @safe nothrow @nogc 383 | { 384 | return stringToHash(e.name); 385 | } 386 | 387 | OpenHashSet!(Foo, Mallocator, FooToHash) hs; 388 | auto f = new Foo; 389 | hs.insert(f); 390 | assert(f in hs); 391 | auto r = hs[]; 392 | } 393 | -------------------------------------------------------------------------------- /src/containers/package.d: -------------------------------------------------------------------------------- 1 | module containers; 2 | 3 | /** 4 | * This package file imports all other containers modules. 5 | */ 6 | 7 | public import containers.cyclicbuffer; 8 | public import containers.dynamicarray; 9 | public import containers.hashmap; 10 | public import containers.hashset; 11 | public import containers.immutablehashset; 12 | public import containers.openhashset; 13 | public import containers.simdset; 14 | public import containers.slist; 15 | public import containers.treemap; 16 | public import containers.ttree; 17 | public import containers.unrolledlist; 18 | -------------------------------------------------------------------------------- /src/containers/simdset.d: -------------------------------------------------------------------------------- 1 | /** 2 | * SIMD-accelerated Set 3 | * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 | * Authors: Brian Schott 5 | * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt Boost License 1.0) 6 | */ 7 | module containers.simdset; 8 | 9 | private import std.experimental.allocator.mallocator : Mallocator; 10 | 11 | /** 12 | * Set implementation that is well suited for small sets and simple items. 13 | * 14 | * Uses SSE instructions to compare multiple elements simultaneously, but has 15 | * linear time complexity. 16 | * 17 | * Note: Only works on x86_64. Does NOT add GC ranges. Do not store pointers in 18 | * this container unless they are also stored somewhere else. 19 | * 20 | * Params: 21 | * T = the element type 22 | * Allocator = the allocator to use. Defaults to `Mallocator`. 23 | */ 24 | version (D_InlineAsm_X86_64) struct SimdSet(T, Allocator = Mallocator) 25 | if (T.sizeof == 1 || T.sizeof == 2 || T.sizeof == 4 || T.sizeof == 8) 26 | { 27 | this(this) @disable; 28 | 29 | private import std.experimental.allocator.common : stateSize; 30 | 31 | static if (stateSize!Allocator != 0) 32 | { 33 | /// No default construction if an allocator must be provided. 34 | this() @disable; 35 | 36 | /** 37 | * Use the given `allocator` for allocations. 38 | */ 39 | this(Allocator allocator) 40 | in 41 | { 42 | static if (is(typeof(allocator is null))) 43 | assert(allocator !is null, "Allocator must not be null"); 44 | } 45 | do 46 | { 47 | this.allocator = allocator; 48 | } 49 | } 50 | 51 | ~this() 52 | { 53 | scope (failure) assert(false, "SimdSet destructor threw an exception"); 54 | clear(); 55 | } 56 | 57 | void clear() 58 | { 59 | allocator.deallocate(cast(void[]) storage); 60 | _length = 0; 61 | storage = []; 62 | } 63 | 64 | /** 65 | * Params: 66 | * item = the item to check 67 | * Returns: 68 | * true if the set contains the given item 69 | */ 70 | bool contains(T item) const pure nothrow @nogc @trusted 71 | { 72 | if (_length == 0) 73 | return false; 74 | bool retVal; 75 | immutable remainder = _length % (16 / T.sizeof); 76 | ushort mask = remainder == 0 ? 0xffff : (1 << (remainder * T.sizeof)) - 1; 77 | //ushort resultMask; 78 | ulong ptrStart = cast(ulong) storage.ptr; 79 | ulong ptrEnd = ptrStart + storage.length * T.sizeof; 80 | static if (T.sizeof == 1) 81 | ulong needle = (cast(ubyte) item) * 0x01010101_01010101; 82 | else static if (T.sizeof == 2) 83 | ulong needle = (cast(ushort) item) * 0x00010001_00010001; 84 | else static if (T.sizeof == 4) 85 | ulong needle = (cast(ulong) item) * 0x00000001_00000001; 86 | else static if (T.sizeof == 8) 87 | ulong needle = cast(ulong) item; 88 | else 89 | static assert(false); 90 | mixin(asmSearch()); 91 | end: 92 | return retVal; 93 | } 94 | 95 | /// ditto 96 | bool opBinaryRight(string op)(T item) const pure nothrow @nogc @safe if (op == "in") 97 | { 98 | return contains(item); 99 | } 100 | 101 | /** 102 | * Inserts the given item into the set. 103 | * 104 | * Params: 105 | * item = the item to insert 106 | * Returns: 107 | * true if the item was inserted or false if it was already present 108 | */ 109 | bool insert(T item) 110 | { 111 | if (contains(item)) 112 | return false; 113 | if (storage.length > _length) 114 | storage[_length] = item; 115 | else 116 | { 117 | immutable size_t cl = (storage.length * T.sizeof); 118 | immutable size_t nl = cl + 16; 119 | void[] a = cast(void[]) storage; 120 | allocator.reallocate(a, nl); 121 | storage = cast(typeof(storage)) a; 122 | storage[_length] = item; 123 | } 124 | _length++; 125 | return true; 126 | } 127 | 128 | /// ditto 129 | bool opOpAssign(string op)(T item) if (op == "~") 130 | { 131 | return insert(item); 132 | } 133 | 134 | /// ditto 135 | alias insertAnywhere = insert; 136 | 137 | /// ditto 138 | alias put = insert; 139 | 140 | /** 141 | * Removes the given item from the set. 142 | * 143 | * Params: 144 | * item = the time to remove 145 | * Returns: 146 | * true if the item was removed, false if it was not present 147 | */ 148 | bool remove(T item) 149 | { 150 | import std.algorithm : countUntil; 151 | 152 | // TODO: Make this more efficient 153 | 154 | ptrdiff_t begin = countUntil(storage, item); 155 | if (begin == -1) 156 | return false; 157 | foreach (i; begin .. _length - 1) 158 | storage[i] = storage[i + 1]; 159 | _length--; 160 | return true; 161 | } 162 | 163 | /** 164 | * Slice operator 165 | */ 166 | auto opSlice(this This)() 167 | { 168 | import containers.internal.element_type : ContainerElementType; 169 | return cast(ContainerElementType!(This, T)[]) storage[0 .. _length]; 170 | } 171 | 172 | /** 173 | * Returns: 174 | * the number of items in the set 175 | */ 176 | size_t length() const pure nothrow @nogc @property 177 | { 178 | return _length; 179 | } 180 | 181 | invariant 182 | { 183 | assert((storage.length * T.sizeof) % 16 == 0, "Bad storage length"); 184 | } 185 | 186 | private: 187 | 188 | import containers.internal.storage_type : ContainerStorageType; 189 | private import containers.internal.mixins : AllocatorState; 190 | 191 | static string asmSearch() 192 | { 193 | import std.string : format; 194 | 195 | static if (T.sizeof == 1) 196 | enum instruction = `pcmpeqb`; 197 | else static if (T.sizeof == 2) 198 | enum instruction = `pcmpeqw`; 199 | else static if (T.sizeof == 4) 200 | enum instruction = `pcmpeqd`; 201 | else static if (T.sizeof == 8) 202 | enum instruction = `pcmpeqq`; 203 | else 204 | static assert(false); 205 | 206 | static if (__VERSION__ >= 2067) 207 | string s = `asm pure nothrow @nogc`; 208 | else 209 | string s = `asm`; 210 | 211 | return s ~ ` 212 | { 213 | mov R8, ptrStart; 214 | mov R9, ptrEnd; 215 | sub R8, 16; 216 | sub R9, 16; 217 | movq XMM0, needle; 218 | shufpd XMM0, XMM0, 0; 219 | loop: 220 | add R8, 16; 221 | movdqu XMM1, [R8]; 222 | %s XMM1, XMM0; 223 | pmovmskb RAX, XMM1; 224 | //mov resultMask, AX; 225 | mov BX, AX; 226 | and BX, mask; 227 | cmp R8, R9; 228 | cmove AX, BX; 229 | popcnt AX, AX; 230 | test AX, AX; 231 | jnz found; 232 | cmp R8, R9; 233 | jl loop; 234 | mov retVal, 0; 235 | jmp end; 236 | found: 237 | mov retVal, 1; 238 | jmp end; 239 | }`.format(instruction); 240 | } 241 | 242 | mixin AllocatorState!Allocator; 243 | ContainerStorageType!(T)[] storage; 244 | size_t _length; 245 | } 246 | 247 | /// 248 | version (D_InlineAsm_X86_64) version(emsi_containers_unittest) unittest 249 | { 250 | import std.string : format; 251 | 252 | void testSimdSet(T)() 253 | { 254 | SimdSet!T set; 255 | assert(set.insert(1)); 256 | assert(set.length == 1); 257 | assert(set.contains(1)); 258 | assert(!set.insert(1)); 259 | set.insert(0); 260 | set.insert(20); 261 | assert(set.contains(1)); 262 | assert(set.contains(0)); 263 | assert(!set.contains(10)); 264 | assert(!set.contains(50)); 265 | assert(set.contains(20)); 266 | foreach (T i; 28 .. 127) 267 | set.insert(i); 268 | foreach (T i; 28 .. 127) 269 | assert(set.contains(i), "%d".format(i)); 270 | foreach (T i; 28 .. 127) 271 | assert(set.remove(i)); 272 | assert(set.length == 3, "%d".format(set.length)); 273 | assert(set.contains(0)); 274 | assert(set.contains(1)); 275 | assert(set.contains(20)); 276 | assert(!set.contains(28)); 277 | } 278 | 279 | testSimdSet!ubyte(); 280 | testSimdSet!ushort(); 281 | testSimdSet!uint(); 282 | testSimdSet!ulong(); 283 | testSimdSet!byte(); 284 | testSimdSet!short(); 285 | testSimdSet!int(); 286 | testSimdSet!long(); 287 | } 288 | 289 | version (D_InlineAsm_X86_64) struct SimdSet(T) if (!(T.sizeof == 1 290 | || T.sizeof == 2 || T.sizeof == 4 || T.sizeof == 8)) 291 | { 292 | import std.string : format; 293 | static assert (false, ("Cannot instantiate SimdSet of type %s because its size " 294 | ~ "(%d) does not fit evenly into XMM registers.").format(T.stringof, T.sizeof)); 295 | } 296 | -------------------------------------------------------------------------------- /src/containers/slist.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Singly-linked list. 3 | * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 | * Authors: Brian Schott 5 | * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 | */ 7 | 8 | module containers.slist; 9 | 10 | private import containers.internal.node : shouldAddGCRange; 11 | private import std.experimental.allocator.mallocator : Mallocator; 12 | 13 | /** 14 | * Single-linked allocator-backed list. 15 | * Params: 16 | * T = the element type 17 | * Allocator = the allocator to use. Defaults to `Mallocator`. 18 | * supportGC = true if the container should support holding references to 19 | * GC-allocated memory. 20 | */ 21 | struct SList(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T) 22 | { 23 | /// Disable copying. 24 | this(this) @disable; 25 | 26 | private import std.experimental.allocator.common : stateSize; 27 | 28 | static if (stateSize!Allocator != 0) 29 | { 30 | /// No default construction if an allocator must be provided. 31 | this() @disable; 32 | 33 | /** 34 | * Use the given `allocator` for allocations. 35 | */ 36 | this(Allocator allocator) 37 | in 38 | { 39 | static if (is(typeof(allocator is null))) 40 | assert(allocator !is null, "Allocator must not be null"); 41 | } 42 | do 43 | { 44 | this.allocator = allocator; 45 | } 46 | } 47 | 48 | ~this() 49 | { 50 | Node* current = _front; 51 | Node* prev = null; 52 | while (current !is null) 53 | { 54 | prev = current; 55 | current = current.next; 56 | typeid(Node).destroy(prev); 57 | static if (useGC) 58 | { 59 | import core.memory : GC; 60 | GC.removeRange(prev); 61 | } 62 | allocator.dispose(prev); 63 | } 64 | _front = null; 65 | } 66 | 67 | /** 68 | * Complexity: O(1) 69 | * Returns: the most recently inserted item 70 | */ 71 | auto front(this This)() inout @property 72 | in 73 | { 74 | assert (!empty, "Accessing .front of empty SList"); 75 | } 76 | do 77 | { 78 | alias ET = ContainerElementType!(This, T); 79 | return cast(ET) _front.value; 80 | } 81 | 82 | /** 83 | * Complexity: O(length) 84 | * Returns: the least recently inserted item. 85 | */ 86 | auto back(this This)() inout @property 87 | in 88 | { 89 | assert (!empty, "Accessing .back of empty SList"); 90 | } 91 | do 92 | { 93 | alias ET = ContainerElementType!(This, T); 94 | 95 | auto n = _front; 96 | for (; front.next !is null; n = n.next) {} 97 | return cast(ET) n.value; 98 | } 99 | 100 | /** 101 | * Removes the first item in the list. 102 | * 103 | * Complexity: O(1) 104 | * Returns: the first item in the list. 105 | */ 106 | T moveFront() 107 | in 108 | { 109 | assert (!empty, "Accessing .moveFront of empty SList"); 110 | } 111 | do 112 | { 113 | Node* f = _front; 114 | _front = f.next; 115 | T r = f.value; 116 | static if (useGC) 117 | { 118 | import core.memory : GC; 119 | GC.removeRange(f); 120 | } 121 | allocator.dispose(f); 122 | --_length; 123 | return r; 124 | } 125 | 126 | /** 127 | * Removes the first item in the list. 128 | * 129 | * Complexity: O(1) 130 | */ 131 | void popFront() 132 | { 133 | Node* f = _front; 134 | _front = f.next; 135 | static if (useGC) 136 | { 137 | import core.memory : GC; 138 | GC.removeRange(f); 139 | } 140 | allocator.dispose(f); 141 | --_length; 142 | } 143 | 144 | /** 145 | * Complexity: O(1) 146 | * Returns: true if this list is empty 147 | */ 148 | bool empty() inout pure nothrow @property @safe @nogc 149 | { 150 | return _front is null; 151 | } 152 | 153 | /** 154 | * Complexity: O(1) 155 | * Returns: the number of items in the list 156 | */ 157 | size_t length() inout pure nothrow @property @safe @nogc 158 | { 159 | return _length; 160 | } 161 | 162 | /** 163 | * Inserts an item at the front of the list. 164 | * 165 | * Complexity: O(1) 166 | * Params: t = the item to insert into the list 167 | */ 168 | void insertFront(T t) @trusted 169 | { 170 | _front = make!Node(allocator, _front, t); 171 | static if (useGC) 172 | { 173 | import core.memory : GC; 174 | GC.addRange(_front, Node.sizeof); 175 | } 176 | _length++; 177 | } 178 | 179 | /// ditto 180 | alias insert = insertFront; 181 | 182 | /// ditto 183 | alias insertAnywhere = insertFront; 184 | 185 | /// ditto 186 | alias put = insertFront; 187 | 188 | /** 189 | * Supports `list ~= item` syntax 190 | * 191 | * Complexity: O(1) 192 | */ 193 | void opOpAssign(string op)(T t) if (op == "~") 194 | { 195 | put(t); 196 | } 197 | 198 | /** 199 | * Removes the first instance of value found in the list. 200 | * 201 | * Complexity: O(length) 202 | * Returns: true if a value was removed. 203 | */ 204 | bool remove(V)(V value) @trusted 205 | { 206 | Node* prev = null; 207 | Node* cur = _front; 208 | while (cur !is null) 209 | { 210 | if (cur.value == value) 211 | { 212 | if (prev !is null) 213 | prev.next = cur.next; 214 | if (_front is cur) 215 | _front = cur.next; 216 | static if (shouldAddGCRange!T) 217 | { 218 | import core.memory : GC; 219 | GC.removeRange(cur); 220 | } 221 | allocator.dispose(cur); 222 | _length--; 223 | return true; 224 | } 225 | prev = cur; 226 | cur = cur.next; 227 | } 228 | return false; 229 | } 230 | 231 | /** 232 | * Forward range interface 233 | */ 234 | auto opSlice(this This)() inout 235 | { 236 | return Range!(This)(_front); 237 | } 238 | 239 | /** 240 | * Removes all elements from the range 241 | * 242 | * Complexity: O(length) 243 | */ 244 | void clear() 245 | { 246 | Node* prev = null; 247 | Node* cur = _front; 248 | while (cur !is null) 249 | { 250 | prev = cur; 251 | cur = prev.next; 252 | static if (shouldAddGCRange!T) 253 | { 254 | import core.memory : GC; 255 | GC.removeRange(prev); 256 | } 257 | allocator.dispose(prev); 258 | } 259 | _front = null; 260 | _length = 0; 261 | } 262 | 263 | private: 264 | 265 | import std.experimental.allocator : make, dispose; 266 | import containers.internal.node : shouldAddGCRange; 267 | import containers.internal.element_type : ContainerElementType; 268 | import containers.internal.mixins : AllocatorState; 269 | 270 | enum bool useGC = supportGC && shouldAddGCRange!T; 271 | 272 | static struct Range(ThisT) 273 | { 274 | public: 275 | ET front() pure nothrow @property @trusted @nogc 276 | { 277 | return cast(typeof(return)) current.value; 278 | } 279 | 280 | void popFront() pure nothrow @safe @nogc 281 | { 282 | current = current.next; 283 | } 284 | 285 | bool empty() const pure nothrow @property @safe @nogc 286 | { 287 | return current is null; 288 | } 289 | 290 | private: 291 | alias ET = ContainerElementType!(ThisT, T); 292 | const(Node)* current; 293 | } 294 | 295 | static struct Node 296 | { 297 | Node* next; 298 | T value; 299 | } 300 | 301 | mixin AllocatorState!Allocator; 302 | Node* _front; 303 | size_t _length; 304 | } 305 | 306 | version(emsi_containers_unittest) unittest 307 | { 308 | import std.string : format; 309 | import std.algorithm : canFind; 310 | SList!int intList; 311 | foreach (i; 0 .. 100) 312 | intList.put(i); 313 | assert(intList.length == 100, "%d".format(intList.length)); 314 | assert(intList.remove(10)); 315 | assert(!intList.remove(10)); 316 | assert(intList.length == 99); 317 | assert(intList[].canFind(9)); 318 | assert(!intList[].canFind(10)); 319 | SList!string l; 320 | l ~= "abcde"; 321 | l ~= "fghij"; 322 | assert (l.length == 2); 323 | } 324 | 325 | version(emsi_containers_unittest) unittest 326 | { 327 | static class Foo 328 | { 329 | string name; 330 | } 331 | 332 | SList!Foo hs; 333 | auto f = new Foo; 334 | hs.put(f); 335 | } 336 | -------------------------------------------------------------------------------- /src/containers/treemap.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Tree Map 3 | * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 | * Authors: Brian Schott 5 | * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 | */ 7 | 8 | module containers.treemap; 9 | 10 | private import containers.internal.node : shouldAddGCRange; 11 | private import std.experimental.allocator.mallocator : Mallocator; 12 | 13 | /** 14 | * A key→value mapping where the keys are guaranteed to be sorted. 15 | * Params: 16 | * K = the key type 17 | * V = the value type 18 | * Allocator = the allocator to use. Defaults to `Mallocator`. 19 | * less = the key comparison function to use 20 | * supportGC = true to support storing GC-allocated objects, false otherwise 21 | * cacheLineSize = the size of the internal nodes in bytes 22 | */ 23 | struct TreeMap(K, V, Allocator = Mallocator, alias less = "a < b", 24 | bool supportGC = shouldAddGCRange!K || shouldAddGCRange!V, size_t cacheLineSize = 64) 25 | { 26 | this(this) @disable; 27 | 28 | private import std.experimental.allocator.common : stateSize; 29 | 30 | auto allocator() 31 | { 32 | return tree.allocator; 33 | } 34 | 35 | static if (stateSize!Allocator != 0) 36 | { 37 | /// No default construction if an allocator must be provided. 38 | this() @disable; 39 | 40 | /** 41 | * Use the given `allocator` for allocations. 42 | */ 43 | this(Allocator allocator) 44 | { 45 | tree = TreeType(allocator); 46 | } 47 | } 48 | 49 | void clear() 50 | { 51 | tree.clear(); 52 | } 53 | 54 | /** 55 | * Inserts or overwrites the given key-value pair. 56 | */ 57 | void insert(const K key, V value) @trusted 58 | { 59 | auto tme = TreeMapElement(cast(ContainerStorageType!K) key, value); 60 | auto r = tree.equalRange(tme); 61 | if (r.empty) 62 | tree.insert(tme, true); 63 | else 64 | r._containersFront().value = value; 65 | } 66 | 67 | /// Supports $(B treeMap[key] = value;) syntax. 68 | void opIndexAssign(V value, const K key) 69 | { 70 | insert(key, value); 71 | } 72 | 73 | /** 74 | * Supports $(B treeMap[key]) syntax. 75 | * 76 | * Throws: RangeError if the key does not exist. 77 | */ 78 | auto opIndex(this This)(const K key) inout 79 | { 80 | alias CET = ContainerElementType!(This, V); 81 | auto tme = TreeMapElement(cast(ContainerStorageType!K) key); 82 | return cast(CET) tree.equalRange(tme).front.value; 83 | } 84 | 85 | /** 86 | * Returns: the value associated with the given key, or the given `defaultValue`. 87 | */ 88 | auto get(this This)(const K key, lazy V defaultValue) inout @trusted 89 | { 90 | alias CET = ContainerElementType!(This, V); 91 | auto tme = TreeMapElement(key); 92 | auto er = tree.equalRange(tme); 93 | if (er.empty) 94 | return cast(CET) defaultValue; 95 | else 96 | return cast(CET) er.front.value; 97 | } 98 | 99 | /** 100 | * If the given key does not exist in the TreeMap, adds it with 101 | * the value `defaultValue`. 102 | * 103 | * Params: 104 | * key = the key to look up 105 | * defaultValue = the default value 106 | * 107 | * Returns: A pointer to the existing value, or a pointer to the inserted 108 | * value. 109 | */ 110 | auto getOrAdd(this This)(const K key, lazy V defaultValue) 111 | { 112 | alias CET = ContainerElementType!(This, V); 113 | auto tme = TreeMapElement(key); 114 | auto er = tree.equalRange(tme); 115 | if (er.empty) 116 | { 117 | // TODO: This does two lookups and should be made faster. 118 | tree.insert(TreeMapElement(key, defaultValue)); 119 | return cast(CET*) &tree.equalRange(tme)._containersFront().value; 120 | } 121 | else 122 | { 123 | return cast(CET*) &er._containersFront().value; 124 | } 125 | } 126 | 127 | /** 128 | * Removes the key→value mapping for the given key. 129 | * 130 | * Params: key = the key to remove 131 | * Returns: true if the key existed in the map 132 | */ 133 | bool remove(const K key) 134 | { 135 | auto tme = TreeMapElement(cast(ContainerStorageType!K) key); 136 | return tree.remove(tme); 137 | } 138 | 139 | /** 140 | * Returns: true if the mapping contains the given key 141 | */ 142 | bool containsKey(const K key) inout pure nothrow @nogc @trusted 143 | { 144 | auto tme = TreeMapElement(cast(ContainerStorageType!K) key); 145 | return tree.contains(tme); 146 | } 147 | 148 | /** 149 | * Returns: true if the mapping is empty 150 | */ 151 | bool empty() const pure nothrow @property @safe @nogc 152 | { 153 | return tree.empty; 154 | } 155 | 156 | /** 157 | * Returns: the number of key→value pairs in the map 158 | */ 159 | size_t length() inout pure nothrow @property @safe @nogc 160 | { 161 | return tree.length; 162 | } 163 | 164 | /** 165 | * Returns: a GC-allocated array of the keys in the map 166 | */ 167 | auto keys(this This)() inout pure @property @trusted 168 | { 169 | import std.array : array; 170 | 171 | return byKey!(This)().array(); 172 | } 173 | 174 | /** 175 | * Returns: a range of the keys in the map 176 | */ 177 | auto byKey(this This)() inout pure @trusted @nogc 178 | { 179 | import std.algorithm.iteration : map; 180 | alias CETK = ContainerElementType!(This, K); 181 | 182 | return tree[].map!(a => cast(CETK) a.key); 183 | } 184 | 185 | /** 186 | * Returns: a GC-allocated array of the values in the map 187 | */ 188 | auto values(this This)() inout pure @property @trusted 189 | { 190 | import std.array : array; 191 | 192 | return byValue!(This)().array(); 193 | } 194 | 195 | /** 196 | * Returns: a range of the values in the map 197 | */ 198 | auto byValue(this This)() inout pure @trusted @nogc 199 | { 200 | import std.algorithm.iteration : map; 201 | alias CETV = ContainerElementType!(This, V); 202 | 203 | return tree[].map!(a => cast(CETV) a.value); 204 | } 205 | 206 | /// ditto 207 | alias opSlice = byValue; 208 | 209 | /** 210 | * Returns: a range of the kev/value pairs in this map. The element type of 211 | * this range is a struct with `key` and `value` fields. 212 | */ 213 | auto byKeyValue(this This)() inout pure @trusted 214 | { 215 | import std.algorithm.iteration : map; 216 | alias CETV = ContainerElementType!(This, V); 217 | 218 | struct KeyValue 219 | { 220 | const K key; 221 | CETV value; 222 | } 223 | 224 | return tree[].map!(n => KeyValue(n.key, cast(CETV) n.value)); 225 | } 226 | 227 | /** 228 | * Returns: The value associated with the first key in the map. 229 | */ 230 | auto front(this This)() inout pure @trusted 231 | { 232 | alias CETV = ContainerElementType!(This, V); 233 | 234 | return cast(CETV) tree.front.value; 235 | } 236 | 237 | /** 238 | * Returns: The value associated with the last key in the map. 239 | */ 240 | auto back(this This)() inout pure @trusted 241 | { 242 | alias CETV = ContainerElementType!(This, V); 243 | 244 | return cast(CETV) tree.back.value; 245 | } 246 | 247 | private: 248 | 249 | import containers.ttree : TTree; 250 | import containers.internal.storage_type : ContainerStorageType; 251 | import containers.internal.element_type : ContainerElementType; 252 | 253 | enum bool useGC = supportGC && (shouldAddGCRange!K || shouldAddGCRange!V); 254 | 255 | static struct TreeMapElement 256 | { 257 | ContainerStorageType!K key; 258 | ContainerStorageType!V value; 259 | int opCmp(ref const TreeMapElement other) const 260 | { 261 | import std.functional : binaryFun; 262 | return binaryFun!less(key, other.key); 263 | } 264 | } 265 | 266 | alias TreeType = TTree!(TreeMapElement, Allocator, false, "a.opCmp(b) > 0", useGC, cacheLineSize); 267 | TreeType tree; 268 | } 269 | 270 | version(emsi_containers_unittest) @system unittest 271 | { 272 | TreeMap!(string, string) tm; 273 | tm["test1"] = "hello"; 274 | tm["test2"] = "world"; 275 | assert(tm.get("test1", "something") == "hello"); 276 | tm.remove("test1"); 277 | tm.remove("test2"); 278 | assert(tm.length == 0); 279 | assert(tm.empty); 280 | assert(tm.get("test4", "something") == "something"); 281 | assert(tm.get("test4", "something") == "something"); 282 | } 283 | 284 | version(emsi_containers_unittest) unittest 285 | { 286 | import std.range.primitives : walkLength; 287 | import std.stdio : stdout; 288 | import std.experimental.allocator.building_blocks.allocator_list : AllocatorList; 289 | import std.experimental.allocator.building_blocks.free_list : FreeList; 290 | import std.experimental.allocator.building_blocks.region : Region; 291 | import std.experimental.allocator.building_blocks.stats_collector : StatsCollector; 292 | 293 | StatsCollector!(FreeList!(AllocatorList!(a => Region!(Mallocator)(1024 * 1024)), 294 | 64)) allocator; 295 | { 296 | auto intMap = TreeMap!(int, int, typeof(&allocator))(&allocator); 297 | foreach (i; 0 .. 10_000) 298 | intMap[i] = 10_000 - i; 299 | assert(intMap.length == 10_000); 300 | } 301 | assert(allocator.numAllocate == allocator.numDeallocate); 302 | assert(allocator.bytesUsed == 0); 303 | } 304 | 305 | version(emsi_containers_unittest) unittest 306 | { 307 | import std.algorithm.comparison : equal; 308 | import std.algorithm.iteration : each; 309 | import std.range : repeat, take; 310 | 311 | TreeMap!(int, int) tm; 312 | int[] a = [1, 2, 3, 4, 5]; 313 | a.each!(a => tm[a] = 0); 314 | assert(equal(tm.keys, a)); 315 | assert(equal(tm.values, repeat(0).take(a.length))); 316 | } 317 | 318 | version(emsi_containers_unittest) unittest 319 | { 320 | static class Foo 321 | { 322 | string name; 323 | } 324 | 325 | TreeMap!(string, Foo) tm; 326 | auto f = new Foo; 327 | tm["foo"] = f; 328 | } 329 | 330 | 331 | version(emsi_containers_unittest) unittest 332 | { 333 | import std.uuid : randomUUID; 334 | import std.range.primitives : walkLength; 335 | 336 | auto hm = TreeMap!(string, int)(); 337 | assert (hm.length == 0); 338 | assert (!hm.remove("abc")); 339 | hm["answer"] = 42; 340 | assert (hm.length == 1); 341 | assert (hm.containsKey("answer")); 342 | hm.remove("answer"); 343 | assert (hm.length == 0); 344 | hm["one"] = 1; 345 | hm["one"] = 1; 346 | assert (hm.length == 1); 347 | assert (hm["one"] == 1); 348 | hm["one"] = 2; 349 | assert(hm["one"] == 2); 350 | foreach (i; 0 .. 1000) 351 | { 352 | hm[randomUUID().toString] = i; 353 | } 354 | assert (hm.length == 1001); 355 | assert (hm.keys().length == hm.length); 356 | assert (hm.values().length == hm.length); 357 | () @nogc { 358 | assert (hm.byKey().walkLength == hm.length); 359 | assert (hm.byValue().walkLength == hm.length); 360 | assert (hm[].walkLength == hm.length); 361 | assert (hm.byKeyValue().walkLength == hm.length); 362 | }(); 363 | foreach (v; hm) {} 364 | 365 | auto hm2 = TreeMap!(char, char)(); 366 | hm2['a'] = 'a'; 367 | 368 | TreeMap!(int, int) hm3; 369 | assert (hm3.get(100, 20) == 20); 370 | hm3[100] = 1; 371 | assert (hm3.get(100, 20) == 1); 372 | auto pValue = hm3.containsKey(100); 373 | assert(pValue == 1); 374 | } 375 | 376 | version(emsi_containers_unittest) unittest 377 | { 378 | static class Foo 379 | { 380 | string name; 381 | } 382 | 383 | void someFunc(const scope ref TreeMap!(string,Foo) map) @safe 384 | { 385 | foreach (kv; map.byKeyValue()) 386 | { 387 | assert (kv.key == "foo"); 388 | assert (kv.value.name == "Foo"); 389 | } 390 | } 391 | 392 | auto hm = TreeMap!(string, Foo)(); 393 | auto f = new Foo; 394 | f.name = "Foo"; 395 | hm.insert("foo", f); 396 | assert(hm.containsKey("foo")); 397 | } 398 | 399 | // Issue #54 400 | version(emsi_containers_unittest) unittest 401 | { 402 | TreeMap!(string, int) map; 403 | map.insert("foo", 0); 404 | map.insert("bar", 0); 405 | 406 | foreach (key; map.keys()) 407 | map[key] = 1; 408 | foreach (key; map.byKey()) 409 | map[key] = 1; 410 | 411 | foreach (value; map.byValue()) 412 | assert(value == 1); 413 | foreach (value; map.values()) 414 | assert(value == 1); 415 | } 416 | 417 | version(emsi_containers_unittest) unittest 418 | { 419 | TreeMap!(int, int) map; 420 | auto p = map.getOrAdd(1, 1); 421 | assert(*p == 1); 422 | } 423 | 424 | version(emsi_containers_unittest) unittest 425 | { 426 | import std.uuid : randomUUID; 427 | import std.range.primitives : walkLength; 428 | //import std.stdio; 429 | 430 | auto hm = TreeMap!(string, int)(); 431 | foreach (i; 0 .. 1_000_000) 432 | { 433 | auto str = randomUUID().toString; 434 | //writeln("Inserting ", str); 435 | hm[str] = i; 436 | //if (i > 0 && i % 100 == 0) 437 | //writeln(i); 438 | } 439 | //writeln(hm.buckets.length); 440 | 441 | import std.algorithm.sorting:sort; 442 | //ulong[ulong] counts; 443 | //foreach (i, ref bucket; hm.buckets[]) 444 | //counts[bucket.length]++; 445 | //foreach (k; counts.keys.sort()) 446 | //writeln(k, "=>", counts[k]); 447 | } 448 | -------------------------------------------------------------------------------- /src/containers/unrolledlist.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Unrolled Linked List. 3 | * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 | * Authors: Brian Schott 5 | * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 | */ 7 | 8 | module containers.unrolledlist; 9 | 10 | private import core.lifetime : move; 11 | private import containers.internal.node : shouldAddGCRange; 12 | private import std.experimental.allocator.mallocator : Mallocator; 13 | 14 | version (X86_64) 15 | version (LDC) 16 | version = LDC_64; 17 | 18 | /** 19 | * Unrolled Linked List. 20 | * 21 | * Nodes are (by default) sized to fit within a 64-byte cache line. The number 22 | * of items stored per node can be read from the $(B nodeCapacity) field. 23 | * See_also: $(LINK http://en.wikipedia.org/wiki/Unrolled_linked_list) 24 | * Params: 25 | * T = the element type 26 | * Allocator = the allocator to use. Defaults to `Mallocator`. 27 | * supportGC = true to ensure that the GC scans the nodes of the unrolled 28 | * list, false if you are sure that no references to GC-managed memory 29 | * will be stored in this container. 30 | * cacheLineSize = Nodes will be sized to fit within this number of bytes. 31 | */ 32 | struct UnrolledList(T, Allocator = Mallocator, 33 | bool supportGC = shouldAddGCRange!T, size_t cacheLineSize = 64) 34 | { 35 | this(this) @disable; 36 | 37 | private import std.experimental.allocator.common : stateSize; 38 | 39 | static if (stateSize!Allocator != 0) 40 | { 41 | /// No default construction if an allocator must be provided. 42 | this() @disable; 43 | 44 | /** 45 | * Use the given `allocator` for allocations. 46 | */ 47 | this(Allocator allocator) 48 | in 49 | { 50 | static if (is(typeof(allocator is null))) 51 | assert(allocator !is null, "Allocator must not be null"); 52 | } 53 | do 54 | { 55 | this.allocator = allocator; 56 | } 57 | } 58 | 59 | ~this() nothrow 60 | { 61 | scope (failure) assert(false, "UnrolledList destructor threw an exception"); 62 | clear(); 63 | } 64 | 65 | /** 66 | * Removes all items from the list 67 | */ 68 | void clear() 69 | { 70 | Node* previous; 71 | Node* current = _front; 72 | while (current !is null) 73 | { 74 | previous = current; 75 | current = current.next; 76 | 77 | static if (useGC) 78 | { 79 | import core.memory: GC; 80 | GC.removeRange(previous); 81 | } 82 | allocator.dispose(previous); 83 | } 84 | _length = 0; 85 | _front = null; 86 | _back = null; 87 | } 88 | 89 | /** 90 | * Inserts the given item into the end of the list. 91 | * 92 | * Returns: a pointer to the inserted item. 93 | */ 94 | T* insertBack(T item) 95 | { 96 | ContainerStorageType!T* result; 97 | if (_back is null) 98 | { 99 | assert (_front is null, "front/back nullness mismatch"); 100 | _back = allocateNode(move(mutable(item))); 101 | _front = _back; 102 | result = &_back.items[0]; 103 | } 104 | else 105 | { 106 | size_t index = _back.nextAvailableIndex(); 107 | if (index >= nodeCapacity) 108 | { 109 | Node* n = allocateNode(move(mutable(item))); 110 | n.prev = _back; 111 | _back.next = n; 112 | _back = n; 113 | index = 0; 114 | result = &n.items[0]; 115 | } 116 | else 117 | { 118 | _back.items[index] = move(mutable(item)); 119 | _back.markUsed(index); 120 | result = &_back.items[index]; 121 | } 122 | } 123 | _length++; 124 | assert (_back.registry <= fullBitPattern, "Overflow"); 125 | return cast(T*) result; 126 | } 127 | 128 | /** 129 | * Inserts the given range into the end of the list 130 | */ 131 | void insertBack(R)(auto ref R range) 132 | { 133 | foreach (ref r; range) 134 | insertBack(r); 135 | } 136 | 137 | /// ditto 138 | T* opOpAssign(string op)(T item) if (op == "~") 139 | { 140 | return insertBack(item); 141 | } 142 | 143 | /// ditto 144 | alias put = insertBack; 145 | 146 | /// ditto 147 | alias insert = insertBack; 148 | 149 | /** 150 | * Inserts the given item in the frontmost available cell, which may put the 151 | * item anywhere in the list as removal may leave gaps in list nodes. Use 152 | * this only if the order of elements is not important. 153 | * 154 | * Returns: a pointer to the inserted item. 155 | */ 156 | T* insertAnywhere(T item) @trusted 157 | { 158 | Node* n = _front; 159 | while (n !is null) 160 | { 161 | size_t i = n.nextAvailableIndex(); 162 | if (i >= nodeCapacity) 163 | { 164 | if (n.next is null) 165 | { 166 | assert (n is _back, "Wrong _back"); 167 | break; 168 | } 169 | n = n.next; 170 | continue; 171 | } 172 | n.items[i] = move(mutable(item)); 173 | n.markUsed(i); 174 | _length++; 175 | assert (n.registry <= fullBitPattern, "Overflow"); 176 | return cast(T*) &n.items[i]; 177 | } 178 | n = allocateNode(move(mutable(item))); 179 | _length++; 180 | auto retVal = cast(T*) &n.items[0]; 181 | if (_front is null) 182 | { 183 | assert(_back is null, "front/back nullness mismatch"); 184 | _front = n; 185 | } 186 | else 187 | { 188 | n.prev = _back; 189 | _back.next = n; 190 | } 191 | _back = n; 192 | assert (_back.registry <= fullBitPattern, "Overflow"); 193 | return retVal; 194 | } 195 | 196 | /// Returns: the length of the list 197 | size_t length() const nothrow pure @property @safe @nogc 198 | { 199 | return _length; 200 | } 201 | 202 | /// Returns: true if the list is empty 203 | bool empty() const nothrow pure @property @safe @nogc 204 | { 205 | return _length == 0; 206 | } 207 | 208 | /** 209 | * Removes the first instance of the given item from the list. 210 | * 211 | * Returns: true if something was removed. 212 | */ 213 | bool remove(ref const T item) 214 | { 215 | if (_front is null) 216 | return false; 217 | bool retVal; 218 | loop: for (Node* n = _front; n !is null; n = n.next) 219 | { 220 | foreach (i; 0 .. nodeCapacity) 221 | { 222 | if (!n.isFree(i) && n.items[i] == item) 223 | { 224 | n.markUnused(i); 225 | --_length; 226 | retVal = true; 227 | if (n.registry == 0) 228 | deallocateNode(n); 229 | else if (shouldMerge(n, n.next)) 230 | mergeNodes(n, n.next); 231 | else if (shouldMerge(n.prev, n)) 232 | mergeNodes(n.prev, n); 233 | break loop; 234 | } 235 | } 236 | } 237 | return retVal; 238 | } 239 | bool remove(const T item) { return remove(item); } 240 | 241 | /// Pops the front item off of the list 242 | void popFront() 243 | { 244 | moveFront(); 245 | assert (_front is null || _front.registry != 0, "Node is non-null but empty"); 246 | } 247 | 248 | /// Pops the front item off of the list and returns it 249 | T moveFront() 250 | in 251 | { 252 | assert (!empty(), "Accessing .moveFront of empty UnrolledList"); 253 | assert (_front.registry != 0, "Empty node"); 254 | } 255 | do 256 | { 257 | version (LDC_64) 258 | { 259 | import ldc.intrinsics : llvm_cttz; 260 | size_t index = llvm_cttz(_front.registry, true); 261 | } 262 | else 263 | { 264 | import containers.internal.backwards : bsf; 265 | size_t index = bsf(_front.registry); 266 | } 267 | T r = move(_front.items[index]); 268 | _front.markUnused(index); 269 | _length--; 270 | if (_front.registry == 0) 271 | { 272 | auto f = _front; 273 | if (_front.next !is null) 274 | _front.next.prev = null; 275 | assert (_front.next !is _front, "Infinite loop"); 276 | _front = _front.next; 277 | if (_front is null) 278 | _back = null; 279 | else 280 | assert (_front.registry <= fullBitPattern, "Overflow"); 281 | deallocateNode(f); 282 | return r; 283 | } 284 | if (shouldMerge(_front, _front.next)) 285 | mergeNodes(_front, _front.next); 286 | return r; 287 | } 288 | 289 | debug (EMSI_CONTAINERS) invariant 290 | { 291 | import std.string: format; 292 | assert (_front is null || _front.registry != 0, format("%x, %b", _front, _front.registry)); 293 | assert (_front !is null || _back is null, "_front/_back nullness mismatch"); 294 | if (_front !is null) 295 | { 296 | const(Node)* c = _front; 297 | while (c.next !is null) 298 | c = c.next; 299 | assert(c is _back, "_back pointer is wrong"); 300 | } 301 | } 302 | 303 | /** 304 | * Time complexity is O(1) 305 | * Returns: the item at the front of the list 306 | */ 307 | ref inout(T) front() inout nothrow @property 308 | in 309 | { 310 | assert (!empty, "Accessing .front of empty UnrolledList"); 311 | assert (_front.registry != 0, "Empty node"); 312 | } 313 | do 314 | { 315 | version (LDC_64) 316 | { 317 | import ldc.intrinsics : llvm_cttz; 318 | immutable index = llvm_cttz(_front.registry, true); 319 | } 320 | else 321 | { 322 | import containers.internal.backwards : bsf; 323 | immutable index = bsf(_front.registry); 324 | } 325 | return *(cast(typeof(return)*) &_front.items[index]); 326 | } 327 | 328 | /** 329 | * Time complexity is O(nodeCapacity), where the nodeCapacity 330 | * is the number of items in a single list node. It is a constant 331 | * related to the cache line size. 332 | * Returns: the item at the back of the list 333 | */ 334 | ref inout(T) back() inout nothrow @property 335 | in 336 | { 337 | assert (!empty, "Accessing .back of empty UnrolledList"); 338 | assert (!_back.empty, "Empty node"); 339 | } 340 | do 341 | { 342 | size_t i = nodeCapacity - 1; 343 | while (_back.isFree(i)) 344 | i--; 345 | return *(cast(typeof(return)*) &_back.items[i]); 346 | } 347 | 348 | /// Pops the back item off of the list. 349 | void popBack() 350 | { 351 | moveBack(); 352 | } 353 | 354 | /// Removes an item from the back of the list and returns it. 355 | T moveBack() 356 | in 357 | { 358 | assert (!empty, "Accessing .moveBack of empty UnrolledList"); 359 | assert (!_back.empty, "Empty node"); 360 | } 361 | do 362 | { 363 | size_t i = nodeCapacity - 1; 364 | while (_back.isFree(i)) 365 | { 366 | if (i == 0) 367 | break; 368 | else 369 | i--; 370 | } 371 | assert (!_back.isFree(i), "Empty node"); 372 | T item = move(_back.items[i]); 373 | _back.markUnused(i); 374 | _length--; 375 | if (_back.registry == 0) 376 | { 377 | deallocateNode(_back); 378 | return item; 379 | } 380 | else if (shouldMerge(_back.prev, _back)) 381 | mergeNodes(_back.prev, _back); 382 | return item; 383 | } 384 | 385 | /// Returns: a range over the list 386 | auto opSlice(this This)() const nothrow pure @nogc @trusted 387 | { 388 | return Range!(This)(_front); 389 | } 390 | 391 | static struct Range(ThisT) 392 | { 393 | @disable this(); 394 | 395 | this(inout(Node)* current) 396 | { 397 | import std.format:format; 398 | 399 | this.current = current; 400 | if (current !is null) 401 | { 402 | version(LDC_64) 403 | { 404 | import ldc.intrinsics : llvm_cttz; 405 | index = llvm_cttz(current.registry, true); 406 | } 407 | else 408 | { 409 | import containers.internal.backwards : bsf; 410 | index = bsf(current.registry); 411 | } 412 | 413 | assert (index < nodeCapacity); 414 | } 415 | else 416 | current = null; 417 | } 418 | 419 | ref ET front() const nothrow @property @trusted @nogc 420 | { 421 | return *(cast(ET*) ¤t.items[index]); 422 | //return cast(T) current.items[index]; 423 | } 424 | 425 | void popFront() nothrow pure @safe @nogc 426 | { 427 | index++; 428 | while (true) 429 | { 430 | 431 | if (index >= nodeCapacity) 432 | { 433 | current = current.next; 434 | if (current is null) 435 | return; 436 | index = 0; 437 | } 438 | else 439 | { 440 | if (current.isFree(index)) 441 | index++; 442 | else 443 | return; 444 | } 445 | } 446 | } 447 | 448 | bool empty() const nothrow pure @property @safe @nogc 449 | { 450 | return current is null; 451 | } 452 | 453 | Range save() const nothrow pure @property @safe @nogc 454 | { 455 | return this; 456 | } 457 | 458 | private: 459 | 460 | alias ET = ContainerElementType!(ThisT, T); 461 | const(Node)* current; 462 | size_t index; 463 | } 464 | 465 | private: 466 | 467 | import std.experimental.allocator: make, dispose; 468 | import containers.internal.node : FatNodeInfo, shouldAddGCRange, 469 | fullBits, shouldNullSlot; 470 | import containers.internal.storage_type : ContainerStorageType; 471 | import containers.internal.element_type : ContainerElementType; 472 | import containers.internal.mixins : AllocatorState; 473 | 474 | alias N = FatNodeInfo!(T.sizeof, 2, cacheLineSize); 475 | enum size_t nodeCapacity = N[0]; 476 | alias BookkeepingType = N[1]; 477 | enum fullBitPattern = fullBits!(BookkeepingType, nodeCapacity); 478 | enum bool useGC = supportGC && shouldAddGCRange!T; 479 | 480 | static ref ContainerStorageType!T mutable(ref T value) { return *cast(ContainerStorageType!T*)&value; } 481 | 482 | Node* _back; 483 | Node* _front; 484 | size_t _length; 485 | mixin AllocatorState!Allocator; 486 | 487 | Node* allocateNode(T item) 488 | { 489 | Node* n = make!Node(allocator); 490 | static if (useGC) 491 | { 492 | import core.memory: GC; 493 | GC.addRange(n, Node.sizeof); 494 | } 495 | n.items[0] = move(mutable(item)); 496 | n.markUsed(0); 497 | return n; 498 | } 499 | 500 | void deallocateNode(Node* n) 501 | { 502 | if (n.prev !is null) 503 | n.prev.next = n.next; 504 | if (n.next !is null) 505 | n.next.prev = n.prev; 506 | if (_front is n) 507 | _front = n.next; 508 | if (_back is n) 509 | _back = n.prev; 510 | 511 | static if (useGC) 512 | { 513 | import core.memory: GC; 514 | GC.removeRange(n); 515 | } 516 | allocator.dispose(n); 517 | } 518 | 519 | static bool shouldMerge(const Node* first, const Node* second) 520 | { 521 | if (first is null || second is null) 522 | return false; 523 | version (LDC_64) 524 | { 525 | import ldc.intrinsics : llvm_ctpop; 526 | 527 | immutable f = llvm_ctpop(first.registry); 528 | immutable s = llvm_ctpop(second.registry); 529 | } 530 | else 531 | { 532 | import containers.internal.backwards : popcnt; 533 | 534 | immutable f = popcnt(first.registry); 535 | immutable s = popcnt(second.registry); 536 | } 537 | return f + s <= nodeCapacity; 538 | } 539 | 540 | void mergeNodes(Node* first, Node* second) 541 | in 542 | { 543 | assert (first !is null, "Invalid merge"); 544 | assert (second !is null, "Invalid merge"); 545 | assert (second is first.next, "Invalid merge"); 546 | } 547 | do 548 | { 549 | size_t i; 550 | ContainerStorageType!T[nodeCapacity] temp; 551 | foreach (j; 0 .. nodeCapacity) 552 | if (!first.isFree(j)) 553 | temp[i++] = move(first.items[j]); 554 | foreach (j; 0 .. nodeCapacity) 555 | if (!second.isFree(j)) 556 | temp[i++] = move(second.items[j]); 557 | foreach (j; 0 .. i) 558 | first.items[j] = move(temp[j]); 559 | first.registry = 0; 560 | foreach (k; 0 .. i) 561 | first.markUsed(k); 562 | assert (first.registry <= fullBitPattern, "Overflow"); 563 | deallocateNode(second); 564 | } 565 | 566 | static struct Node 567 | { 568 | size_t nextAvailableIndex() const nothrow pure @safe @nogc 569 | { 570 | static if (BookkeepingType.sizeof < uint.sizeof) 571 | immutable uint notReg = ~(cast(uint) registry); 572 | else 573 | immutable auto notReg = ~registry; 574 | version (LDC_64) 575 | { 576 | import ldc.intrinsics : llvm_cttz; 577 | return llvm_cttz(notReg, true); 578 | } 579 | else 580 | { 581 | import containers.internal.backwards : bsf; 582 | return bsf(notReg); 583 | } 584 | } 585 | 586 | void markUsed(size_t index) nothrow pure @safe @nogc 587 | { 588 | registry |= (BookkeepingType(1) << index); 589 | } 590 | 591 | void markUnused(size_t index) nothrow pure @safe @nogc 592 | { 593 | registry &= ~(BookkeepingType(1) << index); 594 | static if (shouldNullSlot!T) 595 | items[index] = null; 596 | } 597 | 598 | bool empty() const nothrow pure @safe @nogc 599 | { 600 | return registry == 0; 601 | } 602 | 603 | bool isFree(size_t index) const nothrow pure @safe @nogc 604 | { 605 | return (registry & (BookkeepingType(1) << index)) == 0; 606 | } 607 | 608 | debug(EMSI_CONTAINERS) invariant() 609 | { 610 | import std.string : format; 611 | assert (registry <= fullBitPattern, format("%016b %016b", registry, fullBitPattern)); 612 | assert (prev !is &this, "Infinite loop"); 613 | assert (next !is &this, "Infinite loop"); 614 | } 615 | 616 | BookkeepingType registry; 617 | ContainerStorageType!T[nodeCapacity] items; 618 | Node* prev; 619 | Node* next; 620 | } 621 | } 622 | 623 | version(emsi_containers_unittest) unittest 624 | { 625 | import std.algorithm : equal; 626 | import std.range : iota; 627 | import std.string : format; 628 | UnrolledList!ubyte l; 629 | static assert (l.Node.sizeof <= 64); 630 | assert (l.empty); 631 | l.insert(0); 632 | assert (l.length == 1); 633 | assert (!l.empty); 634 | foreach (i; 1 .. 100) 635 | l.insert(cast(ubyte) i); 636 | assert (l.length == 100); 637 | assert (equal(l[], iota(100))); 638 | foreach (i; 0 .. 100) 639 | assert (l.remove(cast(ubyte) i), format("%d", i)); 640 | assert (l.length == 0, format("%d", l.length)); 641 | assert (l.empty); 642 | 643 | assert(*l.insert(1) == 1); 644 | assert(*l.insert(2) == 2); 645 | assert (l.remove(1)); 646 | assert (!l.remove(1)); 647 | assert (!l.empty); 648 | 649 | UnrolledList!ubyte l2; 650 | l2.insert(1); 651 | l2.insert(2); 652 | l2.insert(3); 653 | assert (l2.front == 1); 654 | l2.popFront(); 655 | assert (l2.front == 2); 656 | assert (equal(l2[], [2, 3])); 657 | l2.popFront(); 658 | assert (equal(l2[], [3])); 659 | l2.popFront(); 660 | assert (l2.empty, format("%d", l2.front)); 661 | assert (equal(l2[], cast(int[]) [])); 662 | UnrolledList!int l3; 663 | foreach (i; 0 .. 200) 664 | l3.insert(i); 665 | foreach (i; 0 .. 200) 666 | { 667 | auto x = l3.moveFront(); 668 | assert (x == i, format("%d %d", i, x)); 669 | } 670 | assert (l3.empty); 671 | foreach (i; 0 .. 200) 672 | l3.insert(i); 673 | assert (l3.length == 200); 674 | foreach (i; 0 .. 200) 675 | { 676 | assert (l3.length == 200 - i); 677 | auto x = l3.moveBack(); 678 | assert (x == 200 - i - 1, format("%d %d", 200 - 1 - 1, x)); 679 | } 680 | assert (l3.empty); 681 | } 682 | 683 | version(emsi_containers_unittest) unittest 684 | { 685 | struct A { int a; int b; } 686 | UnrolledList!(const(A)) objs; 687 | objs.insert(A(10, 11)); 688 | static assert (is (typeof(objs.front) == const)); 689 | static assert (is (typeof(objs[].front) == const)); 690 | } 691 | 692 | version(emsi_containers_unittest) unittest 693 | { 694 | static class A 695 | { 696 | int a; 697 | int b; 698 | 699 | this(int a, int b) 700 | { 701 | this.a = a; 702 | this.b = b; 703 | } 704 | } 705 | 706 | UnrolledList!(A) objs; 707 | objs.insert(new A(10, 11)); 708 | } 709 | 710 | // Issue #52 711 | version(emsi_containers_unittest) unittest 712 | { 713 | UnrolledList!int list; 714 | list.insert(0); 715 | list.insert(0); 716 | list.insert(0); 717 | list.insert(0); 718 | list.insert(0); 719 | 720 | foreach (ref it; list[]) 721 | it = 1; 722 | 723 | foreach (it; list[]) 724 | assert(it == 1); 725 | } 726 | 727 | // Issue #53 728 | version(emsi_containers_unittest) unittest 729 | { 730 | UnrolledList!int ints; 731 | ints.insertBack(0); 732 | ints.insertBack(0); 733 | 734 | ints.front = 1; 735 | ints.back = 11; 736 | 737 | assert(ints.front == 1); 738 | assert(ints.back == 11); 739 | } 740 | 741 | // Issue #168 742 | version(emsi_containers_unittest) unittest 743 | { 744 | import std.typecons : RefCounted; 745 | alias E = RefCounted!int; 746 | 747 | E e = E(12); 748 | UnrolledList!E ints; 749 | ints.insertBack(e); 750 | ints.clear(); 751 | // crucial: no assert failure 752 | assert (e == 12); 753 | } 754 | 755 | // Issue #170 756 | version(emsi_containers_unittest) unittest 757 | { 758 | static struct S { @disable this(this); } 759 | UnrolledList!S list; 760 | 761 | list.insert(S()); 762 | list.remove(S()); 763 | 764 | list.insert(S()); 765 | S s; 766 | list.remove(s); 767 | } 768 | -------------------------------------------------------------------------------- /test/compile_test.d: -------------------------------------------------------------------------------- 1 | import containers.cyclicbuffer; 2 | import containers.dynamicarray; 3 | import containers.hashmap; 4 | import containers.hashset; 5 | import containers.openhashset; 6 | import containers.simdset; 7 | import containers.slist; 8 | import containers.treemap; 9 | import containers.ttree; 10 | import containers.unrolledlist; 11 | 12 | private void testContainerSingle(alias Container)() 13 | { 14 | testContainerSingleVal!(Container)(); 15 | testContainerSingleRef!(Container)(); 16 | } 17 | 18 | private void testContainerSingleVal(alias Container)() 19 | { 20 | Container!(int) mm; 21 | Container!(const int) mc; 22 | Container!(immutable int) mi; 23 | 24 | const Container!(int) cm; 25 | const Container!(const int) cc; 26 | const Container!(immutable int) ci; 27 | 28 | immutable Container!(int) im; 29 | immutable Container!(const int) ic; 30 | immutable Container!(immutable int) ii; 31 | 32 | checkSliceFunctionality!(int)(mm); 33 | checkSliceFunctionality!(const int)(mc); 34 | checkSliceFunctionality!(immutable int)(mi); 35 | 36 | checkSliceFunctionality!(int)(cm); 37 | checkSliceFunctionality!(const int)(cc); 38 | checkSliceFunctionality!(immutable int)(ci); 39 | 40 | checkSliceFunctionality!(int)(im); 41 | checkSliceFunctionality!(const int)(ic); 42 | checkSliceFunctionality!(immutable int)(ii); 43 | 44 | static struct NC { @disable this(this); } 45 | debug(check_compliance) static if (!is(Container!NC)) pragma(msg, __traits(identifier, Container) ~ " does not support non-copyable types"); 46 | 47 | static struct NI { @disable this(); } 48 | debug(check_compliance) static if (!is(Container!NI)) pragma(msg, __traits(identifier, Container) ~ " does not support non-constructable types"); 49 | 50 | static struct ND { @disable ~this() {} } 51 | debug(check_compliance) static if (!is(Container!ND)) pragma(msg, __traits(identifier, Container) ~ " does not support non-destructible types"); 52 | } 53 | 54 | private void testContainerSingleRef(alias Container)() 55 | { 56 | Container!(int*) mm; 57 | Container!(const int*) mc; 58 | Container!(immutable int*) mi; 59 | 60 | const Container!(int*) cm; 61 | const Container!(const int*) cc; 62 | const Container!(immutable int*) ci; 63 | 64 | immutable Container!(immutable int*) ii; 65 | 66 | checkSliceFunctionality!(int*)(mm); 67 | checkSliceFunctionality!(const int*)(mc); 68 | checkSliceFunctionality!(immutable int*)(mi); 69 | 70 | checkSliceFunctionality!(const(int)*)(cm); 71 | checkSliceFunctionality!(const int*)(cc); 72 | checkSliceFunctionality!(immutable int*)(ci); 73 | 74 | checkSliceFunctionality!(immutable int*)(ii); 75 | } 76 | 77 | private void testContainerDouble(alias Container)() 78 | { 79 | testContainerDoubleVal!(Container)(); 80 | testContainerDoubleRef!(Container)(); 81 | testContainerDoubleAggregateKey!(Container)(); 82 | } 83 | 84 | private void testContainerDoubleAggregateKey(alias Container)() 85 | { 86 | static struct KeyType 87 | { 88 | int a; 89 | string[] c; 90 | 91 | int opCmp(ref const KeyType other) const 92 | { 93 | if (other.a < a) 94 | return -1; 95 | return other.a > a; 96 | } 97 | 98 | size_t toHash() const 99 | { 100 | return 10; 101 | } 102 | 103 | bool opEquals(ref const KeyType other) const 104 | { 105 | return a == other.a; 106 | } 107 | } 108 | 109 | Container!(const KeyType, int) cm; 110 | 111 | Container!(immutable KeyType, int) im; 112 | 113 | checkIndexFunctionality!(int, const KeyType)(cm); 114 | 115 | checkIndexFunctionality!(int, const KeyType)(im); 116 | 117 | checkSliceFunctionality!(int)(cm); 118 | 119 | checkSliceFunctionality!(int)(im); 120 | } 121 | 122 | private void testContainerDoubleVal(alias Container)() 123 | { 124 | { 125 | Container!(int, int) mmm; 126 | Container!(int, const int) mmc; 127 | Container!(int, immutable int) mmi; 128 | 129 | Container!(const int, int) mcm; 130 | Container!(const int, const int) mcc; 131 | Container!(const int, immutable int) mci; 132 | 133 | Container!(immutable int, int) mim; 134 | Container!(immutable int, const int) mic; 135 | Container!(immutable int, immutable int) mii; 136 | 137 | checkIndexFunctionality!(int, int)(mmm); 138 | checkIndexFunctionality!(const int, int)(mmc); 139 | checkIndexFunctionality!(immutable int, int)(mmi); 140 | 141 | checkIndexFunctionality!(int, const int)(mcm); 142 | checkIndexFunctionality!(const int, const int)(mcc); 143 | checkIndexFunctionality!(immutable int, const int)(mci); 144 | 145 | checkIndexFunctionality!(int, immutable int)(mim); 146 | checkIndexFunctionality!(const int, immutable int)(mic); 147 | checkIndexFunctionality!(immutable int, immutable int)(mii); 148 | 149 | checkSliceFunctionality!(int)(mmm); 150 | checkSliceFunctionality!(const int)(mmc); 151 | checkSliceFunctionality!(immutable int)(mmi); 152 | 153 | checkSliceFunctionality!(int)(mcm); 154 | checkSliceFunctionality!(const int)(mcc); 155 | checkSliceFunctionality!(immutable int)(mci); 156 | 157 | checkSliceFunctionality!(int)(mim); 158 | checkSliceFunctionality!(const int)(mic); 159 | checkSliceFunctionality!(immutable int)(mii); 160 | } 161 | 162 | { 163 | const Container!(int, int) cmm; 164 | const Container!(int, const int) cmc; 165 | const Container!(int, immutable int) cmi; 166 | 167 | const Container!(const int, int) ccm; 168 | const Container!(const int, const int) ccc; 169 | const Container!(const int, immutable int) cci; 170 | 171 | const Container!(immutable int, int) cim; 172 | const Container!(immutable int, const int) cic; 173 | const Container!(immutable int, immutable int) cii; 174 | 175 | checkIndexFunctionality!(int, int)(cmm); 176 | checkIndexFunctionality!(const int, int)(cmc); 177 | checkIndexFunctionality!(immutable int, int)(cmi); 178 | 179 | checkIndexFunctionality!(int, const int)(ccm); 180 | checkIndexFunctionality!(const int, const int)(ccc); 181 | checkIndexFunctionality!(immutable int, const int)(cci); 182 | 183 | checkIndexFunctionality!(int, immutable int)(cim); 184 | checkIndexFunctionality!(const int, immutable int)(cic); 185 | checkIndexFunctionality!(immutable int, immutable int)(cii); 186 | 187 | checkSliceFunctionality!(int)(cmm); 188 | checkSliceFunctionality!(const int)(cmc); 189 | checkSliceFunctionality!(immutable int)(cmi); 190 | 191 | checkSliceFunctionality!(int)(ccm); 192 | checkSliceFunctionality!(const int)(ccc); 193 | checkSliceFunctionality!(immutable int)(cci); 194 | 195 | checkSliceFunctionality!(int)(cim); 196 | checkSliceFunctionality!(const int)(cic); 197 | checkSliceFunctionality!(immutable int)(cii); 198 | } 199 | 200 | { 201 | immutable Container!(int, int) imm; 202 | immutable Container!(int, const int) imc; 203 | immutable Container!(int, immutable int) imi; 204 | 205 | immutable Container!(const int, int) icm; 206 | immutable Container!(const int, const int) icc; 207 | immutable Container!(const int, immutable int) ici; 208 | 209 | immutable Container!(immutable int, int) iim; 210 | immutable Container!(immutable int, const int) iic; 211 | immutable Container!(immutable int, immutable int) iii; 212 | 213 | checkIndexFunctionality!(int, int)(imm); 214 | checkIndexFunctionality!(const int, int)(imc); 215 | checkIndexFunctionality!(immutable int, int)(imi); 216 | 217 | checkIndexFunctionality!(int, const int)(icm); 218 | checkIndexFunctionality!(const int, const int)(icc); 219 | checkIndexFunctionality!(immutable int, const int)(ici); 220 | 221 | checkIndexFunctionality!(int, immutable int)(iim); 222 | checkIndexFunctionality!(const int, immutable int)(iic); 223 | checkIndexFunctionality!(immutable int, immutable int)(iii); 224 | 225 | checkSliceFunctionality!(int)(imm); 226 | checkSliceFunctionality!(const int)(imc); 227 | checkSliceFunctionality!(immutable int)(imi); 228 | 229 | checkSliceFunctionality!(int)(icm); 230 | checkSliceFunctionality!(const int)(icc); 231 | checkSliceFunctionality!(immutable int)(ici); 232 | 233 | checkSliceFunctionality!(int)(iim); 234 | checkSliceFunctionality!(const int)(iic); 235 | checkSliceFunctionality!(immutable int)(iii); 236 | } 237 | 238 | static struct NC { @disable this(this); } 239 | debug(check_compliance) static if (!is(Container!(NC, int))) pragma(msg, __traits(identifier, Container) ~ " does not support non-copyable keys"); 240 | debug(check_compliance) static if (!is(Container!(int, NC))) pragma(msg, __traits(identifier, Container) ~ " does not support non-copyable values"); 241 | 242 | static struct NI { @disable this(); } 243 | debug(check_compliance) static if (!is(Container!(NI, int))) pragma(msg, __traits(identifier, Container) ~ " does not support non-constructable keys"); 244 | debug(check_compliance) static if (!is(Container!(int, NI))) pragma(msg, __traits(identifier, Container) ~ " does not support non-constructable values"); 245 | 246 | static struct ND { @disable ~this() {} } 247 | debug(check_compliance) static if (!is(Container!(ND, int))) pragma(msg, __traits(identifier, Container) ~ " does not support non-destructable keys"); 248 | debug(check_compliance) static if (!is(Container!(int, ND))) pragma(msg, __traits(identifier, Container) ~ " does not support non-destructable values"); 249 | } 250 | 251 | private void testContainerDoubleRef(alias Container)() 252 | { 253 | { 254 | Container!(int, int*) mmm; 255 | Container!(int, const int*) mmc; 256 | Container!(int, immutable int*) mmi; 257 | 258 | Container!(const int, int*) mcm; 259 | Container!(const int, const int*) mcc; 260 | Container!(const int, immutable int*) mci; 261 | 262 | Container!(immutable int, int*) mim; 263 | Container!(immutable int, const int*) mic; 264 | Container!(immutable int, immutable int*) mii; 265 | 266 | checkIndexFunctionality!(int*, int)(mmm); 267 | checkIndexFunctionality!(const int*, int)(mmc); 268 | checkIndexFunctionality!(immutable int*, int)(mmi); 269 | 270 | checkIndexFunctionality!(int*, const int)(mcm); 271 | checkIndexFunctionality!(const int*, const int)(mcc); 272 | checkIndexFunctionality!(immutable int*, const int)(mci); 273 | 274 | checkIndexFunctionality!(int*, immutable int)(mim); 275 | checkIndexFunctionality!(const int*, immutable int)(mic); 276 | checkIndexFunctionality!(immutable int*, immutable int)(mii); 277 | 278 | checkSliceFunctionality!(int*)(mmm); 279 | checkSliceFunctionality!(const int*)(mmc); 280 | checkSliceFunctionality!(immutable int*)(mmi); 281 | 282 | checkSliceFunctionality!(int*)(mcm); 283 | checkSliceFunctionality!(const int*)(mcc); 284 | checkSliceFunctionality!(immutable int*)(mci); 285 | 286 | checkSliceFunctionality!(int*)(mim); 287 | checkSliceFunctionality!(const int*)(mic); 288 | checkSliceFunctionality!(immutable int*)(mii); 289 | } 290 | 291 | { 292 | const Container!(int, int*) cmm; 293 | const Container!(int, const int*) cmc; 294 | const Container!(int, immutable int*) cmi; 295 | 296 | const Container!(const int, int*) ccm; 297 | const Container!(const int, const int*) ccc; 298 | const Container!(const int, immutable int*) cci; 299 | 300 | const Container!(immutable int, int*) cim; 301 | const Container!(immutable int, const int*) cic; 302 | const Container!(immutable int, immutable int*) cii; 303 | 304 | checkIndexFunctionality!(const(int)*, int)(cmm); 305 | checkIndexFunctionality!(const int*, int)(cmc); 306 | checkIndexFunctionality!(immutable int*, int)(cmi); 307 | 308 | checkIndexFunctionality!(const(int)*, const int)(ccm); 309 | checkIndexFunctionality!(const int*, const int)(ccc); 310 | checkIndexFunctionality!(immutable int*, const int)(cci); 311 | 312 | checkIndexFunctionality!(const(int)*, immutable int)(cim); 313 | checkIndexFunctionality!(const int*, immutable int)(cic); 314 | checkIndexFunctionality!(immutable int*, immutable int)(cii); 315 | 316 | checkSliceFunctionality!(const(int)*)(cmm); 317 | checkSliceFunctionality!(const int*)(cmc); 318 | checkSliceFunctionality!(immutable int*)(cmi); 319 | 320 | checkSliceFunctionality!(const(int)*)(ccm); 321 | checkSliceFunctionality!(const int*)(ccc); 322 | checkSliceFunctionality!(immutable int*)(cci); 323 | 324 | checkSliceFunctionality!(const(int)*)(cim); 325 | checkSliceFunctionality!(const int*)(cic); 326 | checkSliceFunctionality!(immutable int*)(cii); 327 | } 328 | 329 | { 330 | immutable Container!(int, immutable int*) imi; 331 | 332 | immutable Container!(const int, immutable int*) ici; 333 | 334 | immutable Container!(immutable int, immutable int*) iii; 335 | 336 | checkIndexFunctionality!(immutable int*, int)(imi); 337 | 338 | checkIndexFunctionality!(immutable int*, const int)(ici); 339 | 340 | checkIndexFunctionality!(immutable int*, immutable int)(iii); 341 | 342 | checkSliceFunctionality!(immutable int*)(imi); 343 | 344 | checkSliceFunctionality!(immutable int*)(ici); 345 | 346 | checkSliceFunctionality!(immutable int*)(iii); 347 | } 348 | } 349 | 350 | private void checkSliceFunctionality(Type, Container)(ref Container container) 351 | { 352 | import std.array : front; 353 | static if (__traits(hasMember, Container, "opSlice")) 354 | { 355 | auto r = container[]; 356 | static assert(is(typeof(r.front()) == Type)); 357 | static assert(is(typeof(container.length) == size_t)); 358 | assert(container.length == 0); 359 | } 360 | } 361 | 362 | private void checkIndexFunctionality(Type, KeyType, Container)(ref Container container) 363 | { 364 | import std.traits : hasFunctionAttributes; 365 | 366 | static assert(__traits(compiles, {container[KeyType.init];})); 367 | // The tests here will expect the wrong thing for opIndex implementations 368 | // that return by ref. 369 | static if (!hasFunctionAttributes!(Container.opIndex!Container, "ref")) 370 | static assert(is(typeof(container[KeyType.init]) == Type)); 371 | static assert(is(typeof(container.length) == size_t)); 372 | assert(container.length == 0); 373 | } 374 | 375 | 376 | unittest 377 | { 378 | testContainerDouble!(HashMap)(); 379 | testContainerDouble!(TreeMap)(); 380 | testContainerSingle!(HashSet)(); 381 | testContainerSingle!(UnrolledList)(); 382 | testContainerSingle!(OpenHashSet)(); 383 | version (D_InlineAsm_X86_64) testContainerSingle!(SimdSet)(); 384 | testContainerSingle!(SList)(); 385 | testContainerSingle!(TTree)(); 386 | testContainerSingle!(DynamicArray)(); 387 | testContainerSingle!(CyclicBuffer)(); 388 | } 389 | -------------------------------------------------------------------------------- /test/external_allocator_test.d: -------------------------------------------------------------------------------- 1 | module external_allocator_test; 2 | 3 | import containers.cyclicbuffer; 4 | import containers.dynamicarray; 5 | import containers.hashmap; 6 | import containers.hashset; 7 | import containers.immutablehashset; 8 | import containers.openhashset; 9 | import containers.simdset; 10 | import containers.slist; 11 | import containers.treemap; 12 | import containers.ttree; 13 | import containers.unrolledlist; 14 | import std.meta : AliasSeq; 15 | import std.range.primitives : walkLength; 16 | import std.stdio : stdout; 17 | import std.experimental.allocator.building_blocks.allocator_list : AllocatorList; 18 | import std.experimental.allocator.building_blocks.free_list : FreeList; 19 | import std.experimental.allocator.building_blocks.region : Region; 20 | import std.experimental.allocator.building_blocks.stats_collector : StatsCollector; 21 | import std.experimental.allocator.mallocator : Mallocator; 22 | 23 | // Chosen for a very important and completely undocumented reason 24 | private enum VERY_SPECIFIC_NUMBER = 371; 25 | 26 | private void testSingle(alias Container, Allocator)(Allocator allocator) 27 | { 28 | auto intMap = Container!(int, Allocator)(allocator); 29 | foreach (i; 0 .. VERY_SPECIFIC_NUMBER) 30 | intMap.insert(i); 31 | assert(intMap.length == VERY_SPECIFIC_NUMBER); 32 | } 33 | 34 | private void testDouble(alias Container, Allocator)(Allocator allocator) 35 | { 36 | auto intMap = Container!(int, int, Allocator)(allocator); 37 | foreach (i; 0 .. VERY_SPECIFIC_NUMBER) 38 | intMap[i] = VERY_SPECIFIC_NUMBER - i; 39 | assert(intMap.length == VERY_SPECIFIC_NUMBER); 40 | } 41 | 42 | version (D_InlineAsm_X86_64) 43 | { 44 | alias SingleContainers = AliasSeq!(CyclicBuffer, DynamicArray, HashSet, /+ImmutableHashSet,+/ 45 | OpenHashSet, SimdSet, SList, TTree, UnrolledList); 46 | } 47 | else 48 | { 49 | alias SingleContainers = AliasSeq!(CyclicBuffer, DynamicArray, HashSet, /+ImmutableHashSet,+/ 50 | OpenHashSet, /+SimdSet,+/ SList, TTree, UnrolledList); 51 | } 52 | 53 | alias DoubleContainers = AliasSeq!(HashMap, TreeMap); 54 | 55 | alias AllocatorType = StatsCollector!( 56 | FreeList!(AllocatorList!(a => Region!(Mallocator)(1024 * 1024), Mallocator), 64)); 57 | 58 | unittest 59 | { 60 | foreach (C; SingleContainers) 61 | { 62 | AllocatorType allocator; 63 | testSingle!(C, AllocatorType*)(&allocator); 64 | assert(allocator.numAllocate > 0 || allocator.numReallocate > 0, 65 | "No allocations happened for " ~ C.stringof); 66 | assert(allocator.numAllocate == allocator.numDeallocate || allocator.numReallocate > 0); 67 | assert(allocator.bytesUsed == 0); 68 | } 69 | foreach (C; DoubleContainers) 70 | { 71 | AllocatorType allocator; 72 | testDouble!(C, AllocatorType*)(&allocator); 73 | assert(allocator.numAllocate > 0 || allocator.numReallocate > 0, 74 | "No allocations happened for " ~ C.stringof); 75 | assert(allocator.numAllocate == allocator.numDeallocate || allocator.numReallocate > 0); 76 | assert(allocator.bytesUsed == 0); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /test/hashmap_gc_test.d: -------------------------------------------------------------------------------- 1 | import containers : HashMap; 2 | import std.stdio : writefln; 3 | import core.memory : GC; 4 | 5 | /** 6 | * Generate a random alphanumeric string. 7 | */ 8 | @trusted string randomString(uint len) 9 | { 10 | import std.ascii : letters, digits; 11 | import std.conv : to; 12 | import std.random : randomSample; 13 | import std.range : chain; 14 | 15 | auto asciiLetters = to!(dchar[])(letters); 16 | auto asciiDigits = to!(dchar[])(digits); 17 | 18 | if (len == 0) 19 | len = 1; 20 | 21 | auto res = to!string(randomSample(chain(asciiLetters, asciiDigits), len)); 22 | return res; 23 | } 24 | 25 | void main() 26 | { 27 | immutable iterationCount = 4; 28 | HashMap!(string, string) hmap; 29 | 30 | for (uint n = 1; n <= iterationCount; n++) 31 | { 32 | foreach (i; 0 .. 1_000_000) 33 | hmap[randomString(4)] = randomString(16); 34 | GC.collect(); 35 | hmap = HashMap!(string, string)(16); 36 | GC.collect(); 37 | 38 | foreach (i; 0 .. 1_000_000) 39 | hmap[randomString(4)] = randomString(16); 40 | GC.collect(); 41 | hmap.clear(); 42 | GC.collect(); 43 | 44 | writefln("iteration %s/%s finished", n, iterationCount); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/looptest.d: -------------------------------------------------------------------------------- 1 | module loops; 2 | 3 | import containers.ttree; 4 | import std.stdio; 5 | 6 | void main() 7 | { 8 | TTree!int ints; 9 | 10 | while (true) 11 | { 12 | foreach (i; 0 .. 1_000_000) 13 | ints.insert(i); 14 | 15 | foreach (i; 0 .. 1_000_000) 16 | ints.remove(i); 17 | writeln("iteration finished"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test graphs clean 2 | 3 | DC?=dmd 4 | SRC:=$(shell find ../src/ -name "*.d") 5 | 6 | ifeq ($(DC), ldc2) 7 | FLAGS:=-unittest -main -g -cov -I../src/ -d-debug -wi 8 | hashmap_gc_test_FLAGS:=-g -O3 -d-debug -wi 9 | looptest_FLAGS:=-O3 10 | else 11 | FLAGS:=-unittest -main -g -cov -I../src/ -debug -wi 12 | hashmap_gc_test_FLAGS:=-g -inline -O -release -debug -wi 13 | looptest_FLAGS:=-inline -O -release 14 | endif 15 | FLAGS32:=$(FLAGS) -m32 16 | 17 | all_arch:= 18 | ifeq ($(OS), Windows_NT) 19 | all_arch += all_32 all_64 20 | else 21 | UNAME_S := $(shell uname -s) 22 | ifeq ($(UNAME_S), Linux) 23 | all_arch += all_32 all_64 24 | else 25 | all_arch += all_64 26 | endif 27 | endif 28 | 29 | all: $(all_arch) 30 | 31 | all_64: test compile_test external_allocator_test hashmap_gc_test 32 | ./tests 33 | ./compile_test 34 | ./external_allocator_test 35 | ./hashmap_gc_test 36 | 37 | all_32: test_32 compile_test_32 external_allocator_test_32 38 | ./tests_32 39 | ./compile_test_32 40 | ./external_allocator_test_32 41 | 42 | test: $(SRC) 43 | $(DC) $(FLAGS) $(SRC) -oftests 44 | test_32: $(SRC) 45 | $(DC) $(FLAGS32) $(SRC) -oftests_32 46 | 47 | compile_test: compile_test.d $(SRC) 48 | $(DC) $(FLAGS) compile_test.d $(SRC) -ofcompile_test 49 | compile_test_32: compile_test.d $(SRC) 50 | $(DC) $(FLAGS32) compile_test.d $(SRC) -ofcompile_test_32 51 | 52 | external_allocator_test: external_allocator_test.d $(SRC) 53 | $(DC) $(FLAGS) external_allocator_test.d $(SRC) -ofexternal_allocator_test 54 | external_allocator_test_32: external_allocator_test.d $(SRC) 55 | $(DC) $(FLAGS32) external_allocator_test.d $(SRC) -ofexternal_allocator_test_32 56 | 57 | looptest: looptest.d 58 | $(DC) looptest.d $(looptest_FLAGS) \ 59 | ../src/memory/allocators.d \ 60 | ../src/containers/ttree.d \ 61 | ../src/containers/internal/node.d \ 62 | -I../src/ \ 63 | 64 | hashmap_gc_test: hashmap_gc_test.d $(SRC) 65 | $(DC) $(hashmap_gc_test_FLAGS) \ 66 | -I../src/ \ 67 | $(SRC) \ 68 | hashmap_gc_test.d \ 69 | -ofhashmap_gc_test 70 | 71 | clean: 72 | rm -f tests 73 | rm -f *.o 74 | rm -f *.dot 75 | rm -f *.png 76 | rm -f ..*.lst 77 | rm -f looptest 78 | 79 | graphs: clean 80 | $(DC) $(FLAGS) $(SRC) -oftests -version=graphviz_debugging 81 | -./tests 82 | parallel "dot -Tpng {} > {.}.png" ::: graph*.dot 83 | -------------------------------------------------------------------------------- /times.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlang-community/containers/c25a0ebabbd110a7d937aee0c2ded24f69b377ae/times.png --------------------------------------------------------------------------------