pax_global_header00006660000000000000000000000064135355044630014522gustar00rootroot0000000000000052 comment=43840b51de7d07c63812d89128742e4dc16352c9 dcontainers-0.8.0~alpha.16/000077500000000000000000000000001353550446300154715ustar00rootroot00000000000000dcontainers-0.8.0~alpha.16/.editorconfig000066400000000000000000000002501353550446300201430ustar00rootroot00000000000000[*] end_of_line = lf insert_final_newline = true indent_size = 4 tab_width = 4 trim_trailing_whitespace = true [*.d] indent_style = tab [*.json] indent_style = space dcontainers-0.8.0~alpha.16/.gitignore000066400000000000000000000005101353550446300174550ustar00rootroot00000000000000*.o *.a *.lst *.s test/tests test/tests_32 test/looptest perf.data doc/ .dub/ dub.selections.json test/compile_test test/compile_test_32 test/external_allocator_test test/external_allocator_test_32 test/openhashset test/hashmap_gc_test .gdb_history __test__*__ *.exe .directory *.userprefs .vscode emsi_containers-test-unittest dcontainers-0.8.0~alpha.16/.gitmodules000066400000000000000000000001551353550446300176470ustar00rootroot00000000000000[submodule "stdx-allocator"] path = stdx-allocator url = https://github.com/dlang-community/stdx-allocator dcontainers-0.8.0~alpha.16/.travis.yml000066400000000000000000000011431353550446300176010ustar00rootroot00000000000000language: d sudo: false dist: xenial addons: apt: packages: - pkg-config - gcc-multilib branches: only: - master install: - sudo apt-get install python3-pip python3-setuptools - pip3 install 'meson==0.48.2' - mkdir .ntmp - curl -L https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-linux.zip -o .ntmp/ninja-linux.zip - unzip .ntmp/ninja-linux.zip -d .ntmp before_script: export PATH=$PATH:$PWD/.ntmp script: - meson build && ninja -j8 -C build - ninja -j8 -C build test -v - git submodule update --init --recursive - make -B -C test/ - dub test dcontainers-0.8.0~alpha.16/LICENSE.txt000066400000000000000000000024721353550446300173210ustar00rootroot00000000000000Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dcontainers-0.8.0~alpha.16/README.md000066400000000000000000000050321353550446300167500ustar00rootroot00000000000000Containers [![CI status](https://travis-ci.org/dlang-community/containers.svg?branch=master)](https://travis-ci.org/dlang-community/containers/) ========== Containers backed by std.experimental.allocator # Dependencies Run `git submodule update --init --recursive` after cloning this repository. # Documentation Documentation is available at http://dlang-community.github.io/containers/index.html # Example ```d /+dub.sdl: dependency "emsi_containers" version="~>0.6" +/ import std.stdio; void main(string[] args) { import containers; DynamicArray!int arr; arr ~= 1; foreach (e; arr) e.writeln; } ``` [![Open on run.dlang.io](https://img.shields.io/badge/run.dlang.io-open-blue.svg)](https://run.dlang.io/is/8GYopZ) # Insertion Speed Benchmark ![Benchmark](times.png) Measurements taken on a `Intel(R) Core(TM) i5-4250U CPU @ 1.30GHz` with 8GB of memory. Compiled with dmd-2.068.0 using `-O -release -inline` flags. ### Code ```d import containers.ttree; import std.container.rbtree; import containers.slist; import std.container.slist; import containers.unrolledlist; import std.experimental.allocator; import std.experimental.allocator.building_blocks.allocator_list; import std.experimental.allocator.building_blocks.region; import std.experimental.allocator.mallocator; import std.datetime; import std.stdio; // For fun: change this number and watch the effect it has on the execution time alias Allocator = AllocatorList!(a => Region!Mallocator(1024 * 16), Mallocator); enum NUMBER_OF_ITEMS = 500_000; void testEMSIContainer(alias Container, string ContainerName)() { Allocator allocator; auto c = Container!(int, typeof(&allocator))(&allocator); StopWatch sw = StopWatch(AutoStart.yes); foreach (i; 0 .. NUMBER_OF_ITEMS) c.insert(i); sw.stop(); writeln("Inserts for ", ContainerName, " finished in ", sw.peek().to!("msecs", float), " milliseconds."); } void testPhobosContainer(alias Container, string ContainerName)() { static if (is(Container!int == class)) auto c = new Container!int(); else Container!int c; StopWatch sw = StopWatch(AutoStart.yes); foreach (i; 0 .. NUMBER_OF_ITEMS) c.insert(i); sw.stop(); writeln("Inserts for ", ContainerName, " finished in ", sw.peek().to!("msecs", float), " milliseconds."); } void main() { testEMSIContainer!(TTree, "TTree")(); testPhobosContainer!(RedBlackTree, "RedBlackTree")(); testPhobosContainer!(std.container.slist.SList, "Phobos SList")(); testEMSIContainer!(containers.slist.SList, "EMSI SList")(); testEMSIContainer!(UnrolledList, "UnrolledList")(); } ``` dcontainers-0.8.0~alpha.16/doc-src/000077500000000000000000000000001353550446300170235ustar00rootroot00000000000000dcontainers-0.8.0~alpha.16/doc-src/index.ddoc000066400000000000000000000002031353550446300207600ustar00rootroot00000000000000This page contains documentation for the EMSI containers library. Use the package index on the left to explore the documentation. dcontainers-0.8.0~alpha.16/dub.json000066400000000000000000000010021353550446300171270ustar00rootroot00000000000000{ "name": "emsi_containers", "description": "Containers that use Phobos' experimental allocators", "homepage": "https://github.com/dlang-community/containers", "license": "BSL-1.0", "authors": [ "EMSI", "DLang Community" ], "dependencies": { "stdx-allocator": "~>2.77.2" }, "buildTypes" : { "unittest" : { "buildOptions": ["unittests", "debugMode", "debugInfo"], "versions": ["emsi_containers_unittest"] } } } dcontainers-0.8.0~alpha.16/meson.build000066400000000000000000000046651353550446300176460ustar00rootroot00000000000000project('dcontainers', 'd', meson_version: '>=0.44', license: 'BSL-1.0', version: '0.8.0' ) project_soversion = '0' pkgc = import('pkgconfig') allocator_dep = dependency('stdx-allocator', version: '>= 2.77', fallback: ['stdx-allocator', 'allocator_dep']) # # Sources # dcontainers_src = [ 'src/containers/cyclicbuffer.d', 'src/containers/dynamicarray.d', 'src/containers/hashmap.d', 'src/containers/hashset.d', 'src/containers/immutablehashset.d', 'src/containers/internal/backwards.d', 'src/containers/internal/element_type.d', 'src/containers/internal/hash.d', 'src/containers/internal/mixins.d', 'src/containers/internal/node.d', 'src/containers/internal/storage_type.d', 'src/containers/openhashset.d', 'src/containers/package.d', 'src/containers/simdset.d', 'src/containers/slist.d', 'src/containers/treemap.d', 'src/containers/ttree.d', 'src/containers/unrolledlist.d' ] src_dir = include_directories('src/') # # Targets # dcontainers_lib = library('dcontainers', [dcontainers_src], include_directories: [src_dir], install: true, version: meson.project_version(), soversion: project_soversion, dependencies: [allocator_dep] ) pkgc.generate(name: 'dcontainers', libraries: [dcontainers_lib], subdirs: 'd/containers', requires: ['stdx-allocator'], version: meson.project_version(), description: 'Containers backed by std.experimental.allocator.' ) # for use by others which embed this as subproject dcontainers_dep = declare_dependency( link_with: [dcontainers_lib], include_directories: [src_dir], dependencies: [allocator_dep] ) # # Tests # if meson.get_compiler('d').get_id() == 'llvm' extra_args = ['-main', '-link-defaultlib-shared'] else extra_args = ['-main'] endif dcontainers_test_exe = executable('test_dcontainers', [dcontainers_src, 'test/compile_test.d', 'test/external_allocator_test.d'], include_directories: [src_dir], dependencies: [allocator_dep], d_unittest: true, link_args: extra_args ) test('test_dcontainers', dcontainers_test_exe) # the looptest is a manual test, so we don't run it and only compile it looptest_test_exe = executable('test_looptest', ['test/looptest.d'], dependencies: [dcontainers_dep] ) # # Install # install_subdir('src/containers/', install_dir: 'include/d/containers/') dcontainers-0.8.0~alpha.16/src/000077500000000000000000000000001353550446300162605ustar00rootroot00000000000000dcontainers-0.8.0~alpha.16/src/containers/000077500000000000000000000000001353550446300204255ustar00rootroot00000000000000dcontainers-0.8.0~alpha.16/src/containers/cyclicbuffer.d000066400000000000000000000431741353550446300232430ustar00rootroot00000000000000/** * Cyclic Buffer * Copyright: © 2016 Economic Modeling Specialists, Intl. * Authors: Nickolay Bukreyev * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) */ module containers.cyclicbuffer; private import core.exception : onRangeError; private import stdx.allocator.mallocator : Mallocator; private import std.range.primitives : empty, front, back, popFront, popBack; private import containers.internal.node : shouldAddGCRange; /** * Array that provides constant time (amortized) appending and popping * at either end, as well as random access to the elements. * * Params: * T = the array element type * Allocator = the allocator to use. Defaults to `Mallocator`. * supportGC = true if the container should support holding references to GC-allocated memory. */ struct CyclicBuffer(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T) { @disable this(this); private import std.conv : emplace; private import stdx.allocator.common : stateSize; private import std.traits : isImplicitlyConvertible, hasElaborateDestructor; static if (stateSize!Allocator != 0) { /// No default construction if an allocator must be provided. @disable this(); /** * Use the given `allocator` for allocations. */ this(Allocator allocator) nothrow pure @safe @nogc in { assert(allocator !is null, "Allocator must not be null"); } body { this.allocator = allocator; } } ~this() { clear(); static if (useGC) { import core.memory : GC; GC.removeRange(storage.ptr); } allocator.deallocate(storage); } /** * Removes all contents from the buffer. */ void clear() { if (!empty) { static if (hasElaborateDestructor!T) { if (start <= end) foreach (ref item; storage[start .. end + 1]) .destroy(item); else { foreach (ref item; storage[start .. $]) .destroy(item); foreach (ref item; storage[0 .. end + 1]) .destroy(item); } } start = (end + 1) % capacity; _length = 0; } } /** * Ensures capacity is at least as large as specified. */ size_t reserve(size_t newCapacity) { immutable oldCapacity = capacity; if (newCapacity <= oldCapacity) return oldCapacity; auto old = storage; if (oldCapacity == 0) storage = cast(typeof(storage)) allocator.allocate(newCapacity * T.sizeof); else { auto a = cast(void[]) old; allocator.reallocate(a, newCapacity * T.sizeof); storage = cast(typeof(storage)) a; } static if (useGC) { import core.memory : GC; //Add, then remove. Exactly in that order. GC.addRange(storage.ptr, newCapacity * T.sizeof); GC.removeRange(old.ptr); } if (empty) end = (start - 1 + capacity) % capacity; else if (start > end) { //The buffer was wrapped around prior to reallocation. //`moveEmplaceAll` is only available in 2.069+, so use a low level alternative. //Even more, we don't have to .init the moved away data, because we don't .destroy it. import core.stdc.string : memcpy, memmove; immutable prefix = end + 1; immutable suffix = oldCapacity - start; if (prefix <= suffix) { //The prefix is being moved right behind of suffix. immutable space = newCapacity - oldCapacity; if (space >= prefix) { memcpy(storage.ptr + oldCapacity, storage.ptr, prefix * T.sizeof); end += oldCapacity; } else { //There is not enough space, so move what we can, //and shift the rest to the start of the buffer. memcpy(storage.ptr + oldCapacity, storage.ptr, space * T.sizeof); end -= space; memmove(storage.ptr, storage.ptr + space, (end + 1) * T.sizeof); } } else { //The suffix is being moved forward, to the end of the buffer. //Due to the fact that these locations may overlap, use `memmove`. memmove(storage.ptr + newCapacity - suffix, storage.ptr + start, suffix * T.sizeof); start = newCapacity - suffix; } //Ensure everything is still alright. if (start <= end) assert(end + 1 - start == length); else assert(end + 1 + (newCapacity - start) == length); } return capacity; } /** * Inserts the given item into the start of the buffer. */ void insertFront(U)(U value) if (isImplicitlyConvertible!(U, T)) { if (empty) reserve(4); else if ((end + 1) % capacity == start) reserve(capacity >= 65_536 ? capacity + 65_536 : capacity * 2); start = (start - 1 + capacity) % capacity; _length++; emplace(&storage[start], value); } /** * Inserts the given item into the end of the buffer. */ void insertBack(U)(U value) if (isImplicitlyConvertible!(U, T)) { if (empty) reserve(4); else if ((end + 1) % capacity == start) reserve(capacity >= 65_536 ? capacity + 65_536 : capacity * 2); end = (end + 1) % capacity; _length++; emplace(&storage[end], value); } /// ditto alias insert = insertBack; /// ditto alias insertAnywhere = insertBack; /// ditto alias put = insertBack; /** * Removes the item at the start of the buffer. */ void removeFront() { version (assert) if (empty) onRangeError(); size_t pos = start; start = (start + 1) % capacity; _length--; static if (hasElaborateDestructor!T) .destroy(storage[pos]); } /// ditto alias popFront = removeFront; /** * Removes the item at the end of the buffer. */ void removeBack() { version (assert) if (empty) onRangeError(); size_t pos = end; end = (end - 1 + capacity) % capacity; _length--; static if (hasElaborateDestructor!T) .destroy(storage[pos]); } /// ditto alias popBack = removeBack; /// Accesses to the item at the start of the buffer. auto ref front(this This)() nothrow pure @property @safe { version (assert) if (empty) onRangeError(); alias ET = ContainerElementType!(This, T, true); return cast(ET) storage[start]; } /// Accesses to the item at the end of the buffer. auto ref back(this This)() nothrow pure @property @safe { version (assert) if (empty) onRangeError(); alias ET = ContainerElementType!(This, T, true); return cast(ET) storage[end]; } /// buffer[i] auto ref opIndex(this This)(size_t i) nothrow pure @safe { version (assert) if (i >= length) onRangeError(); alias ET = ContainerElementType!(This, T, true); return cast(ET) storage[(start + i) % $]; } /// buffer[] Range!This opIndex(this This)() nothrow pure @safe @nogc { if (empty) return typeof(return)(storage[0 .. 0], storage[0 .. 0]); if (start <= end) return typeof(return)(storage[start .. end + 1], storage[0 .. 0]); return typeof(return)(storage[start .. $], storage[0 .. end + 1]); } /// buffer[i .. j] size_t[2] opSlice(size_t k: 0)(size_t i, size_t j) const nothrow pure @safe @nogc { return [i, j]; } /// ditto Range!This opIndex(this This)(size_t[2] indices) nothrow pure @safe { size_t i = indices[0], j = indices[1]; version (assert) { if (i > j) onRangeError(); if (j > length) onRangeError(); } if (i == j) return typeof(return)(storage[0 .. 0], storage[0 .. 0]); i = (start + i) % capacity; j = (start + j) % capacity; if (i < j) return typeof(return)(storage[i .. j], storage[0 .. 0]); return typeof(return)(storage[i .. $], storage[0 .. j]); } static struct Range(ThisT) { private { static if (is(ThisT == immutable)) { alias SliceT = immutable(ContainerStorageType!T)[]; } else static if (is(ThisT == const)) { alias SliceT = const(ContainerStorageType!T)[]; } else { alias SliceT = ContainerStorageType!T[]; } } @disable this(); this(SliceT a, SliceT b) nothrow pure @safe @nogc { head = a; tail = b; } This save(this This)() nothrow pure @property @safe @nogc { return this; } bool empty() const nothrow pure @property @safe @nogc { return head.empty && tail.empty; } size_t length() const nothrow pure @property @safe @nogc { return head.length + tail.length; } alias opDollar = length; auto ref front(this This)() nothrow pure @property @safe { if (!head.empty) return cast(ET) head.front; return cast(ET) tail.front; } auto ref back(this This)() nothrow pure @property @safe { if (!tail.empty) return cast(ET) tail.back; return cast(ET) head.back; } void popFront() nothrow pure @safe { if (head.empty) { import std.algorithm.mutation : swap; //Always try to keep `head` non-empty. swap(head, tail); } head.popFront(); } void popBack() nothrow pure @safe { if (!tail.empty) tail.popBack(); else head.popBack(); } /// range[i] auto ref opIndex(this This)(size_t i) nothrow pure @safe { return cast(ET) (i < head.length ? head[i] : tail[i - head.length]); } /// range[] This opIndex(this This)() nothrow pure @safe @nogc { return this.save; } /// range[i .. j] size_t[2] opSlice(size_t k: 0)(size_t i, size_t j) const nothrow pure @safe @nogc { return [i, j]; } /// ditto This opIndex(this This)(size_t[2] indices) nothrow pure @safe { size_t i = indices[0], j = indices[1]; version (assert) { if (i > j) onRangeError(); if (j > length) onRangeError(); } if (i >= head.length) return typeof(return)(tail[i - head.length .. j - head.length], tail[0 .. 0]); if (j <= head.length) return typeof(return)(head[i .. j], head[0 .. 0]); return typeof(return)(head[i .. $], tail[0 .. j - head.length]); } /// range[...]++ auto ref opUnary(string op)() nothrow pure @safe @nogc if (op == "++" || op == "--") { mixin(op ~ "head[];"); mixin(op ~ "tail[];"); return this; } /// range[...] = value auto ref opAssign(U)(const auto ref U value) nothrow pure @safe @nogc { head[] = value; tail[] = value; return this; } /// range[...] += value auto ref opOpAssign(string op, U)(const auto ref U value) nothrow pure @safe @nogc { mixin("head[] " ~ op ~ "= value;"); mixin("tail[] " ~ op ~ "= value;"); return this; } private: alias ET = ContainerElementType!(ThisT, T); SliceT head, tail; } /// Returns: the number of items in the buffer. size_t length() const nothrow pure @property @safe @nogc { return _length; } /// ditto alias opDollar = length; /// Returns: maximal number of items the buffer can hold without reallocation. size_t capacity() const nothrow pure @property @safe @nogc { return storage.length; } /// Returns: whether or not the CyclicBuffer is empty. bool empty() const nothrow pure @property @safe @nogc { return length == 0; } private: import containers.internal.storage_type : ContainerStorageType; import containers.internal.element_type : ContainerElementType; import containers.internal.mixins : AllocatorState; enum bool useGC = supportGC && shouldAddGCRange!T; mixin AllocatorState!Allocator; ContainerStorageType!T[] storage; size_t start, end, _length; } version(emsi_containers_unittest) private { import std.algorithm.comparison : equal; import stdx.allocator.gc_allocator : GCAllocator; import stdx.allocator.building_blocks.free_list : FreeList; import std.range : iota, lockstep, StoppingPolicy; struct S { int* a; ~this() { (*a)++; } } class C { int* a; this(int* a) { this.a = a; } ~this() { (*a)++; } } } version(emsi_containers_unittest) unittest { static void test(int size) { { CyclicBuffer!int b; assert(b.empty); foreach (i; 0 .. size) { assert(b.length == i); b.insertBack(i); assert(b.back == i); } assert(b.length == size); foreach (i; 0 .. size) { assert(b.length == size - i); assert(b.front == i); b.removeFront(); } assert(b.empty); } { CyclicBuffer!int b; foreach (i; 0 .. size) { assert(b.length == i); b.insertFront(i); assert(b.front == i); } assert(b.length == size); foreach (i; 0 .. size) { assert(b.length == size - i); assert(b.back == i); b.removeBack(); } assert(b.empty); } } foreach (size; [1, 2, 3, 4, 5, 7, 8, 9, 512, 520, 0x10000, 0x10001, 0x20000]) test(size); } version(emsi_containers_unittest) unittest { static void test(int prefix, int suffix, int newSize) { CyclicBuffer!int b; foreach_reverse (i; 0 .. suffix) b.insertFront(i); foreach (i; suffix .. suffix + prefix) b.insertBack(i); assert(b.length == prefix + suffix); b.reserve(newSize); assert(b.length == prefix + suffix); assert(equal(b[], iota(prefix + suffix))); } immutable prefixes = [2, 3, 3, 4, 4]; immutable suffixes = [3, 2, 4, 3, 4]; immutable sizes = [16, 16, 9, 9, 9]; foreach (a, b, c; lockstep(prefixes, suffixes, sizes, StoppingPolicy.requireSameLength)) test(a, b, c); } version(emsi_containers_unittest) unittest { int a = 0; { CyclicBuffer!S b; { S s = { &a }; foreach (i; 0 .. 5) b.insertBack(s); assert(a == 5); foreach (i; 0 .. 5) b.insertBack(S(&a)); assert(a == 10); foreach (i; 0 .. 5) { b.removeBack(); b.removeFront(); } assert(a == 20); } assert(a == 21); } assert(a == 21); } version(emsi_containers_unittest) unittest { int* a = new int; CyclicBuffer!C b; { C c = new C(a); foreach (i; 0 .. 10) b.insertBack(c); assert(*a == 0); foreach (i; 0 .. 5) { b.removeBack(); b.removeFront(); } foreach (i; 0 .. b.capacity) b.insertFront(null); assert(*a == 0); } string s = ""; foreach (i; 0 .. 1_000) s = s ~ 'a'; s = ""; import core.memory : GC; GC.collect(); assert(*a == 0 || *a == 1); } version(emsi_containers_unittest) unittest { CyclicBuffer!int b; b.insertFront(10); assert(b[0] == 10); b.insertFront(20); assert(b[0] == 20); assert(b[1] == 10); b.insertFront(30); assert(b[0] == 30); assert(b[1] == 20); assert(b[2] == 10); b.insertBack(5); assert(b[0] == 30); assert(b[1] == 20); assert(b[2] == 10); assert(b[3] == 5); b.back = 7; assert(b[3] == 7); } version(emsi_containers_unittest) unittest { import std.range : isInputRange, isForwardRange, isBidirectionalRange, isRandomAccessRange; CyclicBuffer!int b; static assert(isInputRange!(typeof(b[]))); static assert(isForwardRange!(typeof(b[]))); static assert(isBidirectionalRange!(typeof(b[]))); static assert(isRandomAccessRange!(typeof(b[]))); } version(emsi_containers_unittest) unittest { CyclicBuffer!int b; assert(b[].empty); } version(emsi_containers_unittest) unittest { FreeList!(Mallocator, 0, 64) alloc; FreeList!(GCAllocator, 0, 64) alloc2; auto b = CyclicBuffer!(int, typeof(&alloc))(&alloc); auto b2 = CyclicBuffer!(int, typeof(&alloc2))(&alloc2); auto b3 = CyclicBuffer!(int, GCAllocator)(); } version(emsi_containers_unittest) unittest { static void testConst(const ref CyclicBuffer!int b, int x) { assert(b[0] == x); assert(b.front == x); static assert(!__traits(compiles, { ++b[0]; } )); assert(equal(b[], [x])); } CyclicBuffer!int b; b.insertFront(0); assert(b.front == 0); b.front++; assert(b[0] == 1); b[0]++; ++b[0]; assert(b.front == 3); assert(!b.empty); b[0] *= 2; assert(b[0] == 6); testConst(b, 6); b[]++; assert(equal(b[], [7])); b[0] = 5; assert(b[0] == 5); assert(b.front == 5); testConst(b, 5); assert(b[][0] == 5); } version(emsi_containers_unittest) unittest { int a = 0; { CyclicBuffer!S b; foreach (i; 0 .. 5) b.insertBack(S(&a)); assert(a == 5); } assert(a == 10); a = 0; { CyclicBuffer!S b; foreach (i; 0 .. 4) b.insertBack(S(&a)); assert(a == 4); b.removeFront(); assert(a == 5); b.insertBack(S(&a)); assert(a == 6); } assert(a == 10); } version(emsi_containers_unittest) unittest { CyclicBuffer!int b; foreach (i; 0 .. 4) b.insertBack(i); b.removeFront(); b.removeFront(); b.insertBack(4); b.insertBack(5); assert(equal(b[], [2, 3, 4, 5])); b.reserve(5); assert(equal(b[], [2, 3, 4, 5])); } version(emsi_containers_unittest) unittest { CyclicBuffer!int b; foreach (i; 0 .. 4) b.insertBack(i); b.removeFront(); b.removeFront(); b.removeFront(); b.insertBack(4); b.insertBack(5); b.insertBack(6); assert(equal(b[], [3, 4, 5, 6])); b.reserve(5); assert(equal(b[], [3, 4, 5, 6])); } version(emsi_containers_unittest) unittest { static void test(ref CyclicBuffer!int b) { assert(equal(b[], [4, 5, 6, 7, 8, 9, 10, 11])); assert(b[3 .. 3].empty); auto slice = b[1 .. 6]; assert(equal(slice, [5, 6, 7, 8, 9])); slice[3 .. 5] = 0; assert(equal(b[], [4, 5, 6, 7, 0, 0, 10, 11])); slice[0 .. 2] += 1; assert(equal(b[], [4, 6, 7, 7, 0, 0, 10, 11])); slice[0 .. 2]--; assert(equal(b[], [4, 5, 6, 7, 0, 0, 10, 11])); auto copy = slice.save; assert(equal(slice, copy)); assert(equal(slice, copy[])); assert(slice.back == 0); slice.popBack(); assert(equal(slice, [5, 6, 7, 0])); assert(slice.back == 0); slice.popBack(); assert(equal(slice, [5, 6, 7])); assert(slice.back == 7); slice.popBack(); assert(equal(slice, [5, 6])); assert(equal(copy, [5, 6, 7, 0, 0])); slice[1] = 10; assert(-copy[1] == -10); copy[1] *= 2; assert(slice[1] == 20); assert(b[2] == 20); auto copy2 = copy[0 .. $]; assert(equal(copy, copy2)); } { CyclicBuffer!int b; foreach (i; 4 .. 12) b.insertBack(i); test(b); } { CyclicBuffer!int b; foreach (i; 0 .. 8) b.insertBack(i); foreach (i; 0 .. 4) b.removeFront(); foreach (i; 8 .. 12) b.insertBack(i); test(b); } } version(emsi_containers_unittest) unittest { CyclicBuffer!int b; foreach (i; 0 .. 10) b.insertBack(i); assert(b.capacity >= 10); b.reserve(12); assert(b.capacity >= 12); } version(emsi_containers_unittest) unittest { CyclicBuffer!int b; foreach (i; 0 .. 6) b.insertBack(i); foreach (i; 6 .. 8) b.insertFront(i); assert(equal(b[], [7, 6, 0, 1, 2, 3, 4, 5])); b.reserve(b.capacity + 1); assert(equal(b[], [7, 6, 0, 1, 2, 3, 4, 5])); } version(emsi_containers_unittest) unittest { static class Foo { string name; } CyclicBuffer!Foo b; } dcontainers-0.8.0~alpha.16/src/containers/dynamicarray.d000066400000000000000000000332541353550446300232640ustar00rootroot00000000000000/** * Dynamic Array * Copyright: © 2015 Economic Modeling Specialists, Intl. * Authors: Brian Schott * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) */ module containers.dynamicarray; private import containers.internal.node : shouldAddGCRange; private import stdx.allocator.mallocator : Mallocator; /** * Array that is able to grow itself when items are appended to it. Uses * malloc/free/realloc to manage its storage. * * Params: * T = the array element type * Allocator = the allocator to use. Defaults to `Mallocator`. * supportGC = true if the container should support holding references to * GC-allocated memory. */ struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T) { this(this) @disable; private import stdx.allocator.common : stateSize; static if (is(typeof((T[] a, const T[] b) => a[0 .. b.length] = b[0 .. $]))) { /// Either `const(T)` or `T`. alias AppendT = const(T); /// Either `const(typeof(this))` or `typeof(this)`. alias AppendTypeOfThis = const(typeof(this)); } else { alias AppendT = T; alias AppendTypeOfThis = typeof(this); } static if (stateSize!Allocator != 0) { /// No default construction if an allocator must be provided. this() @disable; /** * Use the given `allocator` for allocations. */ this(Allocator allocator) in { assert(allocator !is null, "Allocator must not be null"); } body { this.allocator = allocator; } } ~this() { import stdx.allocator.mallocator : Mallocator; import containers.internal.node : shouldAddGCRange; if (arr is null) return; static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor")) { foreach (ref item; arr[0 .. l]) { item.__xdtor(); } } static if (useGC) { import core.memory : GC; GC.removeRange(arr.ptr); } allocator.deallocate(arr); } /// Slice operator overload pragma(inline, true) auto opSlice(this This)() @nogc { return opSlice!(This)(0, l); } /// ditto pragma(inline, true) auto opSlice(this This)(size_t a, size_t b) @nogc { alias ET = ContainerElementType!(This, T); return cast(ET[]) arr[a .. b]; } /// Index operator overload pragma(inline, true) auto opIndex(this This)(size_t i) @nogc { return opSlice!(This)(i, i + 1)[0]; } /** * Inserts the given value into the end of the array. */ void insertBack(T value) { import stdx.allocator.mallocator : Mallocator; import containers.internal.node : shouldAddGCRange; if (arr.length == 0) { arr = cast(typeof(arr)) allocator.allocate(T.sizeof * 4); static if (useGC) { import core.memory: GC; GC.addRange(arr.ptr, arr.length * T.sizeof); } } else if (l >= arr.length) { immutable size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1; void[] a = cast(void[]) arr; allocator.reallocate(a, c * T.sizeof); arr = cast(typeof(arr)) a; static if (useGC) { import core.memory: GC; GC.removeRange(arr.ptr); GC.addRange(arr.ptr, arr.length * T.sizeof); } } import std.traits: hasElaborateAssign, hasElaborateDestructor; static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) { // If a destructor is run before blit or assignment involves // more than just a blit, ensure that arr[l] is in a valid // state before assigning to it. import core.stdc.string : memcpy, memset; const init = typeid(T).initializer(); if (init.ptr is null) // null pointer means initialize to 0s (() @trusted => memset(arr.ptr + l, 0, T.sizeof))(); else (() @trusted => memcpy(arr.ptr + l, init.ptr, T.sizeof))(); } emplace(arr[l++], value); } /// ditto alias insert = insertBack; /// ditto alias insertAnywhere = insertBack; /// ditto alias put = insertBack; /** * ~= operator overload */ scope ref typeof(this) opOpAssign(string op)(T value) if (op == "~") { insert(value); return this; } /** * ~= operator overload for an array of items */ scope ref typeof(this) opOpAssign(string op, bool checkForOverlap = true)(AppendT[] rhs) if (op == "~" && !is(T == AppendT[])) { // Disabling checkForOverlap when this function is called from opBinary!"~" // is not just for efficiency, but to avoid circular function calls that // would prevent inference of @nogc, etc. static if (checkForOverlap) if ((() @trusted => arr.ptr <= rhs.ptr && arr.ptr + arr.length > rhs.ptr)()) { // Special case where rhs is a slice of this array. this = this ~ rhs; return this; } reserve(l + rhs.length); import std.traits: hasElaborateAssign, hasElaborateDestructor; static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) { foreach (ref value; rhs) emplace(arr[l++], value); } else { arr[l .. l + rhs.length] = rhs[0 .. rhs.length]; l += rhs.length; } return this; } /// ditto scope ref typeof(this) opOpAssign(string op)(ref AppendTypeOfThis rhs) if (op == "~") { return this ~= rhs.arr[0 .. rhs.l]; } /** * ~ operator overload */ typeof(this) opBinary(string op)(ref AppendTypeOfThis other) if (op == "~") { typeof(this) ret; ret.reserve(l + other.l); ret.opOpAssign!("~", false)(arr[0 .. l]); ret.opOpAssign!("~", false)(other.arr[0 .. other.l]); return ret; } /// ditto typeof(this) opBinary(string op)(AppendT[] values) if (op == "~") { typeof(this) ret; ret.reserve(l + values.length); ret.opOpAssign!("~", false)(arr[0 .. l]); ret.opOpAssign!("~", false)(values); return ret; } /** * Ensures sufficient capacity to accommodate `n` elements. */ void reserve(size_t n) { if (arr.length >= n) return; if (arr.ptr is null) { size_t c = 4; if (c < n) c = n; arr = cast(typeof(arr)) allocator.allocate(T.sizeof * c); static if (useGC) { import core.memory: GC; GC.addRange(arr.ptr, arr.length * T.sizeof); } } else { size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1; if (c < n) c = n; static if (useGC) void* oldPtr = arr.ptr; void[] a = cast(void[]) arr; allocator.reallocate(a, c * T.sizeof); arr = cast(typeof(arr)) a; static if (useGC) { import core.memory: GC; GC.removeRange(oldPtr); GC.addRange(arr.ptr, arr.length * T.sizeof); } } } static if (is(typeof({T value;}))) // default construction is allowed { /** * Change the array length. * When growing, initialize new elements to the default value. */ void resize(size_t n) { resize(n, T.init); } } /** * Change the array length. * When growing, initialize new elements to the given value. */ void resize(size_t n, T value) { if (arr.length < n) reserve(n); if (l < n) // Growing? { import std.traits: hasElaborateAssign, hasElaborateDestructor; static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) { foreach (i; l..n) emplace(arr[l], value); } else arr[l..n] = value; } else { static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor")) { foreach (i; n..l) arr[i].__xdtor(); } } l = n; } /** * Remove the item at the given index from the array. */ void remove(const size_t i) { if (i < this.l) { auto next = i + 1; while (next < this.l) { arr[next - 1] = arr[next]; ++next; } --l; static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor")) { arr[l].__xdtor(); } } else { import core.exception : RangeError; throw new RangeError("Out of range index used to remove element"); } } /** * Removes the last element from the array. */ void removeBack() { this.remove(this.length - 1); } /// Index assignment support void opIndexAssign(T value, size_t i) @nogc { arr[i] = value; } /// Slice assignment support void opSliceAssign(T value) @nogc { arr[0 .. l] = value; } /// ditto void opSliceAssign(T value, size_t i, size_t j) @nogc { arr[i .. j] = value; } /// Returns: the number of items in the array size_t length() const nothrow pure @property @safe @nogc { return l; } /// Ditto alias opDollar = length; /// Returns: whether or not the DynamicArray is empty. bool empty() const nothrow pure @property @safe @nogc { return l == 0; } /** * Returns: a slice to the underlying array. * * As the memory of the array may be freed, access to this array is * highly unsafe. */ auto ptr(this This)() @nogc @property { alias ET = ContainerElementType!(This, T); return cast(ET*) arr.ptr; } /// Returns: the front element of the DynamicArray. auto ref T front() pure @property { return arr[0]; } /// Returns: the back element of the DynamicArray. auto ref T back() pure @property { return arr[l - 1]; } private: static void emplace(ref ContainerStorageType!T target, ref AppendT source) { (cast(void[])((&target)[0..1]))[] = cast(void[])((&source)[0..1]); static if (__traits(hasMember, T, "__xpostblit")) target.__xpostblit(); } import containers.internal.storage_type : ContainerStorageType; import containers.internal.element_type : ContainerElementType; import containers.internal.mixins : AllocatorState; enum bool useGC = supportGC && shouldAddGCRange!T; mixin AllocatorState!Allocator; ContainerStorageType!(T)[] arr; size_t l; } version(emsi_containers_unittest) unittest { import std.algorithm : equal; import std.range : iota; DynamicArray!int ints; assert(ints.empty); foreach (i; 0 .. 100) { ints.insert(i); assert(ints.front == 0); assert(ints.back == i); } assert (equal(ints[], iota(100))); assert (ints.length == 100); ints[0] = 100; assert (ints[0] == 100); ints[0 .. 5] = 20; foreach (i; ints[0 .. 5]) assert (i == 20); ints[] = 432; foreach (i; ints[]) assert (i == 432); auto arr = ints.ptr; arr[0] = 1337; assert(arr[0] == 1337); assert(ints[0] == 1337); } version(emsi_containers_unittest) { class Cls { int* a; this(int* a) { this.a = a; } ~this() { ++(*a); } } } version(emsi_containers_unittest) unittest { int a = 0; { DynamicArray!(Cls) arr; arr.insert(new Cls( & a)); } assert(a == 0); // Destructor not called. } version(emsi_containers_unittest) unittest { import std.exception : assertThrown; import core.exception : RangeError; DynamicArray!int empty; assertThrown!RangeError(empty.remove(1337)); assert(empty.length == 0); DynamicArray!int one; one.insert(0); assert(one.length == 1); assertThrown!RangeError(one.remove(1337)); assert(one.length == 1); one.remove(0); assert(one.length == 0); DynamicArray!int two; two.insert(0); two.insert(1); assert(two.length == 2); assertThrown!RangeError(two.remove(1337)); assert(two.length == 2); two.remove(0); assert(two.length == 1); assert(two[0] == 1); two.remove(0); assert(two.length == 0); two.insert(0); two.insert(1); assert(two.length == 2); two.remove(1); assert(two.length == 1); assert(two[0] == 0); assertThrown!RangeError(two.remove(1)); assert(two.length == 1); assert(two[0] == 0); two.remove(0); assert(two.length == 0); } version(emsi_containers_unittest) unittest { int a = 0; DynamicArray!(Cls, Mallocator, true) arr; arr.insert(new Cls(&a)); arr.remove(0); assert(a == 0); // Destructor not called. } version(emsi_containers_unittest) unittest { DynamicArray!(int*, Mallocator, true) arr; foreach (i; 0 .. 4) arr.insert(new int(i)); assert (arr.length == 4); int*[] slice = arr[1 .. $ - 1]; assert (slice.length == 2); assert (*slice[0] == 1); assert (*slice[1] == 2); } version(emsi_containers_unittest) unittest { import std.format : format; DynamicArray!int arr; foreach (int i; 0 .. 10) arr ~= i; assert(arr.length == 10, "arr.length = %d".format(arr.length)); auto arr2 = arr ~ arr; assert(arr2.length == 20); auto arr3 = arr2 ~ [100, 99, 98]; assert(arr3.length == 23); while(!arr3.empty) arr3.removeBack(); assert(arr3.empty); } version(emsi_containers_unittest) @system unittest { DynamicArray!int a; a.reserve(1000); assert(a.length == 0); assert(a.empty); assert(a.arr.length >= 1000); int* p = a[].ptr; foreach (i; 0 .. 1000) { a.insert(i); } assert(p is a[].ptr); } version(emsi_containers_unittest) unittest { // Ensure that Array.insert doesn't call the destructor for // a struct whose state is uninitialized memory. static struct S { int* a; ~this() @nogc nothrow { if (a !is null) ++(*a); } } int a = 0; DynamicArray!S arr; // This next line may segfault if destructors are called // on structs in invalid states. arr.insert(S(&a)); assert(a == 1); } version(emsi_containers_unittest) @nogc unittest { struct HStorage { import containers.dynamicarray: DynamicArray; DynamicArray!int storage; } auto hs = HStorage(); } version(emsi_containers_unittest) @nogc unittest { DynamicArray!char a; const DynamicArray!char b = a ~ "def"; a ~= "abc"; a ~= b; assert(a[] == "abcdef"); a ~= a; assert(a[] == "abcdefabcdef"); } version(emsi_containers_unittest) unittest { static struct S { bool initialized; @nogc: @disable this(); this(int) { initialized = true; } ~this() { assert(initialized); } } auto s = S(0); DynamicArray!S arr; arr.insertBack(s); arr ~= [s]; } version(emsi_containers_unittest) @nogc unittest { DynamicArray!int a; a.resize(5, 42); assert(a.length == 5); assert(a[2] == 42); a.resize(3, 17); assert(a.length == 3); assert(a[2] == 42); struct Counter { @nogc: static int count; @disable this(); this(int) { count++; } this(this) { count++; } ~this() { count--; } } DynamicArray!Counter b; assert(Counter.count == 0); static assert(!is(typeof(b.resize(5)))); b.resize(5, Counter(0)); assert(Counter.count == 5); b.resize(3, Counter(0)); assert(Counter.count == 3); } dcontainers-0.8.0~alpha.16/src/containers/hashmap.d000066400000000000000000000425551353550446300222260ustar00rootroot00000000000000/** * Hash Map * Copyright: © 2015 Economic Modeling Specialists, Intl. * Authors: Brian Schott * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) */ module containers.hashmap; private import containers.internal.hash; private import containers.internal.node : shouldAddGCRange; private import stdx.allocator.mallocator : Mallocator; private import std.traits : isBasicType, Unqual; /** * Associative array / hash map. * Params: * K = the key type * V = the value type * Allocator = the allocator type to use. Defaults to `Mallocator` * hashFunction = the hash function to use on the keys * supportGC = true if the container should support holding references to * GC-allocated memory. */ struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K, bool supportGC = shouldAddGCRange!K || shouldAddGCRange!V, bool storeHash = true) { this(this) @disable; private import stdx.allocator.common : stateSize; static if (stateSize!Allocator != 0) { this() @disable; /** * Use the given `allocator` for allocations. */ this(Allocator allocator) pure nothrow @nogc @safe in { assert(allocator !is null, "Allocator must not be null"); } body { this.allocator = allocator; } /** * Constructs an HashMap with an initial bucket count of bucketCount. bucketCount * must be a power of two. */ this(size_t bucketCount, Allocator allocator) in { assert(allocator !is null, "Allocator must not be null"); assert((bucketCount & (bucketCount - 1)) == 0, "bucketCount must be a power of two"); } body { this.allocator = allocator; initialize(bucketCount); } invariant { assert(allocator !is null); } } else { /** * Constructs an HashMap with an initial bucket count of bucketCount. bucketCount * must be a power of two. */ this(size_t bucketCount) in { assert((bucketCount & (bucketCount - 1)) == 0, "bucketCount must be a power of two"); } body { initialize(bucketCount); } } ~this() nothrow { scope (failure) assert(false); clear(); } /** * Removes all items from the map */ void clear() { import stdx.allocator : dispose; // always remove ranges from GC first before disposing of buckets, to // prevent segfaults when the GC collects at an unfortunate time static if (useGC) GC.removeRange(buckets.ptr); allocator.dispose(buckets); buckets = null; _length = 0; } /** * Supports `aa[key]` syntax. */ ref opIndex(this This)(K key) { import std.conv : text; import std.exception : enforce; alias CET = ContainerElementType!(This, V, true); size_t i; auto n = find(key, i); enforce(n !is null, "'" ~ text(key) ~ "' not found in HashMap"); return *cast(CET*) &n.value; } /** * Returns: `true` if there is an entry in this map for the given `key`, * false otherwise. */ bool containsKey(this This)(K key) inout { size_t i; return find(key, i) !is null; } /** * Gets the value for the given key, or returns `defaultValue` if the given * key is not present. * * Params: * key = the key to look up * value = the default value * Returns: the value indexed by `key`, if present, or `defaultValue` otherwise. */ auto get(this This)(K key, lazy V defaultValue) { alias CET = ContainerElementType!(This, V); size_t i; auto n = find(key, i); if (n is null) return defaultValue; return cast(CET) n.value; } /** * If the given key does not exist in the HashMap, adds it with * the value `defaultValue`. * * Params: * key = the key to look up * value = the default value * Returns: a pointer to the value stored in the HashMap with the given key. * The pointer is guaranteed to be valid only until the next HashMap * modification. */ auto getOrAdd(this This)(K key, lazy V defaultValue) { alias CET = ContainerElementType!(This, V); size_t i; auto n = find(key, i); if (n is null) return cast(CET*) &insert(key, defaultValue).value; else return cast(CET*) &n.value; } /** * Supports $(B aa[key] = value;) syntax. */ void opIndexAssign(V value, const K key) { insert(key, value); } /** * Supports $(B key in aa) syntax. * * Returns: pointer to the value corresponding to the given key, * or null if the key is not present in the HashMap. */ inout(V)* opBinaryRight(string op)(const K key) inout nothrow @trusted if (op == "in") { size_t i; auto n = find(key, i); if (n is null) return null; return &(cast(inout) n).value; } /** * Removes the value associated with the given key * Returns: true if a value was actually removed. */ bool remove(K key) { size_t i; auto n = find(key, i); if (n is null) return false; static if (storeHash) auto node = Node(n.hash, n.key); else auto node = Node(n.key); immutable bool removed = buckets[i].remove(node); if (removed) _length--; return removed; } /** * Returns: the number of key/value pairs in this container. */ size_t length() const nothrow pure @property @safe @nogc { return _length; } /** * Returns: `true` if there are no items in this container. */ bool empty() const nothrow pure @property @safe @nogc { return _length == 0; } /** * Returns: a range of the keys in this map. */ auto byKey(this This)() inout @trusted { return MapRange!(This, IterType.key)(cast(Unqual!(This)*) &this); } /** * Returns: a GC-allocated array filled with the keys contained in this map. */ K[] keys() const @property out(result) { assert (result.length == _length); } body { import std.array : appender; auto app = appender!(K[])(); foreach (ref const bucket; buckets) { foreach (item; bucket) app.put(cast(K) item.key); } return app.data; } /** * Returns: a range of the values in this map. */ auto byValue(this This)() inout @trusted { return MapRange!(This, IterType.value)(cast(Unqual!(This)*) &this); } /// ditto alias opSlice = byValue; /** * Returns: a GC-allocated array containing the values contained in this map. */ auto values(this This)() const @property out(result) { assert (result.length == _length); } body { import std.array : appender; auto app = appender!(ContainerElementType!(This, V)[])(); foreach (ref const bucket; buckets) { foreach (item; bucket) app.put(cast(ContainerElementType!(This, V)) item.value); } return app.data; } /** * Returns: a range of the kev/value pairs in this map. The element type of * this range is a struct with `key` and `value` fields. */ auto byKeyValue(this This)() inout @trusted { return MapRange!(This, IterType.both)(cast(Unqual!(This)*) &this); } /** * Support for $(D foreach(key, value; aa) { ... }) syntax; */ int opApply(int delegate(in ref K, ref V) del) { int result = 0; foreach (ref bucket; buckets) foreach (ref node; bucket[]) if ((result = del(*cast(K*)&node.key, *cast(V*)&node.value)) != 0) return result; return result; } /// ditto int opApply(int delegate(in ref K, in ref V) del) const { int result = 0; foreach (const ref bucket; buckets) foreach (const ref node; bucket[]) if ((result = del(*cast(K*)&node.key, *cast(V*)&node.value)) != 0) return result; return result; } /// ditto int opApply(int delegate(ref V) del) { int result = 0; foreach (ref bucket; buckets) foreach (ref node; bucket[]) if ((result = del(*cast(V*)&node.value)) != 0) return result; return result; } /// ditto int opApply(int delegate(in ref V) del) const { int result = 0; foreach (const ref bucket; buckets) foreach (const ref node; bucket[]) if ((result = del(*cast(V*)&node.value)) != 0) return result; return result; } mixin AllocatorState!Allocator; private: import stdx.allocator : make, makeArray; import containers.unrolledlist : UnrolledList; import containers.internal.storage_type : ContainerStorageType; import containers.internal.element_type : ContainerElementType; import containers.internal.mixins : AllocatorState; import core.memory : GC; enum bool useGC = supportGC && (shouldAddGCRange!K || shouldAddGCRange!V); alias Hash = typeof({ K k = void; return hashFunction(k); }()); enum IterType: ubyte { key, value, both } static struct MapRange(MapType, IterType Type) { static if (Type == IterType.both) { struct FrontType { ContainerElementType!(MapType, K) key; ContainerElementType!(MapType, V) value; } } else static if (Type == IterType.value) alias FrontType = ContainerElementType!(MapType, V); else static if (Type == IterType.key) alias FrontType = ContainerElementType!(MapType, K); else static assert(false); FrontType front() { static if (Type == IterType.both) return FrontType(cast(ContainerElementType!(MapType, K)) bucketRange.front.key, cast(ContainerElementType!(MapType, V)) bucketRange.front.value); else static if (Type == IterType.value) return cast(ContainerElementType!(MapType, V)) bucketRange.front.value; else static if (Type == IterType.key) return cast(ContainerElementType!(MapType, K)) bucketRange.front.key; else static assert(false); } bool empty() const pure nothrow @nogc @property { return _empty; } void popFront() pure nothrow @nogc { bucketRange.popFront(); if (bucketRange.empty) { while (bucketRange.empty) { bucketIndex++; if (bucketIndex >= hm.buckets.length) { _empty = true; break; } else bucketRange = hm.buckets[bucketIndex][]; } } } private: this(Unqual!(MapType)* hm) { this.hm = hm; this.bucketIndex = 0; bucketRange = typeof(bucketRange).init; this._empty = false; while (true) { if (bucketIndex >= hm.buckets.length) { _empty = true; break; } bucketRange = hm.buckets[bucketIndex][]; if (bucketRange.empty) bucketIndex++; else break; } } Unqual!(MapType)* hm; size_t bucketIndex; typeof(hm.buckets[0].opSlice()) bucketRange; bool _empty; } void initialize(size_t bucketCount = DEFAULT_BUCKET_COUNT) { import std.conv : emplace; assert((bucketCount & (bucketCount - 1)) == 0, "bucketCount must be a power of two"); buckets = makeArray!Bucket(allocator, bucketCount); static if (useGC) GC.addRange(buckets.ptr, buckets.length * Bucket.sizeof); foreach (ref bucket; buckets) { static if (stateSize!Allocator == 0) emplace(&bucket); else emplace(&bucket, allocator); } } Node* insert(const K key, V value) { return insert(key, value, hashFunction(key)); } Node* insert(const K key, V value, const Hash hash, const bool modifyLength = true) { if (buckets.length == 0) initialize(); immutable size_t index = hashToIndex(hash, buckets.length); foreach (ref item; buckets[index]) { if (item.hash == hash && item.key == key) { item.value = value; return &item; } } static if (storeHash) Node node = Node(hash, cast(ContainerStorageType!K) key, value); else Node node = Node(cast(ContainerStorageType!K) key, value); Node* n = buckets[index].insertAnywhere(node); if (modifyLength) _length++; if (shouldRehash()) { rehash(); immutable newIndex = hashToIndex(hash, buckets.length); foreach (ref item; buckets[newIndex]) { if (item.hash == hash && item.key == key) return &item; } assert(false); } else return n; } /** * Returns: true if the load factor has been exceeded */ bool shouldRehash() const pure nothrow @safe @nogc { // We let this be greater than one because each bucket is an unrolled // list that has more than one element per linked list node. return (float(_length) / float(buckets.length)) > 1.33f; } /** * Rehash the map. */ void rehash() @trusted { import std.conv : emplace; immutable size_t newLength = buckets.length << 1; immutable size_t newSize = newLength * Bucket.sizeof; Bucket[] oldBuckets = buckets; assert (oldBuckets.ptr == buckets.ptr); buckets = cast(Bucket[]) allocator.allocate(newSize); static if (useGC) GC.addRange(buckets.ptr, buckets.length * Bucket.sizeof); assert (buckets); assert (buckets.length == newLength); foreach (ref bucket; buckets) { static if (stateSize!Allocator == 0) emplace(&bucket); else emplace(&bucket, allocator); } foreach (ref bucket; oldBuckets) { foreach (node; bucket) insert(cast(K) node.key, node.value, node.hash, false); typeid(typeof(bucket)).destroy(&bucket); } static if (useGC) GC.removeRange(oldBuckets.ptr); allocator.deallocate(cast(void[]) oldBuckets); } inout(Node)* find(const K key, ref size_t index) inout { return find(key, index, hashFunction(key)); } inout(Node)* find(const K key, ref size_t index, const Hash hash) inout { import std.array : empty; if (buckets.empty) return null; index = hashToIndex(hash, buckets.length); foreach (ref r; buckets[index]) { if (r.hash == hash && r.key == key) return cast(inout(Node)*) &r; } return null; } struct Node { bool opEquals(ref const K key) const { return key == this.key; } bool opEquals(ref const Node n) const { return this.hash == n.hash && this.key == n.key; } static if (storeHash) Hash hash; else @property Hash hash() const { return hashFunction(key); } ContainerStorageType!K key; ContainerStorageType!V value; } alias Bucket = UnrolledList!(Node, Allocator, useGC); Bucket[] buckets; size_t _length; } /// unittest { import std.uuid : randomUUID; import std.range.primitives : walkLength; auto hm = HashMap!(string, int)(16); assert (hm.length == 0); assert (!hm.remove("abc")); hm["answer"] = 42; assert (hm.length == 1); assert ("answer" in hm); assert (hm.containsKey("answer")); hm.remove("answer"); assert (hm.length == 0); assert ("answer" !in hm); assert (hm.get("answer", 1000) == 1000); assert (!hm.containsKey("answer")); hm["one"] = 1; hm["one"] = 1; assert (hm.length == 1); assert (hm["one"] == 1); hm["one"] = 2; assert(hm["one"] == 2); foreach (i; 0 .. 1000) { hm[randomUUID().toString] = i; } assert (hm.length == 1001); assert (hm.keys().length == hm.length); assert (hm.values().length == hm.length); () @nogc { assert (hm.byKey().walkLength == hm.length); assert (hm.byValue().walkLength == hm.length); assert (hm[].walkLength == hm.length); assert (hm.byKeyValue().walkLength == hm.length); }(); foreach (v; hm) {} auto hm2 = HashMap!(char, char)(4); hm2['a'] = 'a'; HashMap!(int, int) hm3; assert (hm3.get(100, 20) == 20); hm3[100] = 1; assert (hm3.get(100, 20) == 1); auto pValue = 100 in hm3; assert(*pValue == 1); } version(emsi_containers_unittest) unittest { static class Foo { string name; } void someFunc(ref in HashMap!(string,Foo) map) @safe { foreach (kv; map.byKeyValue()) { assert (kv.key == "foo"); assert (kv.value.name == "Foo"); } } auto hm = HashMap!(string, Foo)(16); auto f = new Foo; f.name = "Foo"; hm.insert("foo", f); assert("foo" in hm); } // Issue #54 version(emsi_containers_unittest) unittest { HashMap!(string, int) map; map.insert("foo", 0); map.insert("bar", 0); foreach (key; map.keys()) map[key] = 1; foreach (key; map.byKey()) map[key] = 1; foreach (value; map.byValue()) assert(value == 1); foreach (value; map.values()) assert(value == 1); } version(emsi_containers_unittest) unittest { HashMap!(int, int, Mallocator, (int i) => i) map; auto p = map.getOrAdd(1, 1); assert(*p == 1); *p = 2; assert(map[1] == 2); } debug (EMSI_CONTAINERS) version(emsi_containers_unittest) unittest { import std.uuid : randomUUID; import std.algorithm.iteration : walkLength; import std.stdio; auto hm = HashMap!(string, int)(16); foreach (i; 0 .. 1_000_000) { auto str = randomUUID().toString; //writeln("Inserting ", str); hm[str] = i; //if (i > 0 && i % 100 == 0) //writeln(i); } writeln(hm.buckets.length); import std.algorithm.sorting:sort; ulong[ulong] counts; foreach (i, ref bucket; hm.buckets[]) counts[bucket.length]++; foreach (k; counts.keys.sort()) writeln(k, "=>", counts[k]); } // #74 version(emsi_containers_unittest) unittest { HashMap!(string, size_t) aa; aa["b"] = 0; ++aa["b"]; assert(aa["b"] == 1); } // storeHash == false version(emsi_containers_unittest) unittest { static struct S { size_t v; } HashMap!(S, S, Mallocator, (S s) { return s.v; }, false, false) aa; static assert(aa.Node.sizeof == 2 * S.sizeof); } version(emsi_containers_unittest) unittest { auto hm = HashMap!(string, int)(16); foreach (v; hm) {} foreach (ref v; hm) {} foreach (int v; hm) {} foreach (ref int v; hm) {} foreach (const ref int v; hm) {} foreach (k, v; hm) {} foreach (k, ref v; hm) {} foreach (k, int v; hm) {} foreach (k, ref int v; hm) {} foreach (k, const ref int v; hm) {} foreach (ref k, v; hm) {} foreach (ref k, ref v; hm) {} foreach (ref k, int v; hm) {} foreach (ref k, ref int v; hm) {} foreach (ref k, const ref int v; hm) {} foreach (const string k, v; hm) {} foreach (const string k, ref v; hm) {} foreach (const string k, int v; hm) {} foreach (const string k, ref int v; hm) {} foreach (const string k, const ref int v; hm) {} foreach (const ref string k, v; hm) {} foreach (const ref string k, ref v; hm) {} foreach (const ref string k, int v; hm) {} foreach (const ref string k, ref int v; hm) {} foreach (const ref string k, const ref int v; hm) {} hm["a"] = 1; foreach (k, ref v; hm) { v++; } assert(hm["a"] == 2); } dcontainers-0.8.0~alpha.16/src/containers/hashset.d000066400000000000000000000371731353550446300222440ustar00rootroot00000000000000/** * Hash Set * Copyright: © 2015 Economic Modeling Specialists, Intl. * Authors: Brian Schott * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) */ module containers.hashset; private import containers.internal.hash : generateHash, hashToIndex; private import containers.internal.node : shouldAddGCRange; private import stdx.allocator.mallocator : Mallocator; private import std.traits : isBasicType; /** * Hash Set. * Params: * T = the element type * Allocator = the allocator to use. Defaults to `Mallocator`. * hashFunction = the hash function to use on the elements * supportGC = true if the container should support holding references to * GC-allocated memory. */ struct HashSet(T, Allocator = Mallocator, alias hashFunction = generateHash!T, bool supportGC = shouldAddGCRange!T, bool storeHash = !isBasicType!T) { this(this) @disable; private import stdx.allocator.common : stateSize; static if (stateSize!Allocator != 0) { this() @disable; /** * Use the given `allocator` for allocations. */ this(Allocator allocator) in { assert(allocator !is null, "Allocator must not be null"); } body { this.allocator = allocator; } /** * Constructs a HashSet with an initial bucket count of bucketCount. * bucketCount must be a power of two. */ this(size_t bucketCount, Allocator allocator) in { assert(allocator !is null, "Allocator must not be null"); assert ((bucketCount & (bucketCount - 1)) == 0, "bucketCount must be a power of two"); } body { this.allocator = allocator; initialize(bucketCount); } } else { /** * Constructs a HashSet with an initial bucket count of bucketCount. * bucketCount must be a power of two. */ this(size_t bucketCount) in { assert ((bucketCount & (bucketCount - 1)) == 0, "bucketCount must be a power of two"); } body { initialize(bucketCount); } } ~this() { import stdx.allocator : dispose; import core.memory : GC; static if (useGC) GC.removeRange(buckets.ptr); allocator.dispose(buckets); } /** * Removes all items from the set */ void clear() { foreach (ref bucket; buckets) { destroy(bucket); bucket = Bucket.init; } _length = 0; } /** * Removes the given item from the set. * Returns: false if the value was not present */ bool remove(T value) { if (buckets.length == 0) return false; immutable Hash hash = hashFunction(value); immutable size_t index = hashToIndex(hash, buckets.length); static if (storeHash) immutable bool removed = buckets[index].remove(ItemNode(hash, value)); else immutable bool removed = buckets[index].remove(ItemNode(value)); if (removed) --_length; return removed; } /** * Returns: true if value is contained in the set. */ bool contains(T value) inout { return (value in this) !is null; } /** * Supports $(B a in b) syntax */ inout(T)* opBinaryRight(string op)(T value) inout if (op == "in") { if (buckets.length == 0 || _length == 0) return null; immutable Hash hash = hashFunction(value); immutable index = hashToIndex(hash, buckets.length); return buckets[index].get(value, hash); } /** * Inserts the given item into the set. * Params: value = the value to insert * Returns: true if the value was actually inserted, or false if it was * already present. */ bool insert(T value) { if (buckets.length == 0) initialize(4); Hash hash = hashFunction(value); immutable size_t index = hashToIndex(hash, buckets.length); static if (storeHash) auto r = buckets[index].insert(ItemNode(hash, value)); else auto r = buckets[index].insert(ItemNode(value)); if (r) ++_length; if (shouldRehash) rehash(); return r; } /// ditto bool opOpAssign(string op)(T item) if (op == "~") { return insert(item); } /// ditto alias put = insert; /// ditto alias insertAnywhere = insert; /** * Returns: true if the set has no items */ bool empty() const nothrow pure @nogc @safe @property { return _length == 0; } /** * Returns: the number of items in the set */ size_t length() const nothrow pure @nogc @safe @property { return _length; } /** * Forward range interface */ auto opSlice(this This)() nothrow @nogc @trusted { return Range!(This)(&this); } private: import containers.internal.element_type : ContainerElementType; import containers.internal.mixins : AllocatorState; import containers.internal.node : shouldAddGCRange, FatNodeInfo; import containers.internal.storage_type : ContainerStorageType; import std.traits : isPointer; alias LengthType = ubyte; alias N = FatNodeInfo!(ItemNode.sizeof, 1, 64, LengthType.sizeof); enum ITEMS_PER_NODE = N[0]; static assert(LengthType.max > ITEMS_PER_NODE); enum bool useGC = supportGC && shouldAddGCRange!T; alias Hash = typeof({ T v = void; return hashFunction(v); }()); void initialize(size_t bucketCount) { import core.memory : GC; import stdx.allocator : makeArray; makeBuckets(bucketCount); static if (useGC) GC.addRange(buckets.ptr, buckets.length * Bucket.sizeof); } static struct Range(ThisT) { this(ThisT* t) { foreach (i, ref bucket; t.buckets) { bucketIndex = i; if (bucket.root !is null) { currentNode = cast(Bucket.BucketNode*) bucket.root; break; } } this.t = t; } bool empty() const nothrow @safe @nogc @property { return currentNode is null; } ET front() nothrow @safe @nogc @property { return cast(ET) currentNode.items[nodeIndex].value; } void popFront() nothrow @trusted @nogc { if (nodeIndex + 1 < currentNode.l) { ++nodeIndex; return; } else { nodeIndex = 0; if (currentNode.next is null) { ++bucketIndex; while (bucketIndex < t.buckets.length && t.buckets[bucketIndex].root is null) ++bucketIndex; if (bucketIndex < t.buckets.length) currentNode = cast(Bucket.BucketNode*) t.buckets[bucketIndex].root; else currentNode = null; } else { currentNode = currentNode.next; assert(currentNode.l > 0); } } } private: alias ET = ContainerElementType!(ThisT, T); ThisT* t; Bucket.BucketNode* currentNode; size_t bucketIndex; size_t nodeIndex; } void makeBuckets(size_t bucketCount) { import stdx.allocator : makeArray; static if (stateSize!Allocator == 0) buckets = allocator.makeArray!Bucket(bucketCount); else { import std.conv:emplace; buckets = cast(Bucket[]) allocator.allocate(Bucket.sizeof * bucketCount); foreach (ref bucket; buckets) emplace!Bucket(&bucket, allocator); } } bool shouldRehash() const pure nothrow @safe @nogc { immutable float numberOfNodes = cast(float) _length / cast(float) ITEMS_PER_NODE; return (numberOfNodes / cast(float) buckets.length) > 0.75f; } void rehash() @trusted { import stdx.allocator : makeArray, dispose; import core.memory : GC; immutable size_t newLength = buckets.length << 1; Bucket[] oldBuckets = buckets; makeBuckets(newLength); assert (buckets); assert (buckets.length == newLength); static if (useGC) GC.addRange(buckets.ptr, buckets.length * Bucket.sizeof); foreach (ref const bucket; oldBuckets) { for (Bucket.BucketNode* node = cast(Bucket.BucketNode*) bucket.root; node !is null; node = node.next) { for (size_t i = 0; i < node.l; ++i) { static if (storeHash) { immutable Hash hash = node.items[i].hash; size_t index = hashToIndex(hash, buckets.length); buckets[index].insert(ItemNode(hash, node.items[i].value)); } else { immutable Hash hash = hashFunction(node.items[i].value); size_t index = hashToIndex(hash, buckets.length); buckets[index].insert(ItemNode(node.items[i].value)); } } } } static if (useGC) GC.removeRange(oldBuckets.ptr); allocator.dispose(oldBuckets); } static struct Bucket { this(this) @disable; static if (stateSize!Allocator != 0) { this(Allocator allocator) { this.allocator = allocator; } this() @disable; } ~this() { import core.memory : GC; import stdx.allocator : dispose; BucketNode* current = root; BucketNode* previous; while (true) { if (previous !is null) { static if (useGC) GC.removeRange(previous); allocator.dispose(previous); } previous = current; if (current is null) break; current = current.next; } } static struct BucketNode { ContainerStorageType!(T)* get(ItemNode n) { foreach (ref item; items[0 .. l]) { static if (storeHash) { static if (isPointer!T) { if (item.hash == n.hash && *item.value == *n.value) return &item.value; } else { if (item.hash == n.hash && item.value == n.value) return &item.value; } } else { static if (isPointer!T) { if (*item.value == *n.value) return &item.value; } else { if (item.value == n.value) return &item.value; } } } return null; } void insert(ItemNode n) { items[l] = n; ++l; } bool remove(ItemNode n) { import std.algorithm : SwapStrategy, remove; foreach (size_t i, ref node; items[0 .. l]) { static if (storeHash) { static if (isPointer!T) immutable bool matches = node.hash == n.hash && *node.value == *n.value; else immutable bool matches = node.hash == n.hash && node.value == n.value; } else { static if (isPointer!T) immutable bool matches = *node.value == *n.value; else immutable bool matches = node.value == n.value; } if (matches) { items[0 .. l].remove!(SwapStrategy.unstable)(i); l--; return true; } } return false; } BucketNode* next; LengthType l; ItemNode[ITEMS_PER_NODE] items; } bool insert(ItemNode n) { import core.memory : GC; import stdx.allocator : make; BucketNode* hasSpace = null; for (BucketNode* current = root; current !is null; current = current.next) { if (current.get(n) !is null) return false; if (current.l < current.items.length) hasSpace = current; } if (hasSpace !is null) hasSpace.insert(n); else { BucketNode* newNode = make!BucketNode(allocator); static if (useGC) GC.addRange(newNode, BucketNode.sizeof); newNode.insert(n); newNode.next = root; root = newNode; } return true; } bool remove(ItemNode n) { import core.memory : GC; import stdx.allocator : dispose; BucketNode* current = root; BucketNode* previous; while (current !is null) { immutable removed = current.remove(n); if (removed) { if (current.l == 0) { if (previous !is null) previous.next = current.next; else root = current.next; static if (useGC) GC.removeRange(current); allocator.dispose(current); } return true; } previous = current; current = current.next; } return false; } inout(T)* get(T value, Hash hash) inout { for (BucketNode* current = cast(BucketNode*) root; current !is null; current = current.next) { static if (storeHash) { auto v = current.get(ItemNode(hash, value)); } else { auto v = current.get(ItemNode(value)); } if (v !is null) return cast(typeof(return)) v; } return null; } BucketNode* root; mixin AllocatorState!Allocator; } static struct ItemNode { bool opEquals(ref const T v) const { static if (isPointer!T) return *v == *value; else return v == value; } bool opEquals(ref const ItemNode other) const { static if (storeHash) if (other.hash != hash) return false; static if (isPointer!T) return *other.value == *value; else return other.value == value; } static if (storeHash) Hash hash; ContainerStorageType!T value; static if (storeHash) { this(Z)(Hash nh, Z nv) { this.hash = nh; this.value = nv; } } else { this(Z)(Z nv) { this.value = nv; } } } mixin AllocatorState!Allocator; Bucket[] buckets; size_t _length; } /// version(emsi_containers_unittest) unittest { import std.algorithm : canFind; import std.array : array; import std.range : walkLength; import std.uuid : randomUUID; auto s = HashSet!string(16); assert(s.remove("DoesNotExist") == false); assert(!s.contains("nonsense")); assert(s.put("test")); assert(s.contains("test")); assert(!s.put("test")); assert(s.contains("test")); assert(s.length == 1); assert(!s.contains("nothere")); s.put("a"); s.put("b"); s.put("c"); s.put("d"); string[] strings = s[].array; assert(strings.canFind("a")); assert(strings.canFind("b")); assert(strings.canFind("c")); assert(strings.canFind("d")); assert(strings.canFind("test")); assert(*("a" in s) == "a"); assert(*("b" in s) == "b"); assert(*("c" in s) == "c"); assert(*("d" in s) == "d"); assert(*("test" in s) == "test"); assert(strings.length == 5); assert(s.remove("test")); assert(s.length == 4); s.clear(); assert(s.length == 0); assert(s.empty); s.put("abcde"); assert(s.length == 1); foreach (i; 0 .. 10_000) { s.put(randomUUID().toString); } assert(s.length == 10_001); // Make sure that there's no range violation slicing an empty set HashSet!int e; foreach (i; e[]) assert(i > 0); enum MAGICAL_NUMBER = 600_000; HashSet!int f; foreach (i; 0 .. MAGICAL_NUMBER) assert(f.insert(i)); assert(f.length == f[].walkLength); foreach (i; 0 .. MAGICAL_NUMBER) assert(i in f); foreach (i; 0 .. MAGICAL_NUMBER) assert(f.remove(i)); foreach (i; 0 .. MAGICAL_NUMBER) assert(!f.remove(i)); HashSet!int g; foreach (i; 0 .. MAGICAL_NUMBER) assert(g.insert(i)); static struct AStruct { int a; int b; } HashSet!(AStruct*, Mallocator, a => a.a) fred; fred.insert(new AStruct(10, 10)); auto h = new AStruct(10, 10); assert(h in fred); } version(emsi_containers_unittest) unittest { static class Foo { string name; } hash_t stringToHash(string str) @safe pure nothrow @nogc { hash_t hash = 5381; return hash; } hash_t FooToHash(Foo e) pure @safe nothrow @nogc { return stringToHash(e.name); } HashSet!(Foo, Mallocator, FooToHash) hs; auto f = new Foo; hs.insert(f); assert(f in hs); auto r = hs[]; } version(emsi_containers_unittest) unittest { static class Foo { string name; this(string n) { this.name = n; } bool opEquals(const Foo of) const { if(of !is null) { return this.name == of.name; } else { return false; } } } hash_t stringToHash(string str) @safe pure nothrow @nogc { hash_t hash = 5381; return hash; } hash_t FooToHash(in Foo e) pure @safe nothrow @nogc { return stringToHash(e.name); } string foo = "foo"; HashSet!(const(Foo), Mallocator, FooToHash) hs; const(Foo) f = new const Foo(foo); hs.insert(f); assert(f in hs); auto r = hs[]; assert(!r.empty); auto fro = r.front; assert(fro.name == foo); } version(emsi_containers_unittest) unittest { hash_t maxCollision(ulong x) { return 0; } HashSet!(ulong, Mallocator, maxCollision) set; auto ipn = set.ITEMS_PER_NODE; // Need this info to trigger this bug, so I made it public assert(ipn > 1); // Won't be able to trigger this bug if there's only 1 item per node foreach (i; 0 .. 2 * ipn - 1) set.insert(i); assert(0 in set); // OK bool ret = set.insert(0); // 0 should be already in the set assert(!ret); // Fails assert(set.length == 2 * ipn - 1); // Fails } version(emsi_containers_unittest) unittest { import stdx.allocator.showcase; auto allocator = mmapRegionList(1024); auto set = HashSet!(ulong, typeof(&allocator))(0x1000, &allocator); set.insert(124); } dcontainers-0.8.0~alpha.16/src/containers/immutablehashset.d000066400000000000000000000112541353550446300241340ustar00rootroot00000000000000/** * Immutable hash set. * Copyright: © 2015 Economic Modeling Specialists, Intl. * Authors: Brian Schott * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) */ module containers.immutablehashset; /** * The immutable hash set is useful for constructing a read-only collection that * supports quickly determining if an element is present. * * Because the set does not support inserting, it only takes up as much memory * as is necessary to contain the elements provided at construction. Memory is * managed my malloc/free. */ struct ImmutableHashSet(T, alias hashFunction) { /// @disable this(); /// @disable this(this); /** * Constructs an immutable hash set from the given values. The values must * not have any duplicates. */ this(const T[] values) immutable in { import std.algorithm : sort, uniq; import std.array : array; assert (values.dup.sort().uniq().array().length == values.length); } body { empty = values.length == 0; length = values.length; if (empty) return; immutable float a = (cast(float) values.length) / .75f; size_t bucketCount = 1; while (bucketCount <= cast(size_t) a) bucketCount <<= 1; Node[][] mutableBuckets = cast(Node[][]) Mallocator.instance.allocate((Node[]).sizeof * bucketCount); Node[] mutableNodes = cast(Node[]) Mallocator.instance.allocate(Node.sizeof * values.length); size_t[] lengths = cast(size_t[]) Mallocator.instance.allocate(size_t.sizeof * bucketCount); lengths[] = 0; scope(exit) Mallocator.instance.deallocate(lengths); size_t[] indexes = cast(size_t[]) Mallocator.instance.allocate(size_t.sizeof * values.length); scope(exit) Mallocator.instance.deallocate(indexes); size_t[] hashes = cast(size_t[]) Mallocator.instance.allocate(size_t.sizeof * values.length); scope(exit) Mallocator.instance.deallocate(hashes); foreach (i, ref value; values) { hashes[i] = hashFunction(value); indexes[i] = hashes[i] & (mutableBuckets.length - 1); lengths[indexes[i]]++; } size_t j = 0; foreach (i, l; lengths) { mutableBuckets[i] = mutableNodes[j .. j + l]; j += l; } lengths[] = 0; foreach (i; 0 .. values.length) { immutable l = lengths[indexes[i]]; static if (hasMember!(Node, "hash")) mutableBuckets[indexes[i]][l].hash = hashes[i]; mutableBuckets[indexes[i]][l].value = values[i]; lengths[indexes[i]]++; } buckets = cast(immutable) mutableBuckets; nodes = cast(immutable) mutableNodes; static if (shouldAddGCRange!T) { GC.addRange(buckets.ptr, buckets.length * (Node[]).sizeof); foreach (ref b; buckets) GC.addRange(b.ptr, b.length * Node.sizeof); GC.addRange(nodes.ptr, nodes.length * Node.sizeof); } } ~this() { static if (shouldAddGCRange!T) { GC.removeRange(buckets.ptr); foreach (ref b; buckets) GC.removeRange(b.ptr); GC.removeRange(nodes.ptr); } Mallocator.instance.deallocate(cast(void[]) buckets); Mallocator.instance.deallocate(cast(void[]) nodes); } /** * Returns: A GC-allocated array containing the contents of this set. */ immutable(T)[] opSlice() immutable @safe { if (empty) return []; T[] values = new T[](nodes.length); foreach (i, ref v; values) { v = nodes[i].value; } return values; } /** * Returns: true if this set contains the given value. */ bool contains(T value) immutable { if (empty) return false; immutable size_t hash = hashFunction(value); immutable size_t index = hash & (buckets.length - 1); if (buckets[index].length == 0) return false; foreach (ref node; buckets[index]) { static if (hasMember!(Node, "hash")) if (hash != node.hash) continue; if (node.value != value) continue; return true; } return false; } /** * The number of items in the set. */ immutable size_t length; /** * True if the set is empty. */ immutable bool empty; private: import stdx.allocator.mallocator : Mallocator; import std.traits : isBasicType, hasMember; import containers.internal.node : shouldAddGCRange; import core.memory : GC; static struct Node { T value; static if (!isBasicType!T) size_t hash; } Node[][] buckets; Node[] nodes; } /// version(emsi_containers_unittest) unittest { auto ihs1 = immutable ImmutableHashSet!(int, a => a)([1, 3, 5, 19, 31, 40, 17]); assert (ihs1.contains(1)); assert (ihs1.contains(3)); assert (ihs1.contains(5)); assert (ihs1.contains(19)); assert (ihs1.contains(31)); assert (ihs1.contains(40)); assert (ihs1.contains(17)); assert (!ihs1.contains(100)); assert (ihs1[].length == 7); auto ihs2 = immutable ImmutableHashSet!(int, a => a)([]); assert (ihs2.length == 0); assert (ihs2.empty); assert (ihs2[].length == 0); assert (!ihs2.contains(42)); } dcontainers-0.8.0~alpha.16/src/containers/internal/000077500000000000000000000000001353550446300222415ustar00rootroot00000000000000dcontainers-0.8.0~alpha.16/src/containers/internal/backwards.d000066400000000000000000000011431353550446300243460ustar00rootroot00000000000000module containers.internal.backwards; static if (__VERSION__ < 2071) { /// 64-bit popcnt int popcnt(ulong v) pure nothrow @nogc @safe { import core.bitop : popcnt; return popcnt(cast(uint) v) + popcnt(cast(uint)(v >>> 32)); } version (X86_64) public import core.bitop : bsf; else { /// Allow 64-bit bsf on old compilers int bsf(ulong v) pure nothrow @nogc @safe { import core.bitop : bsf; immutable uint lower = cast(uint) v; immutable uint upper = cast(uint)(v >>> 32); return lower == 0 ? bsf(upper) + 32 : bsf(lower); } } } else public import core.bitop : bsf, popcnt; dcontainers-0.8.0~alpha.16/src/containers/internal/element_type.d000066400000000000000000000141261353550446300251040ustar00rootroot00000000000000/** * Templates for determining range return types. * Copyright: © 2015 Economic Modeling Specialists, Intl. * Authors: Brian Schott * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) */ module containers.internal.element_type; /** * Figures out the types that should be shown to users of the containers when * functions such as front and opIndex are called. * * Params: * ContainerType = The container type, usually taken from a `this This` * template argument * ElementType = The type of the elements of the container. * isRef = If the type is being determined for a function that returns `ref` */ template ContainerElementType(ContainerType, ElementType, bool isRef = false) { import std.traits : isMutable, hasIndirections, PointerTarget, isPointer, Unqual; template ET(bool isConst, T) { static if (isPointer!ElementType) { enum PointerIsConst = is(ElementType == const); enum PointerIsImmutable = is(ElementType == immutable); enum DataIsConst = is(PointerTarget!(ElementType) == const); enum DataIsImmutable = is(PointerTarget!(ElementType) == immutable); static if (isConst) { static if (PointerIsConst) alias ET = ElementType; else static if (PointerIsImmutable) alias ET = ElementType; else alias ET = const(PointerTarget!(ElementType))*; } else { static assert(DataIsImmutable, "An immutable container cannot reference const or mutable data"); static if (PointerIsConst) alias ET = immutable(PointerTarget!ElementType)*; else alias ET = ElementType; } } else { static if (isConst) { static if (is(ElementType == immutable)) alias ET = ElementType; else alias ET = const(Unqual!ElementType); } else alias ET = immutable(Unqual!ElementType); } } static if (isMutable!ContainerType) { alias ContainerElementType = ElementType; } else { static if (isRef || hasIndirections!ElementType) alias ContainerElementType = ET!(is(ContainerType == const), ElementType); else alias ContainerElementType = ElementType; } } unittest { static struct Container {} static struct Data1 { int* x; } static class Data2 { int* x; } static assert(is(ContainerElementType!(Container, int) == int)); static assert(is(ContainerElementType!(Container, const int) == const int)); static assert(is(ContainerElementType!(Container, immutable int) == immutable int)); static assert(is(ContainerElementType!(const Container, int) == int)); static assert(is(ContainerElementType!(const Container, const int) == const int)); static assert(is(ContainerElementType!(const Container, immutable int) == immutable int)); static assert(is(ContainerElementType!(immutable Container, int) == int)); static assert(is(ContainerElementType!(immutable Container, const int) == const int)); static assert(is(ContainerElementType!(immutable Container, immutable int) == immutable int)); static assert(is(ContainerElementType!(Container, Data1) == Data1)); static assert(is(ContainerElementType!(Container, const Data1) == const Data1)); static assert(is(ContainerElementType!(Container, immutable Data1) == immutable Data1)); static assert(is(ContainerElementType!(const Container, Data1) == const Data1)); static assert(is(ContainerElementType!(const Container, const Data1) == const Data1)); static assert(is(ContainerElementType!(const Container, immutable Data1) == immutable Data1)); static assert(is(ContainerElementType!(immutable Container, Data1) == immutable Data1)); static assert(is(ContainerElementType!(immutable Container, const Data1) == immutable Data1)); static assert(is(ContainerElementType!(immutable Container, immutable Data1) == immutable Data1)); static assert(is(ContainerElementType!(Container, Data2) == Data2)); static assert(is(ContainerElementType!(Container, const Data2) == const Data2)); static assert(is(ContainerElementType!(Container, immutable Data2) == immutable Data2)); static assert(is(ContainerElementType!(const Container, Data2) == const Data2)); static assert(is(ContainerElementType!(const Container, const Data2) == const Data2)); static assert(is(ContainerElementType!(const Container, immutable Data2) == immutable Data2)); static assert(is(ContainerElementType!(immutable Container, Data2) == immutable Data2)); static assert(is(ContainerElementType!(immutable Container, const Data2) == immutable Data2)); static assert(is(ContainerElementType!(immutable Container, immutable Data2) == immutable Data2)); static assert(is(ContainerElementType!(Container, int*) == int*)); static assert(is(ContainerElementType!(Container, const int*) == const int*)); static assert(is(ContainerElementType!(Container, const(int)*) == const(int)*)); static assert(is(ContainerElementType!(Container, immutable int*) == immutable int*)); static assert(is(ContainerElementType!(Container, immutable(int)*) == immutable(int)*)); static assert(is(ContainerElementType!(Container, Data1*) == Data1*)); static assert(is(ContainerElementType!(Container, const Data1*) == const Data1*)); static assert(is(ContainerElementType!(Container, const(Data1)*) == const(Data1)*)); static assert(is(ContainerElementType!(Container, immutable Data1*) == immutable Data1*)); static assert(is(ContainerElementType!(Container, immutable(Data1)*) == immutable(Data1)*)); static assert(is(ContainerElementType!(const Container, int*) == const(int)*)); static assert(is(ContainerElementType!(const Container, const int*) == const int*)); static assert(is(ContainerElementType!(const Container, const(int)*) == const(int)*)); static assert(is(ContainerElementType!(const Container, immutable int*) == immutable int*)); static assert(is(ContainerElementType!(const Container, immutable(int)*) == immutable(int)*)); static assert(!__traits(compiles, ContainerElementType!(immutable Container, int*))); static assert(!__traits(compiles, ContainerElementType!(immutable Container, const int*))); static assert(is(ContainerElementType!(immutable Container, immutable int*) == immutable int*)); static assert(is(ContainerElementType!(immutable Container, immutable(int)*) == immutable(int)*)); } dcontainers-0.8.0~alpha.16/src/containers/internal/hash.d000066400000000000000000000032651353550446300233370ustar00rootroot00000000000000/** * Templates for hashing types. * Copyright: © 2015 Economic Modeling Specialists, Intl. * Authors: Brian Schott * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) */ module containers.internal.hash; static if (hash_t.sizeof == 4) { hash_t generateHash(T)(const T value) { return hashOf(value); } } else { hash_t generateHash(T)(T value) if (!is(T == string)) { return hashOf(value); } /** * A variant of the FNV-1a (64) hashing algorithm. */ hash_t generateHash(T)(T value) pure nothrow @nogc @trusted if (is(T == string)) { hash_t h = 0xcbf29ce484222325; foreach (const ubyte c; cast(ubyte[]) value) { h ^= ((c - ' ') * 13); h *= 0x100000001b3; } return h; } } /** * Convert a hash code into a valid array index. * * Prams: * hash = the hash code to be mapped * len = the length of the array that backs the hash container. */ size_t hashToIndex(const size_t hash, const size_t len) pure nothrow @nogc @safe { // This magic number taken from // https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/ // // It's amazing how much faster this makes the hash data structures // when faced with low quality hash functions. static if (size_t.sizeof == 8) enum ulong magic = 11_400_714_819_323_198_485UL; else enum uint magic = 2_654_435_769U; if (len <= 1) return 0; version(LDC) { import ldc.intrinsics : llvm_cttz; return (hash * magic) >>> ((size_t.sizeof * 8) - llvm_cttz(len, true)); } else { import core.bitop : bsf; return (hash * magic) >>> ((size_t.sizeof * 8) - bsf(len)); } } enum size_t DEFAULT_BUCKET_COUNT = 8; dcontainers-0.8.0~alpha.16/src/containers/internal/mixins.d000066400000000000000000000005711353550446300237200ustar00rootroot00000000000000/** * Container mixins * Copyright: © 2015 Economic Modeling Specialists, Intl. * Authors: Brian Schott * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) */ module containers.internal.mixins; mixin template AllocatorState(Allocator) { static if (stateSize!Allocator == 0) alias allocator = Allocator.instance; else Allocator allocator; } dcontainers-0.8.0~alpha.16/src/containers/internal/node.d000066400000000000000000000051311353550446300233330ustar00rootroot00000000000000/** * Templates for container node types. * Copyright: © 2015 Economic Modeling Specialists, Intl. * Authors: Brian Schott * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) */ module containers.internal.node; template FatNodeInfo(size_t bytesPerItem, size_t pointerCount, size_t cacheLineSize = 64, size_t extraSpace = size_t.max) { import std.meta : AliasSeq; import std.format : format; template fatNodeCapacity(alias L, bool CheckLength = true) { enum size_t optimistic = (cacheLineSize - ((void*).sizeof * pointerCount) - L) / bytesPerItem; static if (optimistic > 0) { enum fatNodeCapacity = optimistic; static if (CheckLength) { static assert(optimistic <= L * 8, ("%d bits required for bookkeeping" ~ " but only %d are possible. Try reducing the cache line size argument.") .format(optimistic, L * 8)); } } else enum fatNodeCapacity = 1; } static if (extraSpace == size_t.max) { static if (__traits(compiles, fatNodeCapacity!(ubyte.sizeof))) alias FatNodeInfo = AliasSeq!(fatNodeCapacity!(ubyte.sizeof), ubyte); else static if (__traits(compiles, fatNodeCapacity!(ushort.sizeof))) alias FatNodeInfo = AliasSeq!(fatNodeCapacity!(ushort.sizeof), ushort); else static if (__traits(compiles, fatNodeCapacity!(uint.sizeof))) alias FatNodeInfo = AliasSeq!(fatNodeCapacity!(uint.sizeof), uint); else static if (__traits(compiles, fatNodeCapacity!(ulong.sizeof))) alias FatNodeInfo = AliasSeq!(fatNodeCapacity!(ulong.sizeof), ulong); else static assert(false, "aaargh" ~ extraSpace.stringof); } else alias FatNodeInfo = AliasSeq!(fatNodeCapacity!(extraSpace, false), void); } // Double linked fat node of int with bookkeeping in a uint should be able to // hold 11 ints per node. // 64 - 16 - 4 = 4 * 11 version (X86_64) static assert (FatNodeInfo!(int.sizeof, 2)[0] == 11); template shouldNullSlot(T) { import std.traits; enum shouldNullSlot = isPointer!T || is (T == class) || is (T == interface) || isDynamicArray!T || is(T == delegate); // closures or class method shoulde be null for GC recycle } template shouldAddGCRange(T) { import std.traits; enum shouldAddGCRange = hasIndirections!T; } static assert (shouldAddGCRange!string); static assert (!shouldAddGCRange!int); template fullBits(T, size_t n, size_t c = 0) { static if (c >= (n - 1)) enum T fullBits = (T(1) << c); else enum T fullBits = (T(1) << c) | fullBits!(T, n, c + 1); } static assert (fullBits!(ushort, 1) == 1); static assert (fullBits!(ushort, 2) == 3); static assert (fullBits!(ushort, 3) == 7); static assert (fullBits!(ushort, 4) == 15); dcontainers-0.8.0~alpha.16/src/containers/internal/storage_type.d000066400000000000000000000101131353550446300251070ustar00rootroot00000000000000/** * Templates for determining data storage types. * Copyright: © 2015 Economic Modeling Specialists, Intl. * Authors: Brian Schott * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) */ module containers.internal.storage_type; /** * This template is used to determine the type that a container should contain * a given type T. * * $(P In many cases it is not possible for a Container!(T) to hold T if T is * $(B const) or $(B immutable). For instance, a binary heap will need to * rearrange items in its internal data storage, or an unrolled list will need * to move items within a node.) * * $(P All containers must only return const references or const copies of data * stored within them if the contained type is const. This template exists * because containers may need to move entire items but not change internal * state of those items and D's type system would otherwise not allow this.) * * $(UL * $(LI If $(B T) is mutable (i.e. not $(B immutable) or $(B const)), this * template aliases itself to T.) * $(LI $(B Class and Interface types:) Rebindable is used. Using this properly * in a container requires that $(LINK2 https://issues.dlang.org/show_bug.cgi?id=8663, * issue 8663) be fixed. Without this fix it is not possible to implement * containers such as a binary heap that must compare container elements * using $(B opCmp())) * $(LI $(B Struct and Union types:) * $(UL * $(LI Stuct and union types that have elaborate constructors, * elaboriate opAssign, or a destructor cannot be stored in containers * because there may be user-visible effects of discarding $(B const) * or $(B immutable) on these struct types.) * $(LI Other struct and union types will be stored as non-const * versions of themselves.) * )) * $(LI $(B Basic types:) Basic types will have their const or immutable status * removed.) * $(LI $(B Pointers:) Pointers will have their type constructors shifted. E.g. * const(int*) becomes const(int)*) * ) */ template ContainerStorageType(T) { import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor, hasElaborateAssign, isBasicType, isDynamicArray, isPointer, Unqual; import std.typecons : Rebindable; static if (is (T == const) || is (T == immutable)) { static if (isBasicType!T || isDynamicArray!T || isPointer!T) alias ContainerStorageType = Unqual!T; else static if (is (T == class) || is (T == interface)) alias ContainerStorageType = Rebindable!T; else static if (is (T == struct)) { alias U = Unqual!T; static if (hasElaborateAssign!U || hasElaborateCopyConstructor!U || hasElaborateDestructor!U) static assert (false, "Cannot store " ~ T.stringof ~ " because of postblit, opAssign, or ~this"); else alias ContainerStorageType = U; } else static assert (false, "Don't know how to handle type " ~ T.stringof); } else alias ContainerStorageType = T; } /// unittest { static assert (is (ContainerStorageType!(int) == int)); static assert (is (ContainerStorageType!(const int) == int)); } /// unittest { import std.typecons : Rebindable; static assert (is (ContainerStorageType!(Object) == Object)); static assert (is (ContainerStorageType!(const(Object)) == Rebindable!(const(Object)))); } /// unittest { struct A { int foo; } struct B { void opAssign(typeof(this)) { this.foo *= 2; } int foo;} // A can be stored easily because it is plain data static assert (is (ContainerStorageType!(A) == A)); static assert (is (ContainerStorageType!(const(A)) == A)); // const(B) cannot be stored in the container because of its // opAssign. Casting away const could lead to some very unexpected // behavior. static assert (!is (typeof(ContainerStorageType!(const(B))))); // Mutable B is not a problem static assert (is (ContainerStorageType!(B) == B)); // Arrays can be stored because the entire pointer-length pair is moved as // a unit. static assert (is (ContainerStorageType!(const(int[])) == const(int)[])); } /// unittest { static assert (is (ContainerStorageType!(const(int*)) == const(int)*)); } dcontainers-0.8.0~alpha.16/src/containers/openhashset.d000066400000000000000000000204211353550446300231120ustar00rootroot00000000000000/** * Open-Addressed Hash Set * Copyright: © 2015 Economic Modeling Specialists, Intl. * Authors: Brian Schott * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) */ module containers.openhashset; private import containers.internal.hash; private import containers.internal.node : shouldAddGCRange; private import stdx.allocator.common : stateSize; private import stdx.allocator.mallocator : Mallocator; /** * Simple open-addressed hash set that uses linear probing to resolve sollisions. * * Params: * T = the element type of the hash set * Allocator = the allocator to use. Defaults to `Mallocator`. * hashFunction = the hash function to use * supportGC = if true, calls to GC.addRange and GC.removeRange will be used * to ensure that the GC does not accidentally free memory owned by this * container. */ struct OpenHashSet(T, Allocator = Mallocator, alias hashFunction = generateHash!T, bool supportGC = shouldAddGCRange!T) { /** * Disallow copy construction */ this(this) @disable; static if (stateSize!Allocator != 0) { this() @disable; /** * Use the given `allocator` for allocations. */ this(Allocator allocator) in { assert(allocator !is null, "Allocator must not be null"); } body { this.allocator = allocator; } /** * Initializes the hash set with the given initial capacity. * * Params: * initialCapacity = the initial capacity for the hash set */ this(size_t initialCapacity, Allocator allocator) in { assert(allocator !is null, "Allocator must not be null"); assert ((initialCapacity & initialCapacity - 1) == 0, "initialCapacity must be a power of 2"); } body { this.allocator = allocator; initialize(initialCapacity); } } else { /** * Initializes the hash set with the given initial capacity. * * Params: * initialCapacity = the initial capacity for the hash set */ this(size_t initialCapacity) in { assert ((initialCapacity & initialCapacity - 1) == 0, "initialCapacity must be a power of 2"); } body { initialize(initialCapacity); } } ~this() nothrow { static if (useGC) GC.removeRange(nodes.ptr); allocator.deallocate(nodes); } /** * Removes all items from the hash set. */ void clear() { if (empty) return; foreach (ref node; nodes) { typeid(typeof(node)).destroy(&node); node.used = false; } _length = 0; } /// bool empty() const pure nothrow @nogc @safe @property { return _length == 0; } /// size_t length() const pure nothrow @nogc @safe @property { return _length; } /** * Returns: * $(B true) if the hash set contains the given item, false otherwise. */ bool contains(T item) const { if (empty) return false; immutable size_t hash = hashFunction(item); size_t index = toIndex(nodes, item, hash); if (index == size_t.max) return false; return nodes[index].hash == hash && nodes[index].data == item; } /// ditto bool opBinaryRight(string op)(T item) inout if (op == "in") { return contains(item); } /** * Inserts the given item into the set. * * Returns: * $(B true) if the item was inserted, false if it was already present. */ bool insert(T item) { if (nodes.length == 0) initialize(DEFAULT_BUCKET_COUNT); immutable size_t hash = hashFunction(item); size_t index = toIndex(nodes, item, hash); if (index == size_t.max) { grow(); index = toIndex(nodes, item, hash); } else if (nodes[index].used && nodes[index].hash == hash && nodes[index].data == item) return false; nodes[index].used = true; nodes[index].hash = hash; nodes[index].data = item; _length++; return true; } /// ditto alias put = insert; /// ditto alias insertAnywhere = insert; /// ditto bool opOpAssign(string op)(T item) if (op == "~") { return insert(item); } /** * Params: * item = the item to remove * Returns: * $(B true) if the item was removed, $(B false) if it was not present */ bool remove(T item) { if (empty) return false; immutable size_t hash = hashFunction(item); size_t index = toIndex(nodes, item, hash); if (index == size_t.max) return false; nodes[index].used = false; destroy(nodes[index].data); _length--; return true; } /** * Returns: * A range over the set. */ auto opSlice(this This)() nothrow pure @nogc @safe { return Range!(This)(nodes); } mixin AllocatorState!Allocator; private: import containers.internal.element_type : ContainerElementType; import containers.internal.mixins : AllocatorState; import containers.internal.storage_type : ContainerStorageType; import core.memory : GC; enum bool useGC = supportGC && shouldAddGCRange!T; static struct Range(ThisT) { ET front() { return cast(typeof(return)) nodes[index].data; } bool empty() const pure nothrow @safe @nogc @property { return index >= nodes.length; } void popFront() pure nothrow @safe @nogc { index++; while (index < nodes.length && !nodes[index].used) index++; } private: alias ET = ContainerElementType!(ThisT, T); this(const Node[] nodes) { this.nodes = nodes; while (true) { if (index >= nodes.length || nodes[index].used) break; index++; } } size_t index; const Node[] nodes; } void grow() { immutable size_t newCapacity = nodes.length << 1; Node[] newNodes = (cast (Node*) allocator.allocate(newCapacity * Node.sizeof)) [0 .. newCapacity]; newNodes[] = Node.init; static if (useGC) GC.addRange(newNodes.ptr, newNodes.length * Node.sizeof, typeid(typeof(nodes))); foreach (ref node; nodes) { immutable size_t newIndex = toIndex(newNodes, node.data, node.hash); newNodes[newIndex] = node; } static if (useGC) GC.removeRange(nodes.ptr); allocator.deallocate(nodes); nodes = newNodes; } void initialize(size_t nodeCount) { nodes = (cast (Node*) allocator.allocate(nodeCount * Node.sizeof))[0 .. nodeCount]; static if (useGC) GC.addRange(nodes.ptr, nodes.length * Node.sizeof, typeid(typeof(nodes))); nodes[] = Node.init; _length = 0; } // Returns: size_t.max if the item was not found static size_t toIndex(const Node[] n, T item, size_t hash) { assert (n.length > 0); immutable size_t index = hashToIndex(hash, n.length); size_t i = index; immutable bucketMask = n.length - 1; while (n[i].used && n[i].data != item) { i = (i + 1) & bucketMask; if (i == index) return size_t.max; } return i; } Node[] nodes; size_t _length; static struct Node { ContainerStorageType!T data; bool used; size_t hash; } } version(emsi_containers_unittest) unittest { import std.string : format; import std.algorithm : equal, sort; import std.range : iota; import std.array : array; OpenHashSet!int ints; assert (ints.empty); assert (equal(ints[], cast(int[]) [])); ints.clear(); ints.insert(10); assert (!ints.empty); assert (ints.length == 1); assert (equal(ints[], [10])); assert (ints.contains(10)); ints.clear(); assert (ints.length == 0); assert (ints.empty); ints ~= 0; assert (!ints.empty); assert (ints.length == 1); assert (equal(ints[], [0])); ints.clear(); assert (ints.length == 0); assert (ints.empty); foreach (i; 0 .. 100) ints ~= i; assert (ints.length == 100, "%d".format(ints.length)); assert (!ints.empty); foreach (i; 0 .. 100) assert (i in ints); assert (equal(ints[].array().sort(), iota(0, 100))); assert (ints.insert(10) == false); auto ohs = OpenHashSet!int(8); assert (!ohs.remove(1000)); assert (ohs.contains(99) == false); assert (ohs.insert(10) == true); assert (ohs.insert(10) == false); foreach (i; 0 .. 7) ohs.insert(i); assert (ohs.contains(6)); assert (!ohs.contains(100)); assert (!ohs.remove(9999)); assert (ohs.remove(0)); assert (ohs.remove(1)); } version(emsi_containers_unittest) unittest { static class Foo { string name; override bool opEquals(Object other) const @safe pure nothrow @nogc { Foo f = cast(Foo)other; return f !is null && f.name == this.name; } } hash_t stringToHash(string str) @safe pure nothrow @nogc { hash_t hash = 5381; return hash; } hash_t FooToHash(Foo e) pure @safe nothrow @nogc { return stringToHash(e.name); } OpenHashSet!(Foo, Mallocator, FooToHash) hs; auto f = new Foo; hs.insert(f); assert(f in hs); auto r = hs[]; } dcontainers-0.8.0~alpha.16/src/containers/package.d000066400000000000000000000007461353550446300221740ustar00rootroot00000000000000module containers; /** * This package file imports all other containers modules. */ public import containers.cyclicbuffer; public import containers.dynamicarray; public import containers.hashmap; public import containers.hashset; public import containers.immutablehashset; public import containers.openhashset; public import containers.simdset; public import containers.slist; public import containers.treemap; public import containers.ttree; public import containers.unrolledlist; dcontainers-0.8.0~alpha.16/src/containers/simdset.d000066400000000000000000000145601353550446300222500ustar00rootroot00000000000000/** * SIMD-accelerated Set * Copyright: © 2015 Economic Modeling Specialists, Intl. * Authors: Brian Schott * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt Boost License 1.0) */ module containers.simdset; private import stdx.allocator.mallocator : Mallocator; /** * Set implementation that is well suited for small sets and simple items. * * Uses SSE instructions to compare multiple elements simultaneously, but has * linear time complexity. * * Note: Only works on x86_64. Does NOT add GC ranges. Do not store pointers in * this container unless they are also stored somewhere else. * * Params: * T = the element type * Allocator = the allocator to use. Defaults to `Mallocator`. */ version (D_InlineAsm_X86_64) struct SimdSet(T, Allocator = Mallocator) if (T.sizeof == 1 || T.sizeof == 2 || T.sizeof == 4 || T.sizeof == 8) { this(this) @disable; private import stdx.allocator.common : stateSize; static if (stateSize!Allocator != 0) { /// No default construction if an allocator must be provided. this() @disable; /** * Use the given `allocator` for allocations. */ this(Allocator allocator) in { assert(allocator !is null, "Allocator must not be null"); } body { this.allocator = allocator; } } ~this() { scope (failure) assert(false); clear(); } void clear() { allocator.deallocate(cast(void[]) storage); _length = 0; storage = []; } /** * Params: * item = the item to check * Returns: * true if the set contains the given item */ bool contains(T item) const pure nothrow @nogc @trusted { if (_length == 0) return false; bool retVal; immutable remainder = _length % (16 / T.sizeof); ushort mask = remainder == 0 ? 0xffff : (1 << (remainder * T.sizeof)) - 1; //ushort resultMask; ulong ptrStart = cast(ulong) storage.ptr; ulong ptrEnd = ptrStart + storage.length * T.sizeof; static if (T.sizeof == 1) ulong needle = (cast(ubyte) item) * 0x01010101_01010101; else static if (T.sizeof == 2) ulong needle = (cast(ushort) item) * 0x00010001_00010001; else static if (T.sizeof == 4) ulong needle = (cast(ulong) item) * 0x00000001_00000001; else static if (T.sizeof == 8) ulong needle = cast(ulong) item; else static assert(false); mixin(asmSearch()); end: return retVal; } /// ditto bool opBinaryRight(string op)(T item) const pure nothrow @nogc @safe if (op == "in") { return contains(item); } /** * Inserts the given item into the set. * * Params: * item = the item to insert * Returns: * true if the item was inserted or false if it was already present */ bool insert(T item) { if (contains(item)) return false; if (storage.length > _length) storage[_length] = item; else { immutable size_t cl = (storage.length * T.sizeof); immutable size_t nl = cl + 16; void[] a = cast(void[]) storage; allocator.reallocate(a, nl); storage = cast(typeof(storage)) a; storage[_length] = item; } _length++; return true; } /// ditto bool opOpAssign(string op)(T item) if (op == "~") { return insert(item); } /// ditto alias insertAnywhere = insert; /// ditto alias put = insert; /** * Removes the given item from the set. * * Params: * item = the time to remove * Returns: * true if the item was removed, false if it was not present */ bool remove(T item) { import std.algorithm : countUntil; // TODO: Make this more efficient ptrdiff_t begin = countUntil(storage, item); if (begin == -1) return false; foreach (i; begin .. _length - 1) storage[i] = storage[i + 1]; _length--; return true; } /** * Slice operator */ auto opSlice(this This)() { import containers.internal.element_type : ContainerElementType; return cast(ContainerElementType!(This, T)[]) storage[0 .. _length]; } /** * Returns: * the number of items in the set */ size_t length() const pure nothrow @nogc @property { return _length; } invariant { assert((storage.length * T.sizeof) % 16 == 0); } private: import containers.internal.storage_type : ContainerStorageType; private import containers.internal.mixins : AllocatorState; static string asmSearch() { import std.string : format; static if (T.sizeof == 1) enum instruction = `pcmpeqb`; else static if (T.sizeof == 2) enum instruction = `pcmpeqw`; else static if (T.sizeof == 4) enum instruction = `pcmpeqd`; else static if (T.sizeof == 8) enum instruction = `pcmpeqq`; else static assert(false); static if (__VERSION__ >= 2067) string s = `asm pure nothrow @nogc`; else string s = `asm`; return s ~ ` { mov R8, ptrStart; mov R9, ptrEnd; sub R8, 16; sub R9, 16; movq XMM0, needle; shufpd XMM0, XMM0, 0; loop: add R8, 16; movdqu XMM1, [R8]; %s XMM1, XMM0; pmovmskb RAX, XMM1; //mov resultMask, AX; mov BX, AX; and BX, mask; cmp R8, R9; cmove AX, BX; popcnt AX, AX; test AX, AX; jnz found; cmp R8, R9; jl loop; mov retVal, 0; jmp end; found: mov retVal, 1; jmp end; }`.format(instruction); } mixin AllocatorState!Allocator; ContainerStorageType!(T)[] storage; size_t _length; } /// version (D_InlineAsm_X86_64) version(emsi_containers_unittest) unittest { import std.string : format; void testSimdSet(T)() { SimdSet!T set; assert(set.insert(1)); assert(set.length == 1); assert(set.contains(1)); assert(!set.insert(1)); set.insert(0); set.insert(20); assert(set.contains(1)); assert(set.contains(0)); assert(!set.contains(10)); assert(!set.contains(50)); assert(set.contains(20)); foreach (T i; 28 .. 127) set.insert(i); foreach (T i; 28 .. 127) assert(set.contains(i), "%d".format(i)); foreach (T i; 28 .. 127) assert(set.remove(i)); assert(set.length == 3, "%d".format(set.length)); assert(set.contains(0)); assert(set.contains(1)); assert(set.contains(20)); assert(!set.contains(28)); } testSimdSet!ubyte(); testSimdSet!ushort(); testSimdSet!uint(); testSimdSet!ulong(); testSimdSet!byte(); testSimdSet!short(); testSimdSet!int(); testSimdSet!long(); } version (D_InlineAsm_X86_64) struct SimdSet(T) if (!(T.sizeof == 1 || T.sizeof == 2 || T.sizeof == 4 || T.sizeof == 8)) { import std.string : format; static assert (false, ("Cannot instantiate SimdSet of type %s because its size " ~ "(%d) does not fit evenly into XMM registers.").format(T.stringof, T.sizeof)); } dcontainers-0.8.0~alpha.16/src/containers/slist.d000066400000000000000000000134461353550446300217400ustar00rootroot00000000000000/** * Singly-linked list. * Copyright: © 2015 Economic Modeling Specialists, Intl. * Authors: Brian Schott * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) */ module containers.slist; private import containers.internal.node : shouldAddGCRange; private import stdx.allocator.mallocator : Mallocator; /** * Single-linked allocator-backed list. * Params: * T = the element type * Allocator = the allocator to use. Defaults to `Mallocator`. * supportGC = true if the container should support holding references to * GC-allocated memory. */ struct SList(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T) { /// Disable copying. this(this) @disable; private import stdx.allocator.common : stateSize; static if (stateSize!Allocator != 0) { /// No default construction if an allocator must be provided. this() @disable; /** * Use the given `allocator` for allocations. */ this(Allocator allocator) in { assert(allocator !is null, "Allocator must not be null"); } body { this.allocator = allocator; } } ~this() { Node* current = _front; Node* prev = null; while (current !is null) { prev = current; current = current.next; typeid(Node).destroy(prev); static if (useGC) { import core.memory : GC; GC.removeRange(prev); } allocator.dispose(prev); } _front = null; } /** * Complexity: O(1) * Returns: the most recently inserted item */ auto front(this This)() inout @property in { assert (!empty); } do { alias ET = ContainerElementType!(This, T); return cast(ET) _front.value; } /** * Complexity: O(length) * Returns: the least recently inserted item. */ auto back(this This)() inout @property in { assert (!empty); } do { alias ET = ContainerElementType!(This, T); auto n = _front; for (; front.next !is null; n = n.next) {} return cast(ET) n.value; } /** * Removes the first item in the list. * * Complexity: O(1) * Returns: the first item in the list. */ T moveFront() in { assert (!empty); } do { Node* f = _front; _front = f.next; T r = f.value; static if (useGC) { import core.memory : GC; GC.removeRange(f); } allocator.dispose(f); --_length; return r; } /** * Removes the first item in the list. * * Complexity: O(1) */ void popFront() { Node* f = _front; _front = f.next; static if (useGC) { import core.memory : GC; GC.removeRange(f); } allocator.dispose(f); --_length; } /** * Complexity: O(1) * Returns: true if this list is empty */ bool empty() inout pure nothrow @property @safe @nogc { return _front is null; } /** * Complexity: O(1) * Returns: the number of items in the list */ size_t length() inout pure nothrow @property @safe @nogc { return _length; } /** * Inserts an item at the front of the list. * * Complexity: O(1) * Params: t = the item to insert into the list */ void insertFront(T t) @trusted { _front = make!Node(allocator, _front, t); static if (useGC) { import core.memory : GC; GC.addRange(_front, Node.sizeof); } _length++; } /// ditto alias insert = insertFront; /// ditto alias insertAnywhere = insertFront; /// ditto alias put = insertFront; /** * Supports `list ~= item` syntax * * Complexity: O(1) */ void opOpAssign(string op)(T t) if (op == "~") { put(t); } /** * Removes the first instance of value found in the list. * * Complexity: O(length) * Returns: true if a value was removed. */ bool remove(V)(V value) @trusted { Node* prev = null; Node* cur = _front; while (cur !is null) { if (cur.value == value) { if (prev !is null) prev.next = cur.next; if (_front is cur) _front = cur.next; static if (shouldAddGCRange!T) { import core.memory : GC; GC.removeRange(cur); } allocator.dispose(cur); _length--; return true; } prev = cur; cur = cur.next; } return false; } /** * Forward range interface */ auto opSlice(this This)() inout { return Range!(This)(_front); } /** * Removes all elements from the range * * Complexity: O(length) */ void clear() { Node* prev = null; Node* cur = _front; while (cur !is null) { prev = cur; cur = prev.next; static if (shouldAddGCRange!T) { import core.memory : GC; GC.removeRange(prev); } allocator.dispose(prev); } _front = null; _length = 0; } private: import stdx.allocator : make, dispose; import containers.internal.node : shouldAddGCRange; import containers.internal.element_type : ContainerElementType; import containers.internal.mixins : AllocatorState; enum bool useGC = supportGC && shouldAddGCRange!T; static struct Range(ThisT) { public: ET front() pure nothrow @property @trusted @nogc { return cast(typeof(return)) current.value; } void popFront() pure nothrow @safe @nogc { current = current.next; } bool empty() const pure nothrow @property @safe @nogc { return current is null; } private: alias ET = ContainerElementType!(ThisT, T); const(Node)* current; } static struct Node { Node* next; T value; } mixin AllocatorState!Allocator; Node* _front; size_t _length; } version(emsi_containers_unittest) unittest { import std.string : format; import std.algorithm : canFind; SList!int intList; foreach (i; 0 .. 100) intList.put(i); assert(intList.length == 100, "%d".format(intList.length)); assert(intList.remove(10)); assert(!intList.remove(10)); assert(intList.length == 99); assert(intList[].canFind(9)); assert(!intList[].canFind(10)); SList!string l; l ~= "abcde"; l ~= "fghij"; assert (l.length == 2); } version(emsi_containers_unittest) unittest { static class Foo { string name; } SList!Foo hs; auto f = new Foo; hs.put(f); } dcontainers-0.8.0~alpha.16/src/containers/treemap.d000066400000000000000000000227541353550446300222410ustar00rootroot00000000000000/** * Tree Map * Copyright: © 2015 Economic Modeling Specialists, Intl. * Authors: Brian Schott * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) */ module containers.treemap; private import containers.internal.node : shouldAddGCRange; private import stdx.allocator.mallocator : Mallocator; /** * A key→value mapping where the keys are guaranteed to be sorted. * Params: * K = the key type * V = the value type * Allocator = the allocator to use. Defaults to `Mallocator`. * less = the key comparison function to use * supportGC = true to support storing GC-allocated objects, false otherwise * cacheLineSize = the size of the internal nodes in bytes */ struct TreeMap(K, V, Allocator = Mallocator, alias less = "a < b", bool supportGC = shouldAddGCRange!K || shouldAddGCRange!V, size_t cacheLineSize = 64) { this(this) @disable; private import stdx.allocator.common : stateSize; auto allocator() { return tree.allocator; } static if (stateSize!Allocator != 0) { /// No default construction if an allocator must be provided. this() @disable; /** * Use the given `allocator` for allocations. */ this(Allocator allocator) { tree = TreeType(allocator); } } void clear() { tree.clear(); } /** * Inserts or overwrites the given key-value pair. */ void insert(const K key, V value) @trusted { auto tme = TreeMapElement(cast(ContainerStorageType!K) key, value); auto r = tree.equalRange(tme); if (r.empty) tree.insert(tme, true); else r._containersFront().value = value; } /// Supports $(B treeMap[key] = value;) syntax. void opIndexAssign(V value, const K key) { insert(key, value); } /** * Supports $(B treeMap[key]) syntax. * * Throws: RangeError if the key does not exist. */ auto opIndex(this This)(const K key) inout { alias CET = ContainerElementType!(This, V); auto tme = TreeMapElement(cast(ContainerStorageType!K) key); return cast(CET) tree.equalRange(tme).front.value; } /** * Returns: the value associated with the given key, or the given `defaultValue`. */ auto get(this This)(const K key, lazy V defaultValue) inout @trusted { alias CET = ContainerElementType!(This, V); auto tme = TreeMapElement(key); auto er = tree.equalRange(tme); if (er.empty) return cast(CET) defaultValue; else return cast(CET) er.front.value; } /** * If the given key does not exist in the TreeMap, adds it with * the value `defaultValue`. * * Params: * key = the key to look up * value = the default value */ auto getOrAdd(this This)(const K key, lazy V value) @safe { alias CET = ContainerElementType!(This, V); auto tme = TreeMapElement(key); auto er = tree.equalRange(tme); if (er.empty) { insert(value, key); return value; } else return er.front.value; } /** * Removes the key→value mapping for the given key. * * Params: key = the key to remove * Returns: true if the key existed in the map */ bool remove(const K key) { auto tme = TreeMapElement(cast(ContainerStorageType!K) key); return tree.remove(tme); } /** * Returns: true if the mapping contains the given key */ bool containsKey(const K key) inout pure nothrow @nogc @trusted { auto tme = TreeMapElement(cast(ContainerStorageType!K) key); return tree.contains(tme); } /** * Returns: true if the mapping is empty */ bool empty() const pure nothrow @property @safe @nogc { return tree.empty; } /** * Returns: the number of key→value pairs in the map */ size_t length() inout pure nothrow @property @safe @nogc { return tree.length; } /** * Returns: a GC-allocated array of the keys in the map */ auto keys(this This)() inout pure @property @trusted { import std.array : array; return byKey!(This)().array(); } /** * Returns: a range of the keys in the map */ auto byKey(this This)() inout pure @trusted @nogc { import std.algorithm.iteration : map; alias CETK = ContainerElementType!(This, K); return tree[].map!(a => cast(CETK) a.key); } /** * Returns: a GC-allocated array of the values in the map */ auto values(this This)() inout pure @property @trusted { import std.array : array; return byValue!(This)().array(); } /** * Returns: a range of the values in the map */ auto byValue(this This)() inout pure @trusted @nogc { import std.algorithm.iteration : map; alias CETV = ContainerElementType!(This, V); return tree[].map!(a => cast(CETV) a.value); } /// ditto alias opSlice = byValue; /** * Returns: a range of the kev/value pairs in this map. The element type of * this range is a struct with `key` and `value` fields. */ auto byKeyValue(this This)() inout pure @trusted { import std.algorithm.iteration : map; alias CETV = ContainerElementType!(This, V); struct KeyValue { const K key; CETV value; } return tree[].map!(n => KeyValue(n.key, cast(CETV) n.value)); } private: import containers.ttree : TTree; import containers.internal.storage_type : ContainerStorageType; import containers.internal.element_type : ContainerElementType; enum bool useGC = supportGC && (shouldAddGCRange!K || shouldAddGCRange!V); static struct TreeMapElement { ContainerStorageType!K key; ContainerStorageType!V value; int opCmp(ref const TreeMapElement other) const { import std.functional : binaryFun; return binaryFun!less(key, other.key); } } alias TreeType = TTree!(TreeMapElement, Allocator, false, "a.opCmp(b) > 0", useGC, cacheLineSize); TreeType tree; } version(emsi_containers_unittest) @system unittest { TreeMap!(string, string) tm; tm["test1"] = "hello"; tm["test2"] = "world"; assert(tm.get("test1", "something") == "hello"); tm.remove("test1"); tm.remove("test2"); assert(tm.length == 0); assert(tm.empty); assert(tm.get("test4", "something") == "something"); assert(tm.get("test4", "something") == "something"); } version(emsi_containers_unittest) unittest { import std.range.primitives : walkLength; import std.stdio : stdout; import stdx.allocator.building_blocks.allocator_list : AllocatorList; import stdx.allocator.building_blocks.free_list : FreeList; import stdx.allocator.building_blocks.region : Region; import stdx.allocator.building_blocks.stats_collector : StatsCollector; StatsCollector!(FreeList!(AllocatorList!(a => Region!(Mallocator)(1024 * 1024)), 64)) allocator; { auto intMap = TreeMap!(int, int, typeof(&allocator))(&allocator); foreach (i; 0 .. 10_000) intMap[i] = 10_000 - i; assert(intMap.length == 10_000); } assert(allocator.numAllocate == allocator.numDeallocate); assert(allocator.bytesUsed == 0); } version(emsi_containers_unittest) unittest { import std.algorithm.comparison : equal; import std.algorithm.iteration : each; import std.range : repeat, take; TreeMap!(int, int) tm; int[] a = [1, 2, 3, 4, 5]; a.each!(a => tm[a] = 0); assert(equal(tm.keys, a)); assert(equal(tm.values, repeat(0).take(a.length))); } version(emsi_containers_unittest) unittest { static class Foo { string name; } TreeMap!(string, Foo) tm; auto f = new Foo; tm["foo"] = f; } version(emsi_containers_unittest) unittest { import std.uuid : randomUUID; import std.range.primitives : walkLength; auto hm = TreeMap!(string, int)(); assert (hm.length == 0); assert (!hm.remove("abc")); hm["answer"] = 42; assert (hm.length == 1); assert (hm.containsKey("answer")); hm.remove("answer"); assert (hm.length == 0); hm["one"] = 1; hm["one"] = 1; assert (hm.length == 1); assert (hm["one"] == 1); hm["one"] = 2; assert(hm["one"] == 2); foreach (i; 0 .. 1000) { hm[randomUUID().toString] = i; } assert (hm.length == 1001); assert (hm.keys().length == hm.length); assert (hm.values().length == hm.length); () @nogc { assert (hm.byKey().walkLength == hm.length); assert (hm.byValue().walkLength == hm.length); assert (hm[].walkLength == hm.length); assert (hm.byKeyValue().walkLength == hm.length); }(); foreach (v; hm) {} auto hm2 = TreeMap!(char, char)(); hm2['a'] = 'a'; TreeMap!(int, int) hm3; assert (hm3.get(100, 20) == 20); hm3[100] = 1; assert (hm3.get(100, 20) == 1); auto pValue = hm3.containsKey(100); assert(pValue == 1); } version(emsi_containers_unittest) unittest { static class Foo { string name; } void someFunc(ref in TreeMap!(string,Foo) map) @safe { foreach (kv; map.byKeyValue()) { assert (kv.key == "foo"); assert (kv.value.name == "Foo"); } } auto hm = TreeMap!(string, Foo)(); auto f = new Foo; f.name = "Foo"; hm.insert("foo", f); assert(hm.containsKey("foo")); } // Issue #54 version(emsi_containers_unittest) unittest { TreeMap!(string, int) map; map.insert("foo", 0); map.insert("bar", 0); foreach (key; map.keys()) map[key] = 1; foreach (key; map.byKey()) map[key] = 1; foreach (value; map.byValue()) assert(value == 1); foreach (value; map.values()) assert(value == 1); } version(emsi_containers_unittest) unittest { TreeMap!(int, int) map; auto p = map.getOrAdd(1, 1); assert(p == 1); } version(emsi_containers_unittest) unittest { import std.uuid : randomUUID; import std.range.primitives : walkLength; import std.stdio; auto hm = TreeMap!(string, int)(); foreach (i; 0 .. 1_000_000) { auto str = randomUUID().toString; //writeln("Inserting ", str); hm[str] = i; //if (i > 0 && i % 100 == 0) //writeln(i); } //writeln(hm.buckets.length); import std.algorithm.sorting:sort; //ulong[ulong] counts; //foreach (i, ref bucket; hm.buckets[]) //counts[bucket.length]++; //foreach (k; counts.keys.sort()) //writeln(k, "=>", counts[k]); } dcontainers-0.8.0~alpha.16/src/containers/ttree.d000066400000000000000000001014401353550446300217150ustar00rootroot00000000000000/** * T-Tree. * Copyright: © 2015 Economic Modeling Specialists, Intl. * Authors: Brian Schott * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) */ module containers.ttree; private import containers.internal.node : shouldAddGCRange; private import containers.internal.mixins : AllocatorState; private import stdx.allocator.mallocator : Mallocator; /** * Implements a binary search tree with multiple items per tree node. * * T-tree Nodes are (by default) sized to fit within a 64-byte * cache line. The number of items stored per node can be read from the * `nodeCapacity` field. Each node has 0, 1, or 2 children. Each node has between * 1 and `nodeCapacity` items, or it has `nodeCapacity` items and 0 or * more children. * * Params: * T = the element type * Allocator = the allocator to use. Defaults to `Mallocator`. * allowDuplicates = if true, duplicate values will be allowed in the tree * less = the comparitor function to use * supportGC = true if the container should support holding references to * GC-allocated memory. * cacheLineSize = Nodes will be sized to fit within this number of bytes. * See_also: $(LINK http://en.wikipedia.org/wiki/T-tree) */ struct TTree(T, Allocator = Mallocator, bool allowDuplicates = false, alias less = "a < b", bool supportGC = shouldAddGCRange!T, size_t cacheLineSize = 64) { /** * T-Trees are not copyable due to the way they manage memory and interact * with allocators. */ this(this) @disable; static if (stateSize!Allocator != 0) { /// No default construction if an allocator must be provided. this() @disable; /** * Use `allocator` to allocate and free nodes in the tree. */ this(Allocator allocator) in { assert(allocator !is null, "Allocator must not be null"); } body { this.allocator = allocator; } private alias AllocatorType = Allocator; } else private alias AllocatorType = void*; ~this() @trusted { scope(failure) assert(false); clear(); } /** * Removes all elements from the tree. */ void clear() { _length = 0; if (root is null) return; static if (stateSize!Allocator > 0) deallocateNode(root, allocator); else deallocateNode(root, null); } debug(EMSI_CONTAINERS) invariant() { assert (root is null || _length != 0); } /** * $(B tree ~= item) operator overload. */ void opOpAssign(string op)(T value) if (op == "~") { insert(value); } /** * Inserts the given value(s) into the tree. * * This is not a stable insert. You will get strange results if you insert * into a tree while iterating over it. * * Params: * value = the value to insert * overwrite = if `true` the given `value` will replace the first item * in the tree that is equivalent (That is greater-than and less-than * are both false) to `value`. This is useful in cases where opCmp * and opEquals for `T` type have different meanings. For example, * if the element type is a circle that has a position and a color, * the circle could implement `opCmp` to sort by color, and calling * `insert` with `overwrite` set to `true` would allow you to update * the position of the circle with a certain color in the tree. * Returns: the number of values added. */ size_t insert(T value, bool overwrite = false) @safe { if (root is null) { static if (stateSize!Allocator > 0) root = allocateNode(cast(Value) value, null, allocator); else root = allocateNode(cast(Value) value, null, null); ++_length; return true; } static if (stateSize!Allocator > 0) immutable bool r = root.insert(cast(Value) value, root, allocator, overwrite); else immutable bool r = root.insert(cast(Value) value, root, null, overwrite); if (r) ++_length; return r ? 1 : 0; } /// ditto size_t insert(R)(R r, bool overwrite = false) if (isInputRange!R && is(ElementType!R == T)) { size_t retVal; while (!r.empty) { retVal += insert(r.front(), overwrite); r.popFront(); } return retVal; } /// ditto size_t insert(T[] values, bool overwrite = false) { size_t retVal; foreach (ref v; values) retVal += insert(v, overwrite); return retVal; } /// ditto alias insertAnywhere = insert; /// ditto alias put = insert; /** * Removes a single value from the tree, or does nothing. * * If `allowDuplicates` is true only a single element that is equivalent to * the given `value` will be removed. Which of these elements is removed is * not defined. * * Params: * value = a value equal to the one to be removed * cleanup = a function that should be run on the removed item * Retuns: true if any value was removed */ bool remove(T value, void delegate(T) cleanup = null) { static if (stateSize!Allocator > 0) immutable bool removed = root !is null && root.remove(cast(Value) value, root, allocator, cleanup); else immutable bool removed = root !is null && root.remove(cast(Value) value, root, null, cleanup); if (removed) { --_length; if (_length == 0) { static if (stateSize!Allocator > 0) deallocateNode(root, allocator); else deallocateNode(root, null); } } return removed; } /** * Returns: true if the tree _conains the given value */ bool contains(T value) const @nogc @safe { return root !is null && root.contains(value); } /** * Returns: the number of elements in the tree. */ size_t length() const pure nothrow @nogc @safe @property { return _length; } /** * Returns: true if the tree is empty. */ bool empty() const pure nothrow @nogc @safe @property { return _length == 0; } /** * Returns: a range over the tree. Do not insert into the tree while * iterating because you may iterate over the same value multiple times * or skip some values entirely. */ auto opSlice(this This)() inout @trusted @nogc { return Range!(This)(cast(const(Node)*) root, RangeType.all, T.init); } /** * Returns: a range of elements which are less than value. */ auto lowerBound(this This)(inout T value) inout @trusted { return Range!(This)(cast(const(Node)*) root, RangeType.lower, value); } /** * Returns: a range of elements which are equivalent (though not necessarily * equal) to value. */ auto equalRange(this This)(inout T value) inout @trusted { return Range!(This)(cast(const(Node)*) root, RangeType.equal, value); } /** * Returns: a range of elements which are greater than value. */ auto upperBound(this This)(inout T value) inout @trusted { return Range!(This)(cast(const(Node)*) root, RangeType.upper, value); } /** * Returns: the first element in the tree. */ inout(T) front(this This)() inout pure @trusted @property { import std.exception : enforce; alias CET = ContainerElementType!(This, T); enforce(!empty(), "Attepted to get the front of an empty tree."); inout(Node)* current = root; while (current.left !is null) current = current.left; return cast(CET) current.values[0]; } /** * Returns: the last element in the tree. */ inout(T) back(this This)() inout pure @trusted @property { import std.exception : enforce; alias CET = ContainerElementType!(This, T); enforce(!empty(), "Attepted to get the back of an empty tree."); inout(Node)* current = root; while (current.right !is null) current = current.right; return cast(CET) current.values[current.nextAvailableIndex - 1]; } /** * Tree range */ static struct Range(ThisT) { @disable this(); /** * Standard range operations */ ET front() const @property @nogc { return cast(typeof(return)) current.values[index]; } /// ditto bool empty() const pure nothrow @nogc @safe @property { return current is null; } /// ditto void popFront() { _popFront(); if (current is null) return; with (RangeType) final switch (type) { case upper: case all: break; case equal: if (_less(val, front())) current = null; break; case lower: if (!_less(front(), val)) current = null; break; } } package(containers): // The TreeMap container needs to be able to modify part of the tree // in-place. The reason that this works is that the value part of the // key-value struct contained in a TTree used by a TreeMap is not used // when comparing nodes. Normal users of the containers library cannot // get a reference to the elements because modifying them will violate // the ordering invariant of the tree. T* _containersFront() const @property @nogc @trusted { return cast(T*) ¤t.values[index]; } private: alias ET = ContainerElementType!(ThisT, T); void currentToLeftmost() @nogc { if (current is null) return; while (current.left !is null) current = current.left; } void currentToLeastContaining(inout T val) { if (current is null) return; while (current !is null) { assert(current.registry != 0); auto first = current.values[0]; auto last = current.values[current.nextAvailableIndex - 1]; immutable bool valLessFirst = _less(val, first); immutable bool valLessLast = _less(val, last); immutable bool firstLessVal = _less(first, val); immutable bool lastLessVal = _less(last, val); if (firstLessVal && valLessLast) return; else if (valLessFirst) current = current.left; else if (lastLessVal) current = current.right; else { static if (allowDuplicates) { if (!valLessFirst && !firstLessVal) { auto c = current; current = current.left; currentToLeastContaining(val); if (current is null) current = c; return; } else return; } else return; } } } this(inout(Node)* n, RangeType type, inout T val) @nogc { current = n; this.type = type; this.val = val; with (RangeType) final switch(type) { case all: currentToLeftmost(); break; case lower: currentToLeftmost(); if (_less(val, front())) current = null; break; case equal: currentToLeastContaining(val); while (current !is null && _less(front(), val)) _popFront(); if (current is null || _less(front(), val) || _less(val, front())) current = null; break; case upper: currentToLeastContaining(val); while (current !is null && !_less(val, front())) _popFront(); break; } } void _popFront() @nogc in { assert (!empty); } body { index++; if (index >= nodeCapacity || current.isFree(index)) { index = 0; if (current.right !is null) { current = current.right; while (current.left !is null) current = current.left; } else if (current.parent is null) current = null; else if (current.parent.left is current) current = current.parent; else { while (current.parent.right is current) { current = current.parent; if (current.parent is null) { current = null; return; } } current = current.parent; } } } size_t index; const(Node)* current; const RangeType type; const T val; } mixin AllocatorState!Allocator; private: import containers.internal.element_type : ContainerElementType; import containers.internal.node : FatNodeInfo, fullBits, shouldAddGCRange, shouldNullSlot; import containers.internal.storage_type : ContainerStorageType; import std.algorithm : sort; import std.functional: binaryFun; import std.range : ElementType, isInputRange; import std.traits: isPointer, PointerTarget; import stdx.allocator.common : stateSize; alias N = FatNodeInfo!(T.sizeof, 3, cacheLineSize, ulong.sizeof); alias Value = ContainerStorageType!T; enum size_t nodeCapacity = N[0]; alias BookkeepingType = N[1]; enum HEIGHT_BIT_OFFSET = 48UL; enum fullBitPattern = fullBits!(ulong, nodeCapacity); enum RangeType : ubyte { all, lower, equal, upper } enum bool useGC = supportGC && shouldAddGCRange!T; static assert (nodeCapacity <= HEIGHT_BIT_OFFSET, "cannot fit height info and registry in ulong"); static assert (nodeCapacity <= (typeof(Node.registry).sizeof * 8)); static assert (Node.sizeof <= cacheLineSize); // If we're storing a struct that defines opCmp, don't compare pointers as // that is almost certainly not what the user intended. static if (is(typeof(less) == string )) { // Everything inside of this `static if` is dumb. `binaryFun` does not // correctly infer nothrow and @nogc attributes, among other things, so // we need to declare a function here that has its attributes properly // inferred. It's not currently possible, however, to use this function // with std.algorithm.sort because of symbol visibility issues. Because // of this problem, keep a duplicate of the sorting predicate in string // form in the `_lessStr` alias. static if (less == "a < b" && isPointer!T && __traits(hasMember, PointerTarget!T, "opCmp")) { enum _lessStr = "a.opCmp(*b) < 0"; static bool _less(TT)(const TT a, const TT b) { return a.opCmp(*b) < 0; } } else { enum _lessStr = less; alias _less = binaryFun!less; } } else alias _less = binaryFun!less; static Node* allocateNode(Value value, Node* parent, AllocatorType allocator) @trusted out (result) { assert (result.left is null); assert (result.right is null); } body { import core.memory : GC; import stdx.allocator : make; static if (stateSize!Allocator == 0) Node* n = make!Node(Allocator.instance); else Node* n = make!Node(allocator); n.parent = parent; n.markUsed(0); n.values[0] = cast(Value) value; static if (useGC) GC.addRange(n, Node.sizeof); return n; } static void deallocateNode(ref Node* n, AllocatorType allocator) in { assert (n !is null); } body { import stdx.allocator : dispose; import core.memory : GC; if (n.left !is null) deallocateNode(n.left, allocator); if (n.right !is null) deallocateNode(n.right, allocator); static if (useGC) GC.removeRange(n); static if (stateSize!Allocator == 0) dispose(Allocator.instance, n); else dispose(allocator, n); n = null; } static struct Node { private size_t nextAvailableIndex() const pure nothrow @nogc @safe { import containers.internal.backwards : bsf; return bsf(~(registry & fullBitPattern)); } private void markUsed(size_t index) pure nothrow @nogc @safe { registry |= (1UL << index); } private void markUnused(size_t index) pure nothrow @nogc @safe { registry &= ~(1UL << index); static if (shouldNullSlot!T) values[index] = null; } private bool isFree(size_t index) const pure nothrow @nogc @safe { return (registry & (1UL << index)) == 0; } private bool isFull() const pure nothrow @nogc @safe { return (registry & fullBitPattern) == fullBitPattern; } private bool isEmpty() const pure nothrow @nogc @safe { return (registry & fullBitPattern) == 0; } bool contains(Value value) const @trusted { import std.range : assumeSorted; size_t i = nextAvailableIndex(); if (_less(value, cast(Value) values[0])) return left !is null && left.contains(value); if (_less(values[i - 1], value)) return right !is null && right.contains(value); static if (is(typeof(_lessStr))) return !assumeSorted!_lessStr(values[0 .. i]).equalRange(value).empty; else return !assumeSorted!_less(values[0 .. i]).equalRange(value).empty; } ulong calcHeight() pure nothrow @nogc @safe { immutable ulong l = left !is null ? left.height() : 0; immutable ulong r = right !is null ? right.height() : 0; immutable ulong h = 1 + (l > r ? l : r); assert (h < ushort.max); registry &= fullBitPattern; registry |= (h << HEIGHT_BIT_OFFSET); return h; } ulong height() const pure nothrow @nogc @safe { return registry >>> HEIGHT_BIT_OFFSET; } int imbalanced() const pure nothrow @nogc @safe { if (right !is null && ((left is null && right.height() > 1) || (left !is null && right.height() > left.height() + 1))) return 1; if (left !is null && ((right is null && left.height() > 1) || (right !is null && left.height() > right.height() + 1))) return -1; return 0; } bool insert(T value, ref Node* root, AllocatorType allocator, bool overwrite) @trusted in { static if (isPointer!T || is (T == class) || is (T == interface)) assert (value !is null); } body { import std.algorithm : sort; import std.range : assumeSorted; if (!isFull()) { immutable size_t index = nextAvailableIndex(); static if (!allowDuplicates) { static if (is(typeof(_lessStr))) auto r = assumeSorted!_lessStr(values[0 .. index]).trisect( cast(Value) value); else auto r = assumeSorted!_less(values[0 .. index]).trisect( cast(Value) value); if (!r[1].empty) { if (overwrite) { values[r[0].length] = cast(Value) value; return true; } return false; } } values[index] = cast(Value) value; markUsed(index); static if (is(typeof(_lessStr))) sort!_lessStr(values[0 .. index + 1]); else sort!_less(values[0 .. index + 1]); return true; } if (_less(value, values[0])) { if (left is null) { left = allocateNode(cast(Value) value, &this, allocator); calcHeight(); return true; } immutable bool b = left.insert(value, root, allocator, overwrite); if (imbalanced() == -1) rotateRight(root, allocator); calcHeight(); return b; } if (_less(values[$ - 1], cast(Value) value)) { if (right is null) { right = allocateNode(value, &this, allocator); calcHeight(); return true; } immutable bool b = right.insert(value, root, allocator, overwrite); if (imbalanced() == 1) rotateLeft(root, allocator); calcHeight(); return b; } static if (!allowDuplicates) { static if (is(typeof(_lessStr))) { if (!assumeSorted!_lessStr(values[]).equalRange(cast(Value) value).empty) return false; } else { if (!assumeSorted!_less(values[]).equalRange(cast(Value) value).empty) return false; } } Value[nodeCapacity + 1] temp = void; temp[0 .. $ - 1] = values[]; temp[$ - 1] = cast(Value) value; static if (is(typeof(_lessStr))) sort!_lessStr(temp[]); else sort!_less(temp[]); if (right is null) { values[] = temp[0 .. $ - 1]; right = allocateNode(temp[$ - 1], &this, allocator); return true; } if (left is null) { values[] = temp[1 .. $]; left = allocateNode(temp[0], &this, allocator); return true; } if (right.height < left.height) { values[] = temp[0 .. $ - 1]; immutable bool b = right.insert(temp[$ - 1], root, allocator, overwrite); if (imbalanced() == 1) rotateLeft(root, allocator); calcHeight(); return b; } values[] = temp[1 .. $]; immutable bool b = left.insert(temp[0], root, allocator, overwrite); if (imbalanced() == -1) rotateRight(root, allocator); calcHeight(); return b; } bool remove(Value value, ref Node* n, AllocatorType allocator, void delegate(T) cleanup = null) { import std.range : assumeSorted; assert (!isEmpty()); if (isFull() && _less(value, values[0])) { immutable bool r = left !is null && left.remove(value, left, allocator, cleanup); if (left.isEmpty()) deallocateNode(left, allocator); return r; } if (isFull() && _less(values[$ - 1], value)) { immutable bool r = right !is null && right.remove(value, right, allocator, cleanup); if (right.isEmpty()) deallocateNode(right, allocator); return r; } size_t i = nextAvailableIndex(); static if (is(typeof(_lessStr))) auto sv = assumeSorted!_lessStr(values[0 .. i]); else auto sv = assumeSorted!_less(values[0 .. i]); auto tri = sv.trisect(value); if (tri[1].length == 0) return false; // Clean up removed item if (cleanup !is null) cleanup(tri[1][0]); immutable size_t l = tri[0].length; if (right is null && left is null) { Value[nodeCapacity - 1] temp; temp[0 .. l] = values[0 .. l]; temp[l .. $] = values[l + 1 .. $]; values[0 .. $ - 1] = temp[]; markUnused(nextAvailableIndex() - 1); } else if (right !is null) { Value[nodeCapacity - 1] temp; temp[0 .. l] = values[0 .. l]; temp[l .. $] = values[l + 1 .. $]; values[0 .. $ - 1] = temp[]; values[$ - 1] = right.removeSmallest(allocator); if (right.isEmpty()) deallocateNode(right, allocator); } else if (left !is null) { Value[nodeCapacity - 1] temp; temp[0 .. l] = values[0 .. l]; temp[l .. $] = values[l + 1 .. $]; values[1 .. $] = temp[]; values[0] = left.removeLargest(allocator); if (left.isEmpty()) deallocateNode(left, allocator); } return true; } Value removeSmallest(AllocatorType allocator) in { assert (!isEmpty()); } body { if (left is null && right is null) { Value r = values[0]; Value[nodeCapacity - 1] temp = void; temp[] = values[1 .. $]; values[0 .. $ - 1] = temp[]; markUnused(nextAvailableIndex() - 1); return r; } if (left !is null) { auto r = left.removeSmallest(allocator); if (left.isEmpty()) deallocateNode(left, allocator); return r; } Value r = values[0]; Value[nodeCapacity - 1] temp = void; temp[] = values[1 .. $]; values[0 .. $ - 1] = temp[]; values[$ - 1] = right.removeSmallest(allocator); if (right.isEmpty()) deallocateNode(right, allocator); return r; } Value removeLargest(AllocatorType allocator) in { assert (!isEmpty()); } out (result) { static if (isPointer!T || is (T == class) || is(T == interface)) assert (result !is null); } body { if (left is null && right is null) { immutable size_t i = nextAvailableIndex() - 1; Value r = values[i]; markUnused(i); return r; } if (right !is null) { auto r = right.removeLargest(allocator); if (right.isEmpty()) deallocateNode(right, allocator); return r; } Value r = values[$ - 1]; Value[nodeCapacity - 1] temp = void; temp[] = values[0 .. $ - 1]; values[1 .. $] = temp[]; values[0] = left.removeLargest(allocator); if (left.isEmpty()) deallocateNode(left, allocator); return r; } void rotateLeft(ref Node* root, AllocatorType allocator) @safe { Node* newRoot; if (right.left !is null && right.right is null) { newRoot = right.left; newRoot.parent = this.parent; newRoot.left = &this; newRoot.left.parent = newRoot; newRoot.right = right; newRoot.right.parent = newRoot; newRoot.right.left = null; right = null; left = null; } else { newRoot = right; newRoot.parent = this.parent; right = newRoot.left; if (right !is null) right.parent = &this; newRoot.left = &this; this.parent = newRoot; } cleanup(newRoot, root, allocator); } void rotateRight(ref Node* root, AllocatorType allocator) @safe { Node* newRoot; if (left.right !is null && left.left is null) { newRoot = left.right; newRoot.parent = this.parent; newRoot.right = &this; newRoot.right.parent = newRoot; newRoot.left = left; newRoot.left.parent = newRoot; newRoot.left.right = null; left = null; right = null; } else { newRoot = left; newRoot.parent = this.parent; left = newRoot.right; if (left !is null) left.parent = &this; newRoot.right = &this; this.parent = newRoot; } cleanup(newRoot, root, allocator); } void cleanup(Node* newRoot, ref Node* root, AllocatorType allocator) @safe { if (newRoot.parent !is null) { if (newRoot.parent.right is &this) newRoot.parent.right = newRoot; else newRoot.parent.left = newRoot; } else root = newRoot; newRoot.fillFromChildren(root, allocator); if (newRoot.left !is null) { newRoot.left.fillFromChildren(root, allocator); } if (newRoot.right !is null) { newRoot.right.fillFromChildren(root, allocator); } if (newRoot.left !is null) newRoot.left.calcHeight(); if (newRoot.right !is null) newRoot.right.calcHeight(); newRoot.calcHeight(); } void fillFromChildren(ref Node* root, AllocatorType allocator) @trusted { while (!isFull()) { if (left !is null) { insert(left.removeLargest(allocator), root, allocator, false); if (left.isEmpty()) deallocateNode(left, allocator); } else if (right !is null) { insert(right.removeSmallest(allocator), root, allocator, false); if (right.isEmpty()) deallocateNode(right, allocator); } else return; } } debug(EMSI_CONTAINERS) invariant() { import std.string : format; assert (&this !is null); assert (left !is &this, "%x, %x".format(left, &this)); assert (right !is &this, "%x, %x".format(right, &this)); if (left !is null) { assert (left.left !is &this, "%s".format(values)); assert (left.right !is &this, "%x, %x".format(left.right, &this)); assert (left.parent is &this, "%x, %x, %x".format(left, left.parent, &this)); } if (right !is null) { assert (right.left !is &this, "%s".format(values)); assert (right.right !is &this, "%s".format(values)); assert (right.parent is &this); } } Value[nodeCapacity] values; Node* left; Node* right; Node* parent; ulong registry = 1UL << HEIGHT_BIT_OFFSET; } size_t _length; Node* root; } version(emsi_containers_unittest) unittest { import core.memory : GC; import std.algorithm : equal, sort, map, filter, each; import std.array : array; import std.range : iota, walkLength, isInputRange; import std.string : format; import std.uuid : randomUUID; { TTree!int kt; assert (kt.empty); foreach (i; 0 .. 200) assert (kt.insert(i)); assert(kt.front == 0); assert(kt.back == 199); assert(!kt.empty); assert(kt.length == 200); assert(kt.contains(30)); } { TTree!int kt; assert (!kt.contains(5)); kt.insert(2_000); assert (kt.contains(2_000)); foreach_reverse (i; 0 .. 1_000) { assert (kt.insert(i)); } assert (!kt.contains(100_000)); } { import std.random : uniform; TTree!int kt; foreach (i; 0 .. 1_000) kt.insert(uniform(0, 100_000)); } { TTree!int kt; kt.insert(10); assert (kt.length == 1); assert (!kt.insert(10)); assert (kt.length == 1); } { TTree!(int, Mallocator, true) kt; assert (kt.insert(1)); assert (kt.length == 1); assert (kt.insert(1)); assert (kt.length == 2); assert (kt.contains(1)); } { TTree!(int) kt; foreach (i; 0 .. 200) assert (kt.insert(i)); assert (kt.length == 200); assert (kt.remove(79)); assert (!kt.remove(79)); assert (kt.length == 199); } { string[] strs = [ "2c381d2a-bacd-40db-b6d8-055b144c5ee6", "62104b50-e235-4c95-bcb9-a545e88e2d09", "828c8fc0-a392-4738-a49c-62e991fce090", "62e30465-79eb-446e-b34f-af5d7c491486", "93ec245b-60d2-4422-91ff-66a6d7e299fc", "c1d2f3d7-82cc-4d90-a2c5-9fba335f36cd", "c9d8d980-94eb-4941-b873-00d68021522f", "82dbc4df-cb3c-447a-9d73-cd6291a0ba02", "8d259231-6ab6-49e4-9bb6-fe097c4153ed", "f9f2d719-61e1-4f62-ae2c-bf2a24a13d5b" ]; TTree!string strings; foreach (i, s; strs) assert (strings.insert(s)); sort(strs[]); assert (equal(strs, strings[])); } foreach (x; 0 .. 1000) { TTree!string strings; string[] strs = iota(10).map!(a => randomUUID().toString()).array(); foreach (i, s; strs) assert (strings.insert(s)); assert (strings.length == strs.length); sort(strs); assert (equal(strs, strings[])); } { TTree!string strings; strings.insert(["e", "f", "a", "b", "c", "d"]); assert (equal(strings[], ["a", "b", "c", "d", "e", "f"])); } { TTree!(string, Mallocator, true) strings; assert (strings.insert("b")); assert (strings.insert("c")); assert (strings.insert("a")); assert (strings.insert("d")); assert (strings.insert("d")); assert (strings.length == 5); assert (equal(strings.equalRange("d"), ["d", "d"]), format("%s", strings.equalRange("d"))); assert (equal(strings.equalRange("a"), ["a"]), format("%s", strings.equalRange("a"))); assert (equal(strings.lowerBound("d"), ["a", "b", "c"]), format("%s", strings.lowerBound("d"))); assert (equal(strings.upperBound("c"), ["d", "d"]), format("%s", strings.upperBound("c"))); } { static struct S { string x; int opCmp (ref const S other) const @nogc { if (x < other.x) return -1; if (x > other.x) return 1; return 0; } } TTree!(S*, Mallocator, true) stringTree; auto one = S("offset"); stringTree.insert(&one); auto two = S("object"); assert (stringTree.equalRange(&two).empty); assert (!stringTree.equalRange(&one).empty); assert (stringTree[].front.x == "offset"); } { static struct TestStruct { int opCmp(ref const TestStruct other) const @nogc { return x < other.x ? -1 : (x > other.x ? 1 : 0); } int x; int y; } TTree!(TestStruct*) tsTree; static assert (isInputRange!(typeof(tsTree[]))); foreach (i; 0 .. 100) assert(tsTree.insert(new TestStruct(i, i * 2))); assert (tsTree.length == 100); auto r = tsTree[]; TestStruct* prev = r.front(); r.popFront(); while (!r.empty) { assert (r.front.x > prev.x, format("%s %s", prev.x, r.front.x)); prev = r.front; r.popFront(); } TestStruct a = TestStruct(30, 100); auto eqArray = array(tsTree.equalRange(&a)); assert (eqArray.length == 1, format("%d", eqArray.length)); } { import std.algorithm : canFind; TTree!int ints; foreach (i; 0 .. 50) ints ~= i; assert (canFind(ints[], 20)); assert (walkLength(ints[]) == 50); assert (walkLength(filter!(a => (a & 1) == 0)(ints[])) == 25); } { TTree!int ints; foreach (i; 0 .. 50) ints ~= i; ints.remove(0); assert (ints.length == 49); foreach (i; 1 .. 12) ints.remove(i); assert (ints.length == 49 - 11); } { const(TTree!(const(int))) getInts() { TTree!(const(int)) t; t.insert(1); t.insert(2); t.insert(3); return t; } auto t = getInts(); static assert (is (typeof(t[].front) == const(int))); assert (equal(t[].filter!(a => a & 1), [1, 3])); } { static struct ABC { ulong a; ulong b; int opCmp(ref const ABC other) const @nogc { if (this.a < other.a) return -1; if (this.a > other.a) return 1; return 0; } } TTree!(ABC, Mallocator, true) tree; foreach (i; 0 .. 10) tree.insert(ABC(i)); tree.insert(ABC(15)); tree.insert(ABC(15)); tree.insert(ABC(15)); tree.insert(ABC(15)); foreach (i; 20 .. 30) tree.insert(ABC(i)); assert(tree.equalRange(ABC(15)).walkLength() == 4, format("Actual length = %d", tree.equalRange(ABC(15)).walkLength())); } { TTree!int ints2; iota(0, 1_000_000).each!(a => ints2.insert(a)); assert(equal(iota(0, 1_000_000), ints2[])); assert(ints2.length == 1_000_000); foreach (i; 0 .. 1_000_000) assert(!ints2.equalRange(i).empty, format("Could not find %d", i)); } { TTree!int ints3; foreach (i; iota(0, 1_000_000).filter!(a => a % 2 == 0)) ints3.insert(i); assert(ints3.length == 500_000); foreach (i; iota(0, 1_000_000).filter!(a => a % 2 == 0)) assert(!ints3.equalRange(i).empty); foreach (i; iota(0, 1_000_000).filter!(a => a % 2 == 1)) assert(ints3.equalRange(i).empty); } { TTree!(ubyte, Mallocator, true) ubytes; foreach (i; iota(0, 1_000_000).filter!(a => a % 2 == 0).map!(a => cast(ubyte)(a % ubyte.max))) ubytes.insert(i); assert(ubytes[].walkLength == 500_000, "%d".format(ubytes[].walkLength)); assert(ubytes.length == 500_000, "%d".format(ubytes.length)); foreach (i; iota(0, 1_000_000).filter!(a => a % 2 == 0).map!(a => cast(ubyte)(a % ubyte.max))) assert(!ubytes.equalRange(i).empty); } { import stdx.allocator.building_blocks.free_list : FreeList; import stdx.allocator.building_blocks.allocator_list : AllocatorList; import stdx.allocator.building_blocks.region : Region; import stdx.allocator.building_blocks.stats_collector : StatsCollector; import std.stdio : stdout; StatsCollector!(FreeList!(AllocatorList!(a => Region!(Mallocator)(1024 * 1024)), 64)) allocator; { auto ints4 = TTree!(int, typeof(&allocator))(&allocator); foreach (i; 0 .. 10_000) ints4.insert(i); assert(walkLength(ints4[]) == 10_000); } assert(allocator.numAllocate == allocator.numDeallocate); assert(allocator.bytesUsed == 0); } } version(emsi_containers_unittest) unittest { static class Foo { string name; this(string s) { this.name = s; } } TTree!(Foo, Mallocator, false, "a.name < b.name") tt; auto f = new Foo("foo"); tt.insert(f); f = new Foo("bar"); tt.insert(f); auto r = tt[]; } version(emsi_containers_unittest) unittest { import std.range : walkLength; import std.stdio; TTree!(int, Mallocator, true) tt; tt.insert(10); tt.insert(11); tt.insert(12); assert(tt.length == 3); tt.insert(11); assert(tt.length == 4); tt.remove(11); assert(tt.length == 3); assert(tt[].walkLength == tt.length); } dcontainers-0.8.0~alpha.16/src/containers/unrolledlist.d000066400000000000000000000351661353550446300233250ustar00rootroot00000000000000/** * Unrolled Linked List. * Copyright: © 2015 Economic Modeling Specialists, Intl. * Authors: Brian Schott * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) */ module containers.unrolledlist; private import containers.internal.node : shouldAddGCRange; private import stdx.allocator.mallocator : Mallocator; version (X86_64) version (LDC) version = LDC_64; /** * Unrolled Linked List. * * Nodes are (by default) sized to fit within a 64-byte cache line. The number * of items stored per node can be read from the $(B nodeCapacity) field. * See_also: $(LINK http://en.wikipedia.org/wiki/Unrolled_linked_list) * Params: * T = the element type * Allocator = the allocator to use. Defaults to `Mallocator`. * supportGC = true to ensure that the GC scans the nodes of the unrolled * list, false if you are sure that no references to GC-managed memory * will be stored in this container. * cacheLineSize = Nodes will be sized to fit within this number of bytes. */ struct UnrolledList(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T, size_t cacheLineSize = 64) { this(this) @disable; private import stdx.allocator.common : stateSize; static if (stateSize!Allocator != 0) { /// No default construction if an allocator must be provided. this() @disable; /** * Use the given `allocator` for allocations. */ this(Allocator allocator) in { assert(allocator !is null); } body { this.allocator = allocator; } } ~this() nothrow { scope (failure) assert(false); clear(); } /** * Removes all items from the list */ void clear() { Node* previous; Node* current = _front; while (current !is null) { previous = current; current = current.next; static if (!(is(T == class) || is(T == interface))) foreach (ref item; previous.items) typeid(T).destroy(&item); static if (useGC) { import core.memory: GC; GC.removeRange(previous); } allocator.dispose(previous); } _length = 0; _front = null; _back = null; } /** * Inserts the given item into the end of the list. * * Returns: a pointer to the inserted item. */ T* insertBack(T item) { ContainerStorageType!T* result; if (_back is null) { assert (_front is null); _back = allocateNode(item); _front = _back; result = &_back.items[0]; } else { size_t index = _back.nextAvailableIndex(); if (index >= nodeCapacity) { Node* n = allocateNode(item); n.prev = _back; _back.next = n; _back = n; index = 0; result = &n.items[0]; } else { _back.items[index] = item; _back.markUsed(index); result = &_back.items[index]; } } _length++; assert (_back.registry <= fullBitPattern); return cast(T*) result; } /** * Inserts the given range into the end of the list */ void insertBack(R)(auto ref R range) { foreach (ref r; range) insertBack(r); } /// ditto T* opOpAssign(string op)(T item) if (op == "~") { return insertBack(item); } /// ditto alias put = insertBack; /// ditto alias insert = insertBack; /** * Inserts the given item in the frontmost available cell, which may put the * item anywhere in the list as removal may leave gaps in list nodes. Use * this only if the order of elements is not important. * * Returns: a pointer to the inserted item. */ T* insertAnywhere(T item) @trusted { Node* n = _front; while (n !is null) { size_t i = n.nextAvailableIndex(); if (i >= nodeCapacity) { if (n.next is null) { assert (n is _back); break; } n = n.next; continue; } n.items[i] = item; n.markUsed(i); _length++; assert (n.registry <= fullBitPattern); return cast(T*) &n.items[i]; } n = allocateNode(item); n.items[0] = item; n.markUsed(0); _length++; auto retVal = cast(T*) &n.items[0]; if (_front is null) { assert(_back is null); _front = n; } else { n.prev = _back; _back.next = n; } _back = n; assert (_back.registry <= fullBitPattern); return retVal; } /// Returns: the length of the list size_t length() const nothrow pure @property @safe @nogc { return _length; } /// Returns: true if the list is empty bool empty() const nothrow pure @property @safe @nogc { return _length == 0; } /** * Removes the first instance of the given item from the list. * * Returns: true if something was removed. */ bool remove(T item) { if (_front is null) return false; bool retVal; loop: for (Node* n = _front; n !is null; n = n.next) { foreach (i; 0 .. nodeCapacity) { if (!n.isFree(i) && n.items[i] == item) { n.markUnused(i); --_length; retVal = true; if (n.registry == 0) deallocateNode(n); else if (shouldMerge(n, n.next)) mergeNodes(n, n.next); else if (shouldMerge(n.prev, n)) mergeNodes(n.prev, n); break loop; } } } return retVal; } /// Pops the front item off of the list void popFront() { moveFront(); assert (_front is null || _front.registry != 0); } /// Pops the front item off of the list and returns it T moveFront() in { assert (!empty()); assert (_front.registry != 0); } body { version (LDC_64) { import ldc.intrinsics : llvm_cttz; size_t index = llvm_cttz(_front.registry, true); } else { import containers.internal.backwards : bsf; size_t index = bsf(_front.registry); } T r = _front.items[index]; _front.markUnused(index); _length--; if (_front.registry == 0) { auto f = _front; if (_front.next !is null) _front.next.prev = null; assert (_front.next !is _front); _front = _front.next; if (_front is null) _back = null; else assert (_front.registry <= fullBitPattern); deallocateNode(f); return r; } if (shouldMerge(_front, _front.next)) mergeNodes(_front, _front.next); return r; } debug (EMSI_CONTAINERS) invariant { import std.string: format; assert (_front is null || _front.registry != 0, format("%x, %b", _front, _front.registry)); assert (_front !is null || _back is null); if (_front !is null) { const(Node)* c = _front; while (c.next !is null) c = c.next; assert(c is _back, "_back pointer is wrong"); } } /** * Time complexity is O(1) * Returns: the item at the front of the list */ ref inout(T) front() inout nothrow @property in { assert (!empty); assert (_front.registry != 0); } body { version (LDC_64) { import ldc.intrinsics : llvm_cttz; immutable index = llvm_cttz(_front.registry, true); } else { import containers.internal.backwards : bsf; immutable index = bsf(_front.registry); } return *(cast(typeof(return)*) &_front.items[index]); } /** * Time complexity is O(nodeCapacity), where the nodeCapacity * is the number of items in a single list node. It is a constant * related to the cache line size. * Returns: the item at the back of the list */ ref inout(T) back() inout nothrow @property in { assert (!empty); assert (!_back.empty); } body { size_t i = nodeCapacity - 1; while (_back.isFree(i)) i--; return *(cast(typeof(return)*) &_back.items[i]); } /// Pops the back item off of the list. void popBack() { moveBack(); } /// Removes an item from the back of the list and returns it. T moveBack() in { assert (!empty); assert (!_back.empty); } body { size_t i = nodeCapacity - 1; while (_back.isFree(i)) { if (i == 0) break; else i--; } assert (!_back.isFree(i)); T item = _back.items[i]; _back.markUnused(i); _length--; if (_back.registry == 0) { deallocateNode(_back); return item; } else if (shouldMerge(_back.prev, _back)) mergeNodes(_back.prev, _back); return item; } /// Returns: a range over the list auto opSlice(this This)() const nothrow pure @nogc @trusted { return Range!(This)(_front); } static struct Range(ThisT) { @disable this(); this(inout(Node)* current) { import std.format:format; this.current = current; if (current !is null) { version(LDC_64) { import ldc.intrinsics : llvm_cttz; index = llvm_cttz(current.registry, true); } else { import containers.internal.backwards : bsf; index = bsf(current.registry); } assert (index < nodeCapacity); } else current = null; } ref ET front() const nothrow @property @trusted @nogc { return *(cast(ET*) ¤t.items[index]); //return cast(T) current.items[index]; } void popFront() nothrow pure @safe @nogc { index++; while (true) { if (index >= nodeCapacity) { current = current.next; if (current is null) return; index = 0; } else { if (current.isFree(index)) index++; else return; } } } bool empty() const nothrow pure @property @safe @nogc { return current is null; } Range save() const nothrow pure @property @safe @nogc { return this; } private: alias ET = ContainerElementType!(ThisT, T); const(Node)* current; size_t index; } private: import stdx.allocator: make, dispose; import containers.internal.node : FatNodeInfo, shouldAddGCRange, fullBits, shouldNullSlot; import containers.internal.storage_type : ContainerStorageType; import containers.internal.element_type : ContainerElementType; import containers.internal.mixins : AllocatorState; alias N = FatNodeInfo!(T.sizeof, 2, cacheLineSize); enum size_t nodeCapacity = N[0]; alias BookkeepingType = N[1]; enum fullBitPattern = fullBits!(BookkeepingType, nodeCapacity); enum bool useGC = supportGC && shouldAddGCRange!T; Node* _back; Node* _front; size_t _length; mixin AllocatorState!Allocator; Node* allocateNode(T item) { Node* n = make!Node(allocator); static if (useGC) { import core.memory: GC; GC.addRange(n, Node.sizeof); } n.items[0] = item; n.markUsed(0); return n; } void deallocateNode(Node* n) { if (n.prev !is null) n.prev.next = n.next; if (n.next !is null) n.next.prev = n.prev; if (_front is n) _front = n.next; if (_back is n) _back = n.prev; static if (useGC) { import core.memory: GC; GC.removeRange(n); } allocator.dispose(n); } static bool shouldMerge(const Node* first, const Node* second) { if (first is null || second is null) return false; version (LDC_64) { import ldc.intrinsics : llvm_ctpop; immutable f = llvm_ctpop(first.registry); immutable s = llvm_ctpop(second.registry); } else { import containers.internal.backwards : popcnt; immutable f = popcnt(first.registry); immutable s = popcnt(second.registry); } return f + s <= nodeCapacity; } void mergeNodes(Node* first, Node* second) in { assert (first !is null); assert (second !is null); assert (second is first.next); } body { size_t i; ContainerStorageType!T[nodeCapacity] temp; foreach (j; 0 .. nodeCapacity) if (!first.isFree(j)) temp[i++] = first.items[j]; foreach (j; 0 .. nodeCapacity) if (!second.isFree(j)) temp[i++] = second.items[j]; first.items[0 .. i] = temp[0 .. i]; first.registry = 0; foreach (k; 0 .. i) first.markUsed(k); assert (first.registry <= fullBitPattern); deallocateNode(second); } static struct Node { size_t nextAvailableIndex() const nothrow pure @safe @nogc { static if (BookkeepingType.sizeof < uint.sizeof) immutable uint notReg = ~(cast(uint) registry); else immutable uint notReg = cast(uint) (~registry); version (LDC_64) { import ldc.intrinsics : llvm_cttz; return llvm_cttz(notReg, true); } else { import containers.internal.backwards : bsf; return bsf(notReg); } } void markUsed(size_t index) nothrow pure @safe @nogc { registry |= (BookkeepingType(1) << index); } void markUnused(size_t index) nothrow pure @safe @nogc { registry &= ~(BookkeepingType(1) << index); static if (shouldNullSlot!T) items[index] = null; } bool empty() const nothrow pure @safe @nogc { return registry == 0; } bool isFree(size_t index) const nothrow pure @safe @nogc { return (registry & (BookkeepingType(1) << index)) == 0; } debug(EMSI_CONTAINERS) invariant() { import std.string : format; assert (registry <= fullBitPattern, format("%016b %016b", registry, fullBitPattern)); assert (prev !is &this); assert (next !is &this); } BookkeepingType registry; ContainerStorageType!T[nodeCapacity] items; Node* prev; Node* next; } } version(emsi_containers_unittest) unittest { import std.algorithm : equal; import std.range : iota; import std.string : format; UnrolledList!ubyte l; static assert (l.Node.sizeof <= 64); assert (l.empty); l.insert(0); assert (l.length == 1); assert (!l.empty); foreach (i; 1 .. 100) l.insert(cast(ubyte) i); assert (l.length == 100); assert (equal(l[], iota(100))); foreach (i; 0 .. 100) assert (l.remove(cast(ubyte) i), format("%d", i)); assert (l.length == 0, format("%d", l.length)); assert (l.empty); assert(*l.insert(1) == 1); assert(*l.insert(2) == 2); assert (l.remove(1)); assert (!l.remove(1)); assert (!l.empty); UnrolledList!ubyte l2; l2.insert(1); l2.insert(2); l2.insert(3); assert (l2.front == 1); l2.popFront(); assert (l2.front == 2); assert (equal(l2[], [2, 3])); l2.popFront(); assert (equal(l2[], [3])); l2.popFront(); assert (l2.empty, format("%d", l2.front)); assert (equal(l2[], cast(int[]) [])); UnrolledList!int l3; foreach (i; 0 .. 200) l3.insert(i); foreach (i; 0 .. 200) { auto x = l3.moveFront(); assert (x == i, format("%d %d", i, x)); } assert (l3.empty); foreach (i; 0 .. 200) l3.insert(i); assert (l3.length == 200); foreach (i; 0 .. 200) { assert (l3.length == 200 - i); auto x = l3.moveBack(); assert (x == 200 - i - 1, format("%d %d", 200 - 1 - 1, x)); } assert (l3.empty); } version(emsi_containers_unittest) unittest { struct A { int a; int b; } UnrolledList!(const(A)) objs; objs.insert(A(10, 11)); static assert (is (typeof(objs.front) == const)); static assert (is (typeof(objs[].front) == const)); } version(emsi_containers_unittest) unittest { static class A { int a; int b; this(int a, int b) { this.a = a; this.b = b; } } UnrolledList!(A) objs; objs.insert(new A(10, 11)); } // Issue #52 version(emsi_containers_unittest) unittest { UnrolledList!int list; list.insert(0); list.insert(0); list.insert(0); list.insert(0); list.insert(0); foreach (ref it; list[]) it = 1; foreach (it; list[]) assert(it == 1); } // Issue #53 version(emsi_containers_unittest) unittest { UnrolledList!int ints; ints.insertBack(0); ints.insertBack(0); ints.front = 1; ints.back = 11; assert(ints.front == 1); assert(ints.back == 11); } dcontainers-0.8.0~alpha.16/subprojects/000077500000000000000000000000001353550446300200345ustar00rootroot00000000000000dcontainers-0.8.0~alpha.16/subprojects/stdx-allocator.wrap000066400000000000000000000001741353550446300236710ustar00rootroot00000000000000[wrap-git] directory = stdx-allocator url = https://github.com/dlang-community/stdx-allocator.git revision = v2.77.5 dcontainers-0.8.0~alpha.16/test/000077500000000000000000000000001353550446300164505ustar00rootroot00000000000000dcontainers-0.8.0~alpha.16/test/compile_test.d000066400000000000000000000252101353550446300213040ustar00rootroot00000000000000import containers.cyclicbuffer; import containers.dynamicarray; import containers.hashmap; import containers.hashset; import containers.openhashset; import containers.simdset; import containers.slist; import containers.treemap; import containers.ttree; import containers.unrolledlist; private void testContainerSingle(alias Container)() { testContainerSingleVal!(Container)(); testContainerSingleRef!(Container)(); } private void testContainerSingleVal(alias Container)() { Container!(int) mm; Container!(const int) mc; Container!(immutable int) mi; const Container!(int) cm; const Container!(const int) cc; const Container!(immutable int) ci; immutable Container!(int) im; immutable Container!(const int) ic; immutable Container!(immutable int) ii; checkSliceFunctionality!(int)(mm); checkSliceFunctionality!(const int)(mc); checkSliceFunctionality!(immutable int)(mi); checkSliceFunctionality!(int)(cm); checkSliceFunctionality!(const int)(cc); checkSliceFunctionality!(immutable int)(ci); checkSliceFunctionality!(int)(im); checkSliceFunctionality!(const int)(ic); checkSliceFunctionality!(immutable int)(ii); } private void testContainerSingleRef(alias Container)() { Container!(int*) mm; Container!(const int*) mc; Container!(immutable int*) mi; const Container!(int*) cm; const Container!(const int*) cc; const Container!(immutable int*) ci; immutable Container!(immutable int*) ii; checkSliceFunctionality!(int*)(mm); checkSliceFunctionality!(const int*)(mc); checkSliceFunctionality!(immutable int*)(mi); checkSliceFunctionality!(const(int)*)(cm); checkSliceFunctionality!(const int*)(cc); checkSliceFunctionality!(immutable int*)(ci); checkSliceFunctionality!(immutable int*)(ii); } private void testContainerDouble(alias Container)() { testContainerDoubleVal!(Container)(); testContainerDoubleRef!(Container)(); testContainerDoubleAggregateKey!(Container)(); } private void testContainerDoubleAggregateKey(alias Container)() { static struct KeyType { int a; string[] c; int opCmp(ref const KeyType other) const { if (other.a < a) return -1; return other.a > a; } size_t toHash() const { return 10; } bool opEquals(ref const KeyType other) const { return a == other.a; } } Container!(const KeyType, int) cm; Container!(immutable KeyType, int) im; checkIndexFunctionality!(int, const KeyType)(cm); checkIndexFunctionality!(int, const KeyType)(im); checkSliceFunctionality!(int)(cm); checkSliceFunctionality!(int)(im); } private void testContainerDoubleVal(alias Container)() { { Container!(int, int) mmm; Container!(int, const int) mmc; Container!(int, immutable int) mmi; Container!(const int, int) mcm; Container!(const int, const int) mcc; Container!(const int, immutable int) mci; Container!(immutable int, int) mim; Container!(immutable int, const int) mic; Container!(immutable int, immutable int) mii; checkIndexFunctionality!(int, int)(mmm); checkIndexFunctionality!(const int, int)(mmc); checkIndexFunctionality!(immutable int, int)(mmi); checkIndexFunctionality!(int, const int)(mcm); checkIndexFunctionality!(const int, const int)(mcc); checkIndexFunctionality!(immutable int, const int)(mci); checkIndexFunctionality!(int, immutable int)(mim); checkIndexFunctionality!(const int, immutable int)(mic); checkIndexFunctionality!(immutable int, immutable int)(mii); checkSliceFunctionality!(int)(mmm); checkSliceFunctionality!(const int)(mmc); checkSliceFunctionality!(immutable int)(mmi); checkSliceFunctionality!(int)(mcm); checkSliceFunctionality!(const int)(mcc); checkSliceFunctionality!(immutable int)(mci); checkSliceFunctionality!(int)(mim); checkSliceFunctionality!(const int)(mic); checkSliceFunctionality!(immutable int)(mii); } { const Container!(int, int) cmm; const Container!(int, const int) cmc; const Container!(int, immutable int) cmi; const Container!(const int, int) ccm; const Container!(const int, const int) ccc; const Container!(const int, immutable int) cci; const Container!(immutable int, int) cim; const Container!(immutable int, const int) cic; const Container!(immutable int, immutable int) cii; checkIndexFunctionality!(int, int)(cmm); checkIndexFunctionality!(const int, int)(cmc); checkIndexFunctionality!(immutable int, int)(cmi); checkIndexFunctionality!(int, const int)(ccm); checkIndexFunctionality!(const int, const int)(ccc); checkIndexFunctionality!(immutable int, const int)(cci); checkIndexFunctionality!(int, immutable int)(cim); checkIndexFunctionality!(const int, immutable int)(cic); checkIndexFunctionality!(immutable int, immutable int)(cii); checkSliceFunctionality!(int)(cmm); checkSliceFunctionality!(const int)(cmc); checkSliceFunctionality!(immutable int)(cmi); checkSliceFunctionality!(int)(ccm); checkSliceFunctionality!(const int)(ccc); checkSliceFunctionality!(immutable int)(cci); checkSliceFunctionality!(int)(cim); checkSliceFunctionality!(const int)(cic); checkSliceFunctionality!(immutable int)(cii); } { immutable Container!(int, int) imm; immutable Container!(int, const int) imc; immutable Container!(int, immutable int) imi; immutable Container!(const int, int) icm; immutable Container!(const int, const int) icc; immutable Container!(const int, immutable int) ici; immutable Container!(immutable int, int) iim; immutable Container!(immutable int, const int) iic; immutable Container!(immutable int, immutable int) iii; checkIndexFunctionality!(int, int)(imm); checkIndexFunctionality!(const int, int)(imc); checkIndexFunctionality!(immutable int, int)(imi); checkIndexFunctionality!(int, const int)(icm); checkIndexFunctionality!(const int, const int)(icc); checkIndexFunctionality!(immutable int, const int)(ici); checkIndexFunctionality!(int, immutable int)(iim); checkIndexFunctionality!(const int, immutable int)(iic); checkIndexFunctionality!(immutable int, immutable int)(iii); checkSliceFunctionality!(int)(imm); checkSliceFunctionality!(const int)(imc); checkSliceFunctionality!(immutable int)(imi); checkSliceFunctionality!(int)(icm); checkSliceFunctionality!(const int)(icc); checkSliceFunctionality!(immutable int)(ici); checkSliceFunctionality!(int)(iim); checkSliceFunctionality!(const int)(iic); checkSliceFunctionality!(immutable int)(iii); } } private void testContainerDoubleRef(alias Container)() { { Container!(int, int*) mmm; Container!(int, const int*) mmc; Container!(int, immutable int*) mmi; Container!(const int, int*) mcm; Container!(const int, const int*) mcc; Container!(const int, immutable int*) mci; Container!(immutable int, int*) mim; Container!(immutable int, const int*) mic; Container!(immutable int, immutable int*) mii; checkIndexFunctionality!(int*, int)(mmm); checkIndexFunctionality!(const int*, int)(mmc); checkIndexFunctionality!(immutable int*, int)(mmi); checkIndexFunctionality!(int*, const int)(mcm); checkIndexFunctionality!(const int*, const int)(mcc); checkIndexFunctionality!(immutable int*, const int)(mci); checkIndexFunctionality!(int*, immutable int)(mim); checkIndexFunctionality!(const int*, immutable int)(mic); checkIndexFunctionality!(immutable int*, immutable int)(mii); checkSliceFunctionality!(int*)(mmm); checkSliceFunctionality!(const int*)(mmc); checkSliceFunctionality!(immutable int*)(mmi); checkSliceFunctionality!(int*)(mcm); checkSliceFunctionality!(const int*)(mcc); checkSliceFunctionality!(immutable int*)(mci); checkSliceFunctionality!(int*)(mim); checkSliceFunctionality!(const int*)(mic); checkSliceFunctionality!(immutable int*)(mii); } { const Container!(int, int*) cmm; const Container!(int, const int*) cmc; const Container!(int, immutable int*) cmi; const Container!(const int, int*) ccm; const Container!(const int, const int*) ccc; const Container!(const int, immutable int*) cci; const Container!(immutable int, int*) cim; const Container!(immutable int, const int*) cic; const Container!(immutable int, immutable int*) cii; checkIndexFunctionality!(const(int)*, int)(cmm); checkIndexFunctionality!(const int*, int)(cmc); checkIndexFunctionality!(immutable int*, int)(cmi); checkIndexFunctionality!(const(int)*, const int)(ccm); checkIndexFunctionality!(const int*, const int)(ccc); checkIndexFunctionality!(immutable int*, const int)(cci); checkIndexFunctionality!(const(int)*, immutable int)(cim); checkIndexFunctionality!(const int*, immutable int)(cic); checkIndexFunctionality!(immutable int*, immutable int)(cii); checkSliceFunctionality!(const(int)*)(cmm); checkSliceFunctionality!(const int*)(cmc); checkSliceFunctionality!(immutable int*)(cmi); checkSliceFunctionality!(const(int)*)(ccm); checkSliceFunctionality!(const int*)(ccc); checkSliceFunctionality!(immutable int*)(cci); checkSliceFunctionality!(const(int)*)(cim); checkSliceFunctionality!(const int*)(cic); checkSliceFunctionality!(immutable int*)(cii); } { immutable Container!(int, immutable int*) imi; immutable Container!(const int, immutable int*) ici; immutable Container!(immutable int, immutable int*) iii; checkIndexFunctionality!(immutable int*, int)(imi); checkIndexFunctionality!(immutable int*, const int)(ici); checkIndexFunctionality!(immutable int*, immutable int)(iii); checkSliceFunctionality!(immutable int*)(imi); checkSliceFunctionality!(immutable int*)(ici); checkSliceFunctionality!(immutable int*)(iii); } } private void checkSliceFunctionality(Type, Container)(ref Container container) { import std.array : front; static if (__traits(hasMember, Container, "opSlice")) { auto r = container[]; static assert(is(typeof(r.front()) == Type)); static assert(is(typeof(container.length) == size_t)); assert(container.length == 0); } } private void checkIndexFunctionality(Type, KeyType, Container)(ref Container container) { import std.traits : hasFunctionAttributes; static assert(__traits(compiles, {container[KeyType.init];})); // The tests here will expect the wrong thing for opIndex implementations // that return by ref. static if (!hasFunctionAttributes!(Container.opIndex!Container, "ref")) static assert(is(typeof(container[KeyType.init]) == Type)); static assert(is(typeof(container.length) == size_t)); assert(container.length == 0); } unittest { testContainerDouble!(HashMap)(); testContainerDouble!(TreeMap)(); testContainerSingle!(HashSet)(); testContainerSingle!(UnrolledList)(); testContainerSingle!(OpenHashSet)(); version (D_InlineAsm_X86_64) testContainerSingle!(SimdSet)(); testContainerSingle!(SList)(); testContainerSingle!(TTree)(); testContainerSingle!(DynamicArray)(); testContainerSingle!(CyclicBuffer)(); } dcontainers-0.8.0~alpha.16/test/external_allocator_test.d000066400000000000000000000050641353550446300235430ustar00rootroot00000000000000module external_allocator_test; import containers.cyclicbuffer; import containers.dynamicarray; import containers.hashmap; import containers.hashset; import containers.immutablehashset; import containers.openhashset; import containers.simdset; import containers.slist; import containers.treemap; import containers.ttree; import containers.unrolledlist; import std.meta : AliasSeq; import std.range.primitives : walkLength; import std.stdio : stdout; import stdx.allocator.building_blocks.allocator_list : AllocatorList; import stdx.allocator.building_blocks.free_list : FreeList; import stdx.allocator.building_blocks.region : Region; import stdx.allocator.building_blocks.stats_collector : StatsCollector; import stdx.allocator.mallocator : Mallocator; // Chosen for a very important and completely undocumented reason private enum VERY_SPECIFIC_NUMBER = 371; private void testSingle(alias Container, Allocator)(Allocator allocator) { auto intMap = Container!(int, Allocator)(allocator); foreach (i; 0 .. VERY_SPECIFIC_NUMBER) intMap.insert(i); assert(intMap.length == VERY_SPECIFIC_NUMBER); } private void testDouble(alias Container, Allocator)(Allocator allocator) { auto intMap = Container!(int, int, Allocator)(allocator); foreach (i; 0 .. VERY_SPECIFIC_NUMBER) intMap[i] = VERY_SPECIFIC_NUMBER - i; assert(intMap.length == VERY_SPECIFIC_NUMBER); } version (D_InlineAsm_X86_64) { alias SingleContainers = AliasSeq!(CyclicBuffer, DynamicArray, HashSet, /+ImmutableHashSet,+/ OpenHashSet, SimdSet, SList, TTree, UnrolledList); } else { alias SingleContainers = AliasSeq!(CyclicBuffer, DynamicArray, HashSet, /+ImmutableHashSet,+/ OpenHashSet, /+SimdSet,+/ SList, TTree, UnrolledList); } alias DoubleContainers = AliasSeq!(HashMap, TreeMap); alias AllocatorType = StatsCollector!( FreeList!(AllocatorList!(a => Region!(Mallocator)(1024 * 1024), Mallocator), 64)); unittest { foreach (C; SingleContainers) { AllocatorType allocator; testSingle!(C, AllocatorType*)(&allocator); assert(allocator.numAllocate > 0 || allocator.numReallocate > 0, "No allocations happened for " ~ C.stringof); assert(allocator.numAllocate == allocator.numDeallocate || allocator.numReallocate > 0); assert(allocator.bytesUsed == 0); } foreach (C; DoubleContainers) { AllocatorType allocator; testDouble!(C, AllocatorType*)(&allocator); assert(allocator.numAllocate > 0 || allocator.numReallocate > 0, "No allocations happened for " ~ C.stringof); assert(allocator.numAllocate == allocator.numDeallocate || allocator.numReallocate > 0); assert(allocator.bytesUsed == 0); } } dcontainers-0.8.0~alpha.16/test/hashmap_gc_test.d000066400000000000000000000017041353550446300217500ustar00rootroot00000000000000import containers : HashMap; import std.stdio : writefln; import core.memory : GC; /** * Generate a random alphanumeric string. */ @trusted string randomString(uint len) { import std.ascii : letters, digits; import std.conv : to; import std.random : randomSample; import std.range : chain; auto asciiLetters = to!(dchar[])(letters); auto asciiDigits = to!(dchar[])(digits); if (len == 0) len = 1; auto res = to!string(randomSample(chain(asciiLetters, asciiDigits), len)); return res; } void main() { immutable iterationCount = 4; HashMap!(string, string) hmap; for (uint n = 1; n <= iterationCount; n++) { foreach (i; 0 .. 1_000_000) hmap[randomString(4)] = randomString(16); GC.collect(); hmap = HashMap!(string, string)(16); GC.collect(); foreach (i; 0 .. 1_000_000) hmap[randomString(4)] = randomString(16); GC.collect(); hmap.clear(); GC.collect(); writefln("iteration %s/%s finished", n, iterationCount); } } dcontainers-0.8.0~alpha.16/test/looptest.d000066400000000000000000000003651353550446300204720ustar00rootroot00000000000000module loops; import containers.ttree; import std.stdio; void main() { TTree!int ints; while (true) { foreach (i; 0 .. 1_000_000) ints.insert(i); foreach (i; 0 .. 1_000_000) ints.remove(i); writeln("iteration finished"); } } dcontainers-0.8.0~alpha.16/test/makefile000066400000000000000000000031661353550446300201560ustar00rootroot00000000000000.PHONY: test graphs clean DC?=dmd SRC:=$(shell find ../src/ -name "*.d") \ $(shell find ../stdx-allocator/source -name "*.d") FLAGS:=-unittest -main -g -cov -I../src/ -I../stdx-allocator/source -debug -wi FLAGS32:=$(FLAGS) -m32 all: all_32 all_64 all_64: test compile_test external_allocator_test hashmap_gc_test ./tests ./compile_test ./external_allocator_test ./hashmap_gc_test all_32: test_32 compile_test_32 external_allocator_test_32 ./tests_32 ./compile_test_32 ./external_allocator_test_32 test: $(SRC) $(DC) $(FLAGS) $(SRC) -oftests test_32: $(SRC) $(DC) $(FLAGS32) $(SRC) -oftests_32 compile_test: compile_test.d $(SRC) $(DC) $(FLAGS) compile_test.d $(SRC) -ofcompile_test compile_test_32: compile_test.d $(SRC) $(DC) $(FLAGS32) compile_test.d $(SRC) -ofcompile_test_32 external_allocator_test: external_allocator_test.d $(SRC) $(DC) $(FLAGS) external_allocator_test.d $(SRC) -ofexternal_allocator_test external_allocator_test_32: external_allocator_test.d $(SRC) $(DC) $(FLAGS32) external_allocator_test.d $(SRC) -ofexternal_allocator_test_32 looptest: looptest.d $(DC) looptest.d -inline -O -release \ ../src/memory/allocators.d \ ../src/containers/ttree.d \ ../src/containers/internal/node.d \ -I../src/ \ hashmap_gc_test: hashmap_gc_test.d $(SRC) $(DC) -g -inline -O -release \ -I../src/ -I../stdx-allocator/source -debug -wi \ $(SRC) \ hashmap_gc_test.d \ -ofhashmap_gc_test clean: rm -f tests rm -f *.o rm -f *.dot rm -f *.png rm -f ..*.lst rm -f looptest graphs: clean $(DC) $(FLAGS) $(SRC) -oftests -version=graphviz_debugging -./tests parallel "dot -Tpng {} > {.}.png" ::: graph*.dot dcontainers-0.8.0~alpha.16/times.png000066400000000000000000000240321353550446300173210ustar00rootroot00000000000000PNG  IHDR]T) pHYs-z8'IDATx TUuqPT.$ ^jPD0ĒttQ o?)o̲lOԔ) k >48,4@8\2~?8Z}~gw>r.۩qj Ў(" B.(" Ҵ\puueͪ*ggfM 6ck.~gW6L+W|qȐ!:'Ǒ)++5d\,**;w}f͚0}I&^pK[W^QQQIIIf``ɓ|؍Mح[={H(ȑ#nj?vX)hҌ  Em ;fS.zzzjQ' <})SJJJvq>},YD\???}C__l# )Ϝ93tЫW.]ĉAAAǎ3Lqqq1Rg:=z̘1gؤ x񨨨͛7/\Pn=ZbRkԩ۷oRߤͼzݦI6s:߼\P7o޽{'MܼySٿׯ%&&[={:rd2-X`z(,^ԩSÆ +,,LIIٲeKpppIIIjjjdddNNH2lؙM~ř3g-˗/ߺukLL̸q㼼erWrrrttt\\iBB")ZM6mܸhZcHHHAA$@G޽~ska>~7\@!PEr\@!PEr\@!PEr\@!PEr\@!PErm\+hѣ,KOG΄\lEOzQϛ\&!PEr\@!PEr\@!PEr\@!PEr\@!PEr\@!PEbEEkƪ*ggg[hl>l&rʕ/4fffĔ6@`S.͝;w߾}f >}!'O9re)ug EY3fL~~~uu2qDF |؏ME*SLIKK۷e]' )M&gp\\\˭6^SSS[ئϼcP\ :th{V:&ǣ6o޼pBVYY[QQVDzѼzJ(J6gYW3za=.(8o޼{N4IkKLLW8{F;NCٔ&iwCQFFF,oooF&ٔ/^9s޲|[&''GGGIp&$$hF:rl )(((..tttlگ_?hpr\@!PEr\@!PEr\@!PEr\@!PEr\@!PEr\@!PEr\@!PEr\@!PEr\@!PEr\@!PEr\@!PŪ*ggg: MŽ{]6>>~ٲeZː!CN<㓙SVVV[[++y\\fM~~A7o^pKkի8ydCٚspӧO[JJJ_ Efddxxxʲu԰a SRRl\RR#/8] eS.Ji9ƍk׮]lo3n8//˗ϟ?_INN3L ԋĦ\޽{UωlڴiƍEEEtttCBB $)===F:D~ska>~7\@!PEr\@x8[h >u\,؋QTڭ\@!PEr\@!PEr\@!PEr\@!PEr\@!PEr\@!PEr WVVsΕ+W:::47nضmۢE\]]m{M>qZޕ:vX{#tܸeE>AKKKWꫯ 'LQUUbŊf'gڵݺuk<>nqqիcccOlٲj*ؿȬnEAϿT^e^/7%w1}tE"Y=300`4+s`߾}\q;%Sx4*y k׮H]SOGy/_|Μ9۷oe7n}^tIBe޽=zG% r.))={9k,9{」G=yӧeP)!ZJ"VNΝ;,aK'߰a$ˈzRҧ$,߲_~Erw:tH7|*2d}7ٸscbb$DǏ/Ao4.ƍݻǧMf3\еH R!XBRAsj$ѯ_?)5$<<<.III߮O>MzS,2$""b>\jA eWڸCyyD\XXsQ*Ο~I2ƖlѧOWKUX&Fw-s$S ׳gϒ`/eg}ѷ~[=E+WL2E_gȑݔ3wѤѥs///JixM;0~=KU-k[T={4r#B3)n*HHH{lߍ?Z}MIӆFVִQ+'>?s۶m)h_\vM)w9)kh eee "}ͥ\F:snnn?בtנב(.#m%&]km|">)26oФI/rO>2qV%W4h$bAAAI;ytׯn޼yR|ھm9Ǐ˓/kut.l t랸_eYf{GP!))IBBQnO۸&޽{׮]l2%333&&L\im>IŶ}Q$f͚|}Z,DEE%%%EDDNk?5kMp[\:4G+ Fs***X6oZ Ű0M8%SlQFM1to:9g"$$d@GnI}\KLLo={jc&@+j~.FFF,oooF;NCٔ7ozƍ̵k.[7HNN3L Zih\޽k{BBB ===_@`߁cB+B.(" B.(" B.(" B.(" B.(" B.(" B.(" B.(" B.(" B.(" B.(FbUUA`!C'Ǒ)++m$h%ś7o^pKkիWuuuTTTRRRDDD^^^``ɓ4[\,))%^!(#G OIIU(vq>},Y$ 77O_7;;54?zեK8q"((رc&U_ Ht>ɇd~s!F=>!@\=zt~~n.;k5*,lCd{a9C!!!z:D;} A;Ԥ׾@j~.8p`ŧN6lXaaaJJʖ-[KJJRSS###srr,;NC5?%|LL̸q㼼/_>|iONN3L |IЁ{6mڸqcQQƐIJOOO׽{w{žh}n8 B.(" B.(" B.(" B.Jʫj ͹GOEWw.?jCسni @ˑ(" B.(" B.(" B.(" B.(" B.(" B.@q S#:жEY1lG-rŨ\rvv6s b\̌)++ιX]]8ydA윋Dg׊hҔ3 rCa)+9ii9*ϫV8@s.%&&7Ϟ=[C76;9Cgy Q8tSle\ .))IMMɑWiYYY9L&SBB_t ^HHHAAAqq8F\~B7s|s< @:ǣpms\@!PErW۶m{ꩧzeee;w\re?7noC+4cѵM\M+aZ8|RYWHHȦMsիWe.˚NNvu|~c]Bn<-nٲeժUIϞ= xbaju'x>fTTomcm|k+8qBZf~~ؗ_~ymD>""BNr=zڵk*:ښr/,R[n, Rw_#%u+EI89E^vFzuvܹqFbƌmN]w%UKQd?dnذAji-ڜ-͜9sQ<(}ƒ>({LSRRLm-7&+IɑK/IEy\.+((S~S ӧOy|ԨQogϞ-/ٽ;zaÆɚgΜ͕D  \裏WWW)k5GXX >0,,L"GF0aL5&&FRsr>}7 ˈǎ+**4ﶪJ)L"QPPSRY e۷oeZDdn~lj({Ů_.DpBW]t_/#$T$%,ǎ+-Ǻ&RQ,RTIa* 6le+WW9rFͫ7 $}_PiKfK駟VWWK:u7D:ҿxG{iK" [vh)yU;%w)ڹs$ב%O9@/ld,7u\3Iv~~ңX-YbH5#wY/<<\jPQ$H_:>|_mI&uy}rssp>g$m>g0}G/-?Vg+Eec;$/|ٺH2(Q&?uT*f˖-r-5myzh}].m7i$)žOmۦݕOWVV~7n| ׯl޼yovmmm#Z{ccc|9~},ȹU*5kH7~<)dBN~Gh„ ?v6Seڴi24'NɛW4C{.r.%}:ZWr }/_nCw{ՕwХ\l5rɤ.~}vwV֧yıf+ܸqü>t)"Zb@7/P5>AAA Jծ)/ԚWj7^x!>>ʕ+2].]djӤKYN>t%[b@̯#Ct+(ͯ)/ԶOr MWBs^^26tp5RhtÇbk^ q Jծ)/yV~@O<|jjjCלvmN/֐tu\l͋٢C\Aɾ:ZI^SSc~SJ4%$}OmY{7rp5Rh @]G{b@6WPjsy&>e? J1 \]M}/[l\2dHEE WɹƧ5Ki:v1 C>?ίVWg`Sk>?KA9zhwww9(ӦMOmՃkԆhRh]d܁v|1 y$~}s7z{}+pu 6i˖-ʏ?(i耚7Z2KuÇ.]b5b@4~}Ny3 \\\3گ5o8].:NB.(" B.(,ݫ]KOIENDB`