├── .gitignore ├── LICENSE ├── README.md ├── benchmarks └── benchmark_hashmap.d ├── dub.json └── source └── dstruct ├── graph.d ├── map.d ├── matrix.d ├── option.d ├── package.d ├── set.d ├── support.d └── weak_reference.d /.gitignore: -------------------------------------------------------------------------------- 1 | /.dub 2 | /dub.selections.json 3 | /dub.userprefs 4 | /__test__library__ 5 | /__test__default__ 6 | /benchmark_hashmap 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, w0rp 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dstruct 2 | 3 | This library offers a variety of data structures and operations on those 4 | data structures in D. 5 | 6 | ## Quick Start 7 | 8 | Check out the code somewhere and you can use it as a DUB package. Everything 9 | in the library is some form of template, so this is a source library and 10 | doesn't need to be built itself. 11 | 12 | ## Data Structures in This Library 13 | 14 | * [WeakReference(T)](source/dstruct/weak_reference.d) - An implementation of 15 | weak references. 16 | * [Some(T)](source/dstruct/option.d) - A type wrapping a nullable type which 17 | cannot be null. 18 | * [Option(T)](source/dstruct/option.d) - An Option/Maybe type for safer 19 | null handling. 20 | * [HashMap(K, V)](source/dstruct/map.d) - A garbage collected hashmap type, 21 | just like associative arrays, but usable in more pure and @safe code. 22 | * [HashSet(T)](source/dstruct/set.d) - A garbage collected hashset type. 23 | * [Matrix(T)](source/dstruct/matrix.d) - A garbage collected dynamic 24 | matrix type. 25 | * [Matrix(T, rowCount, columnCount)](source/dstruct/matrix.d) - A static 26 | matrix value type. 27 | * [BasicGraph(T, edgeDirection)](source/dstruct/graph.d) - Directed 28 | and undirected graph types. 29 | 30 | ## Design Philosophy 31 | 32 | This library is designed with the following philosophy. 33 | 34 | * Everything should be as ```@safe``` and ```pure``` as possible, to 35 | make it easier to write pure functions which are safe. 36 | * Exceptions should only be thrown when not doing so would be unsafe. 37 | * Any function which doesn't throw should be marked ```nothrow```. 38 | * As much as possible, you should be able to reference memory in a safe 39 | manner instead of having to copy it, to cut down on allocation. 40 | * If memory is going to be allocated, it should be done as little as possible, 41 | and when it happens it should *probably* be allocated on 42 | the garbage collected heap. 43 | -------------------------------------------------------------------------------- /benchmarks/benchmark_hashmap.d: -------------------------------------------------------------------------------- 1 | import std.datetime; 2 | import std.stdio; 3 | 4 | import dstruct.support; 5 | import dstruct.map; 6 | 7 | struct ScopedBenchmark { 8 | private: 9 | StopWatch _watch; 10 | string _message; 11 | public: 12 | @disable this(); 13 | @disable this(this); 14 | 15 | this(string message) { 16 | _message = message; 17 | _watch.start(); 18 | } 19 | 20 | ~this() { 21 | _watch.stop(); 22 | 23 | writefln("%s: %d usecs", _message, _watch.peek.usecs); 24 | } 25 | } 26 | 27 | struct BadHashObject { 28 | int value; 29 | 30 | this(int value) { 31 | this.value = value; 32 | } 33 | 34 | @safe nothrow 35 | size_t toHash() const { 36 | return value / 10; 37 | } 38 | 39 | @nogc @safe nothrow pure 40 | bool opEquals(ref const BadHashObject other) const { 41 | return value == other.value; 42 | } 43 | 44 | } 45 | 46 | size_t runsPerTest = 50; 47 | int testContainerSize = 10_000; 48 | 49 | enum MapImpl { 50 | std, 51 | dstruct 52 | } 53 | 54 | template MapType(MapImpl impl, K, V) { 55 | static if (impl == MapImpl.std) { 56 | alias MapType = V[K]; 57 | } else { 58 | alias MapType = HashMap!(K, V); 59 | } 60 | } 61 | 62 | void main(string[] argv) { 63 | import std.typetuple; 64 | import core.memory; 65 | 66 | // Disable collections just in case they interrupt execution. 67 | // This could throw the benchmarks off. 68 | GC.disable(); 69 | 70 | foreach(impl; TypeTuple!(MapImpl.std, MapImpl.dstruct)) { 71 | writeln(); 72 | 73 | { 74 | alias T = MapType!(impl, int, int); 75 | auto mark = ScopedBenchmark("fill (" ~ T.stringof ~ ")"); 76 | 77 | foreach(i; 0 .. runsPerTest) { 78 | T map; 79 | 80 | foreach(num; 0 .. testContainerSize) { 81 | map[num] = num; 82 | } 83 | } 84 | } 85 | 86 | { 87 | alias T = MapType!(impl, int, int); 88 | auto mark = ScopedBenchmark("fill and remove (" ~ T.stringof ~ ")"); 89 | 90 | foreach(i; 0 .. runsPerTest) { 91 | T map; 92 | 93 | foreach(num; 0 .. testContainerSize) { 94 | map[num] = num; 95 | } 96 | 97 | foreach(num; 0 .. testContainerSize) { 98 | map.remove(num); 99 | } 100 | } 101 | 102 | } 103 | 104 | { 105 | alias T = MapType!(impl, int, int); 106 | auto mark = ScopedBenchmark("fill and lookup (" ~ T.stringof ~ ")"); 107 | 108 | foreach(i; 0 .. runsPerTest) { 109 | T map; 110 | 111 | foreach(num; 0 .. testContainerSize) { 112 | map[num] = num; 113 | } 114 | 115 | foreach(num; 0 .. testContainerSize) { 116 | auto ptr = num in map; 117 | } 118 | } 119 | } 120 | 121 | { 122 | alias T = MapType!(impl, string, string); 123 | auto mark = ScopedBenchmark("fill (small) (" ~ T.stringof ~ ")"); 124 | 125 | foreach(i; 0 .. runsPerTest) { 126 | T map; 127 | 128 | map["abcdefghijkl"] = "abc"; 129 | map["wgenwegwgewg"] = "abc"; 130 | map["36in33l3n643"] = "abc"; 131 | // Item 4: This should cause another alloc in the new map. 132 | map["36inwzv3n643"] = "abc"; 133 | map["fxnrgegxigww"] = "abc"; 134 | map["nwlt4n43b643"] = "abc"; 135 | map["wzve35496f4d"] = "abc"; 136 | // Item 8: This should cause another alloc in the new map. 137 | map["333n35353335"] = "abc"; 138 | map["t36443643643"] = "abc"; 139 | map["454464n43323"] = "abc"; 140 | map["352352262362"] = "abc"; 141 | map["n33539353353"] = "abc"; 142 | map["f52352523523"] = "abc"; 143 | map["235232235232"] = "abc"; 144 | map["343353534n44"] = "abc"; 145 | // Item 16: This should cause another alloc in the new map. 146 | map["353353535353"] = "abc"; 147 | map["w3535353n353"] = "abc"; 148 | map["32363i239233"] = "abc"; 149 | map["bw4363b44334"] = "abc"; 150 | map["34b433463643"] = "abc"; 151 | map["nwk353445544"] = "abc"; 152 | map["n436k34n6436"] = "abc"; 153 | map["36n43n43n434"] = "abc"; 154 | map["354346434444"] = "abc"; 155 | map["n43k43544353"] = "abc"; 156 | map["435636433463"] = "abc"; 157 | map["463n43kn4363"] = "abc"; 158 | map["4643n4636432"] = "abc"; 159 | map["845b4c9u54kd"] = "abc"; 160 | map["353463464364"] = "abc"; 161 | map["44n63kln3363"] = "abc"; 162 | // Item 32: This should cause the last allocation. 163 | map["464364363363"] = "abc"; 164 | } 165 | } 166 | 167 | { 168 | alias T = MapType!(impl, string, string); 169 | auto mark = ScopedBenchmark("fill (small) and remove (" ~ T.stringof ~ ")"); 170 | 171 | foreach(i; 0 .. runsPerTest) { 172 | T map; 173 | 174 | map["abcdefghijkl"] = "abc"; 175 | map["wgenwegwgewg"] = "abc"; 176 | map["36in33l3n643"] = "abc"; 177 | // Item 4: This should cause another alloc in the new map. 178 | map["36inwzv3n643"] = "abc"; 179 | map["fxnrgegxigww"] = "abc"; 180 | map["nwlt4n43b643"] = "abc"; 181 | map["wzve35496f4d"] = "abc"; 182 | // Item 8: This should cause another alloc in the new map. 183 | map["333n35353335"] = "abc"; 184 | map["t36443643643"] = "abc"; 185 | map["454464n43323"] = "abc"; 186 | map["352352262362"] = "abc"; 187 | map["n33539353353"] = "abc"; 188 | map["f52352523523"] = "abc"; 189 | map["235232235232"] = "abc"; 190 | map["343353534n44"] = "abc"; 191 | // Item 16: This should cause another alloc in the new map. 192 | map["353353535353"] = "abc"; 193 | map["w3535353n353"] = "abc"; 194 | map["32363i239233"] = "abc"; 195 | map["bw4363b44334"] = "abc"; 196 | map["34b433463643"] = "abc"; 197 | map["nwk353445544"] = "abc"; 198 | map["n436k34n6436"] = "abc"; 199 | map["36n43n43n434"] = "abc"; 200 | map["354346434444"] = "abc"; 201 | map["n43k43544353"] = "abc"; 202 | map["435636433463"] = "abc"; 203 | map["463n43kn4363"] = "abc"; 204 | map["4643n4636432"] = "abc"; 205 | map["845b4c9u54kd"] = "abc"; 206 | map["353463464364"] = "abc"; 207 | map["44n63kln3363"] = "abc"; 208 | // Item 32: This should cause the last allocation. 209 | map["464364363363"] = "abc"; 210 | 211 | map.remove("abcdefghijkl"); 212 | map.remove("wgenwegwgewg"); 213 | map.remove("36in33l3n643"); 214 | map.remove("36inwzv3n643"); 215 | map.remove("fxnrgegxigww"); 216 | map.remove("nwlt4n43b643"); 217 | map.remove("wzve35496f4d"); 218 | map.remove("333n35353335"); 219 | map.remove("t36443643643"); 220 | map.remove("454464n43323"); 221 | map.remove("352352262362"); 222 | map.remove("n33539353353"); 223 | map.remove("f52352523523"); 224 | map.remove("235232235232"); 225 | map.remove("343353534n44"); 226 | map.remove("353353535353"); 227 | map.remove("w3535353n353"); 228 | map.remove("32363i239233"); 229 | map.remove("bw4363b44334"); 230 | map.remove("34b433463643"); 231 | map.remove("nwk353445544"); 232 | map.remove("n436k34n6436"); 233 | map.remove("36n43n43n434"); 234 | map.remove("354346434444"); 235 | map.remove("n43k43544353"); 236 | map.remove("435636433463"); 237 | map.remove("463n43kn4363"); 238 | map.remove("4643n4636432"); 239 | map.remove("845b4c9u54kd"); 240 | map.remove("353463464364"); 241 | map.remove("44n63kln3363"); 242 | map.remove("464364363363"); 243 | } 244 | } 245 | 246 | { 247 | alias T = MapType!(impl, BadHashObject, int); 248 | auto mark = ScopedBenchmark("fill and lookup (heavy collision) (" ~ T.stringof ~ ")"); 249 | 250 | foreach(i; 0 .. runsPerTest) { 251 | T map; 252 | 253 | foreach(num; 0 .. testContainerSize) { 254 | auto obj = BadHashObject(num); 255 | 256 | map[obj] = num; 257 | } 258 | 259 | foreach(num; 0 .. testContainerSize) { 260 | auto ptr = BadHashObject(num) in map; 261 | } 262 | } 263 | } 264 | } 265 | 266 | writeln(); 267 | 268 | { 269 | auto mark = ScopedBenchmark("fill, pre-allocated"); 270 | 271 | foreach(i; 0 .. runsPerTest) { 272 | auto map = HashMap!(int, int)(testContainerSize); 273 | 274 | foreach(num; 0 .. testContainerSize) { 275 | map[num] = num; 276 | } 277 | } 278 | } 279 | 280 | writeln(); 281 | } 282 | 283 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dstruct", 3 | "targetType": "sourceLibrary", 4 | "description": "D Data Structures", 5 | "authors": ["w0rp"], 6 | "homepage": "https://github.com/w0rp/dstruct", 7 | "license": "BSD 2-clause", 8 | "copyright": "Copyright © 2015, w0rp", 9 | "dependencies": { 10 | }, 11 | "configurations": [ 12 | {"name": "default"}, 13 | { 14 | "name": "benchmark_hashmap", 15 | "targetType": "executable", 16 | "targetName": "benchmark_hashmap", 17 | "mainSourceFile": "benchmarks/benchmark_hashmap.d" 18 | }, 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /source/dstruct/graph.d: -------------------------------------------------------------------------------- 1 | module dstruct.graph; 2 | 3 | import core.stdc.string: memmove; 4 | 5 | import dstruct.support; 6 | import dstruct.map; 7 | 8 | /// The directionality of a graph. 9 | enum EdgeDirection : bool { 10 | /// Undirected, meaning edges face either direction. 11 | undirected, 12 | /// Directed, meaning edges face one direction. 13 | directed, 14 | } 15 | 16 | @trusted 17 | private bool findAndRemove(T)(ref T[] arr, ref T needle) { 18 | foreach(index; 0 .. arr.length) { 19 | if (cast() arr[index] == cast() needle) { 20 | // When the index we find is right at the end, we shouldn't 21 | // move anything, just reduce the slice on the right by one. 22 | if (index != arr.length - 1) { 23 | // Shift all of the array elements back. 24 | memmove( 25 | cast(void*) &arr[index], 26 | cast(void*) &arr[index + 1], 27 | T.sizeof * (arr.length - index) 28 | ); 29 | } 30 | 31 | // Now shrink the slice by one element. 32 | arr = arr[0 .. $ - 1]; 33 | 34 | return true; 35 | } 36 | } 37 | 38 | return false; 39 | } 40 | 41 | private void addIfMissing(T)(ref T[] arr, ref T value) { 42 | bool found = false; 43 | 44 | foreach(ref otherValue; arr) { 45 | if (cast() value == cast() otherValue) { 46 | found = true; 47 | break; 48 | } 49 | } 50 | 51 | if (!found) { 52 | arr ~= value; 53 | } 54 | } 55 | 56 | /** 57 | * This struct represents a graph type as a reference type. 58 | * 59 | * Graphs types have a type of vertex and a direction. 60 | * 61 | * The graphs are represented by adjacency lists, which have good 62 | * all-around performance characteristics for sparse graphs. 63 | */ 64 | struct BasicGraph(Vertex, EdgeDirection edgeDirection) { 65 | private: 66 | HashMap!(Vertex, Vertex[]) adjacencyMap; 67 | public: 68 | /// true if this graph is a directed graph. 69 | enum bool isDirected = edgeDirection == EdgeDirection.directed; 70 | 71 | /** 72 | * Add a vertex to the graph. 73 | */ 74 | void addVertex(ref Vertex vertex) { 75 | adjacencyMap.setDefault(vertex); 76 | } 77 | 78 | /// ditto 79 | void addVertex(Vertex vertex) { 80 | addVertex(vertex); 81 | } 82 | 83 | /** 84 | * Remove the given vertex from this graph. 85 | * 86 | * Any edges to the given vertex will be removed. 87 | * 88 | * Returns: true if a vertex was removed. 89 | */ 90 | bool removeVertex(ref Vertex vertex) { 91 | // Try to remove the vertex's adjacency mapping first. 92 | if (!adjacencyMap.remove(vertex)) { 93 | return false; 94 | } 95 | 96 | foreach(ref list; adjacencyMap.byValue) { 97 | findAndRemove(list, vertex); 98 | } 99 | 100 | return true; 101 | } 102 | 103 | /// ditto 104 | bool removeVertex(Vertex vertex) { 105 | return removeVertex(vertex); 106 | } 107 | 108 | /** 109 | * Returns: true if the vertex is in the graph. 110 | */ 111 | bool hasVertex(ref Vertex vertex) const { 112 | return (vertex in adjacencyMap) !is null; 113 | } 114 | 115 | /// ditto 116 | bool hasVertex(Vertex vertex) const { 117 | return hasVertex(vertex); 118 | } 119 | 120 | /** 121 | * Add an edge to the graph. 122 | * 123 | * New vertices will be added to the graph automatically. 124 | */ 125 | void addEdge(ref Vertex left, ref Vertex right) { 126 | adjacencyMap.setDefault(left).addIfMissing(right); 127 | 128 | static if (!isDirected) { 129 | adjacencyMap.setDefault(right).addIfMissing(left); 130 | } else { 131 | addVertex(right); 132 | } 133 | } 134 | 135 | /// ditto 136 | void addEdge(ref Vertex left, Vertex right) { 137 | addEdge(left, right); 138 | } 139 | 140 | /// ditto 141 | void addEdge(Vertex left, ref Vertex right) { 142 | addEdge(left, right); 143 | } 144 | 145 | /// ditto 146 | void addEdge(Vertex left, Vertex right) { 147 | addEdge(left, right); 148 | } 149 | 150 | /** 151 | * Remove an edge from the graph. 152 | * 153 | * Vertices in the edge will not be removed. 154 | * 155 | * Returns: true if an edge was removed. 156 | */ 157 | bool removeEdge(ref Vertex left, ref Vertex right) { 158 | auto listPtr = left in adjacencyMap; 159 | 160 | if (listPtr is null) { 161 | return false; 162 | } 163 | 164 | if (!findAndRemove(*listPtr, right)) { 165 | return false; 166 | } 167 | 168 | static if (!isDirected) { 169 | findAndRemove(adjacencyMap[right], left); 170 | } 171 | 172 | return true; 173 | } 174 | 175 | /// ditto 176 | bool removeEdge(ref Vertex left, Vertex right) { 177 | return removeEdge(left, right); 178 | } 179 | 180 | /// ditto 181 | bool removeEdge(Vertex left, ref Vertex right) { 182 | return removeEdge(left, right); 183 | } 184 | 185 | /// ditto 186 | bool removeEdge(Vertex left, Vertex right) { 187 | return removeEdge(left, right); 188 | } 189 | 190 | /** 191 | * Check if an edge exists in the graph. 192 | * 193 | * Returns: true if the edge exists in the graph. 194 | */ 195 | bool hasEdge(ref Vertex left, ref Vertex right) const { 196 | if (auto listPtr = left in adjacencyMap) { 197 | foreach(ref outgoingVertex; *listPtr) { 198 | if (cast() right == cast() outgoingVertex) { 199 | return true; 200 | } 201 | } 202 | 203 | return false; 204 | } 205 | 206 | return false; 207 | } 208 | 209 | /// ditto 210 | bool hasEdge(ref Vertex left, Vertex right) const { 211 | return hasEdge(left, right); 212 | } 213 | 214 | /// ditto 215 | bool hasEdge(Vertex left, ref Vertex right) const { 216 | return hasEdge(left, right); 217 | } 218 | 219 | /// ditto 220 | bool hasEdge(Vertex left, Vertex right) const { 221 | return hasEdge(left, right); 222 | } 223 | 224 | /** 225 | * Return the number of vertices in this graph 226 | * in constant time. 227 | * 228 | * Returns: The number of vertices in this graph. 229 | */ 230 | @property 231 | size_t vertexCount() const { 232 | return adjacencyMap.length; 233 | } 234 | 235 | /** 236 | * Return the number of directed edges in this graph 237 | * in linear time. If this graph is a graph with undirected 238 | * edges, this will always be double the undirected 239 | * edge count. 240 | * 241 | * Returns: The number of directed edges in this graph. 242 | */ 243 | size_t directedEdgeCount() const { 244 | size_t count = 0; 245 | 246 | foreach(ref list; adjacencyMap.byValue()) { 247 | count += list.length; 248 | } 249 | 250 | return count; 251 | } 252 | 253 | /** 254 | * Return the number of edges in this graph 255 | * in linear time. 256 | * 257 | * Returns: The number of edges in this graph. 258 | */ 259 | size_t edgeCount() const { 260 | static if (!isDirected) { 261 | return directedEdgeCount() / 2; 262 | } else { 263 | return directedEdgeCount(); 264 | } 265 | } 266 | } 267 | 268 | /** 269 | * An edge in a graph. 270 | */ 271 | struct Edge(V) { 272 | V from; 273 | V to; 274 | } 275 | 276 | /// A shorthand for an undirected graph. 277 | alias Graph(Vertex) = BasicGraph!(Vertex, EdgeDirection.undirected); 278 | 279 | /// A shorthand for a directed graph. 280 | alias Digraph(Vertex) = BasicGraph!(Vertex, EdgeDirection.directed); 281 | 282 | /// Test if a type T is an undirected graph type with a particular vertex type. 283 | enum isUndirectedGraph(T, Vertex) = is(T == BasicGraph!(Vertex, EdgeDirection.undirected)); 284 | 285 | /// Test if a type T is a directed graph type with a particular vertex type. 286 | enum isDirectedGraph(T, Vertex) = is(T == BasicGraph!(Vertex, EdgeDirection.directed)); 287 | 288 | /// Test if a type T is any graph type with a particular vertex type. 289 | enum isGraph(T, Vertex) = isUndirectedGraph!(T, Vertex) || isDirectedGraph!(T, Vertex); 290 | 291 | // Test the templates. 292 | unittest { 293 | Graph!int graph; 294 | 295 | assert(isUndirectedGraph!(typeof(graph), int)); 296 | assert(isGraph!(typeof(graph), int)); 297 | assert(!isGraph!(typeof(graph), short)); 298 | 299 | Digraph!int digraph; 300 | 301 | assert(isDirectedGraph!(typeof(digraph), int)); 302 | assert(isGraph!(typeof(digraph), int)); 303 | assert(!isGraph!(typeof(digraph), short)); 304 | } 305 | 306 | // Test adding vertices and the vertex count on graphs 307 | unittest { 308 | @safe pure nothrow 309 | void runTest() { 310 | Graph!string graph; 311 | 312 | foreach(symbol; ["a", "b", "c", "d", "a"]) { 313 | graph.addVertex(symbol); 314 | } 315 | 316 | assert(graph.vertexCount == 4); 317 | } 318 | 319 | runTest(); 320 | } 321 | 322 | unittest { 323 | @safe pure nothrow 324 | void runTest() { 325 | Digraph!string digraph; 326 | 327 | foreach(symbol; ["a", "b", "c", "d", "a"]) { 328 | digraph.addVertex(symbol); 329 | } 330 | 331 | // Test that @nogc works. 332 | @nogc @safe pure nothrow 333 | void runNoGCPart(typeof(digraph) digraph) { 334 | assert(digraph.vertexCount == 4); 335 | } 336 | 337 | runNoGCPart(digraph); 338 | } 339 | 340 | runTest(); 341 | } 342 | 343 | // Test adding edges and the edge count. 344 | unittest { 345 | static assert(isUndirectedGraph!(Graph!byte, byte)); 346 | 347 | @safe pure nothrow 348 | void runTest() { 349 | Graph!byte graph; 350 | 351 | byte[2][] edgeList = [[1, 2], [2, 1], [3, 4], [5, 6]]; 352 | 353 | foreach(edge; edgeList) { 354 | graph.addEdge(edge[0], edge[1]); 355 | } 356 | 357 | @nogc @safe pure nothrow 358 | void runNoGCPart(typeof(graph) graph) { 359 | assert(graph.directedEdgeCount == 6); 360 | assert(graph.edgeCount == 3); 361 | assert(graph.hasVertex(1)); 362 | } 363 | 364 | runNoGCPart(graph); 365 | } 366 | 367 | runTest(); 368 | } 369 | 370 | // Test adding edges and the edge count. 371 | unittest { 372 | @safe pure nothrow 373 | void runTest() { 374 | Digraph!byte graph; 375 | 376 | byte[2][] edgeList = [[1, 2], [2, 1], [3, 4], [5, 6]]; 377 | 378 | foreach(edge; edgeList) { 379 | graph.addEdge(edge[0], edge[1]); 380 | } 381 | 382 | @nogc @safe pure nothrow 383 | void runNoGCPart(typeof(graph) graph) { 384 | assert(graph.directedEdgeCount == 4); 385 | assert(graph.edgeCount == 4); 386 | assert(graph.hasVertex(1)); 387 | } 388 | 389 | runNoGCPart(graph); 390 | } 391 | 392 | runTest(); 393 | } 394 | 395 | // Test adding one undirected graph edge implies the reverse. 396 | unittest { 397 | @safe pure nothrow 398 | void runTest() { 399 | Graph!byte graph; 400 | 401 | byte[2][] edgeList = [[1, 2], [3, 4]]; 402 | 403 | foreach(edge; edgeList) { 404 | graph.addEdge(edge[0], edge[1]); 405 | } 406 | 407 | @nogc @safe pure nothrow 408 | void runNoGCPart(typeof(graph) graph) { 409 | assert(graph.edgeCount == 2); 410 | assert(graph.hasEdge(2, 1)); 411 | assert(graph.hasEdge(4, 3)); 412 | } 413 | 414 | runNoGCPart(graph); 415 | } 416 | 417 | runTest(); 418 | } 419 | 420 | // Test that removing a vertex also removes the edges. 421 | unittest { 422 | @safe pure nothrow 423 | void runTest() { 424 | Digraph!byte graph; 425 | 426 | byte[2][] edgeList = [[1, 2], [3, 1], [2, 3]]; 427 | 428 | foreach(edge; edgeList) { 429 | graph.addEdge(edge[0], edge[1]); 430 | } 431 | 432 | @nogc @safe pure nothrow 433 | void runNoGCPart(typeof(graph) graph) { 434 | assert(graph.removeVertex(1)); 435 | assert(graph.edgeCount == 1); 436 | assert(graph.hasEdge(2, 3)); 437 | } 438 | 439 | runNoGCPart(graph); 440 | } 441 | 442 | runTest(); 443 | } 444 | 445 | // Test adding and removing vertices for immutable objects 446 | unittest { 447 | struct SomeType { 448 | int x; 449 | } 450 | 451 | @safe pure nothrow 452 | void runTest() { 453 | Digraph!(immutable(SomeType)) graph; 454 | 455 | graph.addVertex(immutable(SomeType)(3)); 456 | graph.addVertex(immutable(SomeType)(4)); 457 | 458 | @nogc @safe pure nothrow 459 | void runNoGCPart(typeof(graph) graph) { 460 | assert(graph.removeVertex(immutable(SomeType)(3))); 461 | assert(graph.removeVertex(immutable(SomeType)(4))); 462 | } 463 | 464 | runNoGCPart(graph); 465 | } 466 | 467 | runTest(); 468 | } 469 | 470 | unittest { 471 | @safe pure nothrow 472 | void runTest() { 473 | Graph!byte graph; 474 | 475 | byte[2][] edgeList = [[1, 2], [3, 1], [2, 3]]; 476 | 477 | foreach(edge; edgeList) { 478 | graph.addEdge(edge[0], edge[1]); 479 | } 480 | 481 | @nogc @safe pure nothrow 482 | void runNoGCPart(typeof(graph) graph) { 483 | assert(graph.removeVertex(1)); 484 | assert(graph.edgeCount == 1); 485 | assert(graph.hasEdge(2, 3)); 486 | } 487 | 488 | runNoGCPart(graph); 489 | } 490 | 491 | runTest(); 492 | } 493 | 494 | /** 495 | * Given any type of graph, produce a range through the vertices of the graph. 496 | * 497 | * Params: 498 | * graph = A graph. 499 | * 500 | * Returns: 501 | * A ForwardRange through the vertices of the graph. 502 | */ 503 | @nogc @safe pure nothrow 504 | auto vertices(V, EdgeDirection edgeDirection) 505 | (auto ref BasicGraph!(V, edgeDirection) graph) { 506 | return graph.adjacencyMap.byKey; 507 | } 508 | 509 | /// ditto 510 | @nogc @safe pure nothrow 511 | auto vertices(V, EdgeDirection edgeDirection) 512 | (auto ref const(BasicGraph!(V, edgeDirection)) graph) { 513 | return graph.adjacencyMap.byKey; 514 | } 515 | 516 | /// ditto 517 | @nogc @safe pure nothrow 518 | auto vertices(V, EdgeDirection edgeDirection) 519 | (auto ref immutable(BasicGraph!(V, edgeDirection)) graph) { 520 | return graph.adjacencyMap.byKey; 521 | } 522 | 523 | unittest { 524 | @safe pure nothrow 525 | void runTest() { 526 | Digraph!string graph; 527 | 528 | graph.addEdge("a", "b"); 529 | graph.addEdge("a", "c"); 530 | graph.addEdge("a", "d"); 531 | graph.addEdge("b", "e"); 532 | graph.addEdge("b", "f"); 533 | graph.addEdge("b", "g"); 534 | 535 | @nogc @safe pure nothrow 536 | void runNoGCPart(typeof(graph) graph) { 537 | foreach(vertex; graph.vertices) {} 538 | } 539 | 540 | runNoGCPart(graph); 541 | 542 | string[] vertexList; 543 | 544 | foreach(vertex; graph.vertices) { 545 | vertexList ~= vertex; 546 | } 547 | 548 | // We know we will get this order from how the hashing works. 549 | assert(vertexList == ["a", "b", "c", "d", "e", "f", "g"]); 550 | } 551 | 552 | runTest(); 553 | } 554 | 555 | unittest { 556 | Graph!string mGraph; 557 | const(Graph!string) cGraph; 558 | immutable(Graph!string) iGraph; 559 | 560 | auto mVertices = mGraph.vertices(); 561 | auto cVertices = cGraph.vertices(); 562 | auto iVertices = iGraph.vertices(); 563 | 564 | assert(is(typeof(mVertices.front) == string)); 565 | assert(is(typeof(cVertices.front) == const(string))); 566 | assert(is(typeof(iVertices.front) == immutable(string))); 567 | } 568 | 569 | /** 570 | * A range through the edges of a graph. 571 | */ 572 | struct EdgeRange(V, VArr) { 573 | private: 574 | KeyValueRange!(V, VArr) _keyValueRange; 575 | size_t _outgoingIndex; 576 | 577 | @nogc @safe pure nothrow 578 | this (typeof(_keyValueRange) keyValueRange) { 579 | _keyValueRange = keyValueRange; 580 | 581 | // Advance until we find an edge. 582 | while (!_keyValueRange.empty 583 | && _keyValueRange.front.value.length == 0) { 584 | _keyValueRange.popFront; 585 | } 586 | } 587 | public: 588 | /// 589 | @nogc @safe pure nothrow 590 | inout(typeof(this)) save() inout { 591 | return this; 592 | } 593 | 594 | /// 595 | @nogc @safe pure nothrow 596 | @property 597 | bool empty() const { 598 | return _keyValueRange.empty; 599 | } 600 | 601 | /// 602 | @nogc @safe pure nothrow 603 | @property 604 | Edge!V front() const { 605 | auto item = _keyValueRange.front; 606 | 607 | return Edge!V(item.key, item.value[_outgoingIndex]); 608 | } 609 | 610 | /// 611 | @nogc @safe pure nothrow 612 | void popFront() { 613 | if (++_outgoingIndex < _keyValueRange.front.value.length) { 614 | // There's another outgoing edge in the list, so move to that. 615 | return; 616 | } 617 | 618 | // We have to find the next vertex with a non-empty adjacency list. 619 | _outgoingIndex = 0; 620 | 621 | do { 622 | _keyValueRange.popFront; 623 | } while (!_keyValueRange.empty 624 | && _keyValueRange.front.value.length == 0); 625 | } 626 | } 627 | 628 | /** 629 | * Given any type of graph, produce a range through the edges of the graph. 630 | * 631 | * Params: 632 | * graph = A graph. 633 | * 634 | * Returns: 635 | * A ForwardRange through the edges of the graph. 636 | */ 637 | @nogc @safe pure nothrow 638 | auto edges(V, EdgeDirection edgeDirection) 639 | (auto ref BasicGraph!(V, edgeDirection) graph) { 640 | return EdgeRange!(V, V[])(graph.adjacencyMap.byKeyValue); 641 | } 642 | 643 | /// ditto 644 | @nogc @trusted pure nothrow 645 | auto edges(V, EdgeDirection edgeDirection) 646 | (auto ref const(BasicGraph!(V, edgeDirection)) graph) { 647 | return EdgeRange!(const(V), const(V[]))( 648 | cast(KeyValueRange!(const(V), const(V[]))) 649 | graph.adjacencyMap.byKeyValue 650 | ); 651 | } 652 | 653 | /// ditto 654 | @nogc @trusted pure nothrow 655 | auto edges(V, EdgeDirection edgeDirection) 656 | (auto ref immutable(BasicGraph!(V, edgeDirection)) graph) { 657 | return EdgeRange!(immutable(V), immutable(V[]))( 658 | cast(KeyValueRange!(immutable(V), immutable(V[]))) 659 | graph.adjacencyMap.byKeyValue 660 | ); 661 | } 662 | 663 | unittest { 664 | @safe pure nothrow 665 | void runTest() { 666 | Digraph!string graph; 667 | 668 | graph.addEdge("a", "b"); 669 | graph.addEdge("a", "c"); 670 | graph.addEdge("a", "d"); 671 | graph.addEdge("b", "e"); 672 | graph.addEdge("b", "f"); 673 | graph.addEdge("b", "g"); 674 | 675 | @nogc @safe pure nothrow 676 | void runNoGCPart(typeof(graph) graph) { 677 | foreach(vertex; graph.edges) {} 678 | } 679 | 680 | runNoGCPart(graph); 681 | 682 | Edge!string[] edgeList; 683 | 684 | foreach(edge; graph.edges) { 685 | edgeList ~= edge; 686 | } 687 | 688 | // We know we will get this order from how the hashing works. 689 | assert(edgeList.length); 690 | assert(edgeList[0].from == "a"); 691 | assert(edgeList[0].to == "b"); 692 | assert(edgeList[1].from == "a"); 693 | assert(edgeList[1].to == "c"); 694 | assert(edgeList[2].from == "a"); 695 | assert(edgeList[2].to == "d"); 696 | assert(edgeList[3].from == "b"); 697 | assert(edgeList[3].to == "e"); 698 | assert(edgeList[4].from == "b"); 699 | assert(edgeList[4].to == "f"); 700 | assert(edgeList[5].from == "b"); 701 | assert(edgeList[5].to == "g"); 702 | } 703 | 704 | runTest(); 705 | } 706 | 707 | unittest { 708 | Graph!string mGraph; 709 | const(Graph!string) cGraph; 710 | immutable(Graph!string) iGraph; 711 | 712 | auto mVertices = mGraph.edges(); 713 | auto cVertices = cGraph.edges(); 714 | auto iVertices = iGraph.edges(); 715 | 716 | assert(is(typeof(mVertices.front) == Edge!string)); 717 | assert(is(typeof(cVertices.front) == Edge!(const(string)))); 718 | assert(is(typeof(iVertices.front) == Edge!(immutable(string)))); 719 | } 720 | -------------------------------------------------------------------------------- /source/dstruct/map.d: -------------------------------------------------------------------------------- 1 | module dstruct.map; 2 | 3 | import core.memory; 4 | import core.exception; 5 | 6 | import core.stdc.string: memcpy, memset; 7 | 8 | import std.range : ElementType; 9 | import std.traits : Unqual, isPointer; 10 | 11 | import dstruct.support; 12 | 13 | private enum EntryState { 14 | empty = 0, 15 | occupied = 1, 16 | deleted = 2, 17 | } 18 | 19 | private enum SearchFor { 20 | empty = 1, 21 | occupied = 2, 22 | deleted = 4, 23 | notDeleted = empty | occupied, 24 | notOccupied = empty | deleted, 25 | any = empty | occupied | deleted, 26 | } 27 | 28 | private enum is64Bit = is(size_t == ulong); 29 | 30 | // Given a size, compute the space in bytes required for a member when 31 | // the member is aligned to a size_t.sizeof boundary. 32 | private size_t alignedSize(size_t size) { 33 | if (size % size_t.sizeof) { 34 | // If there's a remainder, then our boundary is a little ahead. 35 | return cast(size_t) (size / size_t.sizeof) + 1 * size_t.sizeof; 36 | } 37 | 38 | // If the size is cleanly divisible, it will fit right into place. 39 | return size; 40 | } 41 | 42 | private enum isHashIdentical(T) = 43 | is(T : uint) 44 | | is(T : char) 45 | | is(T : wchar) 46 | | is(T : dchar) 47 | | isPointer!T 48 | | (is64Bit && is(T : ulong)); 49 | 50 | /** 51 | * An item from a map. 52 | * 53 | * The keys and the values in the map are references into the map itself. 54 | */ 55 | struct Entry(K, V) { 56 | private: 57 | K _key; 58 | V _value; 59 | EntryState _state = EntryState.empty; 60 | 61 | static if (!isHashIdentical!K) { 62 | size_t _hash; 63 | } 64 | public: 65 | /** 66 | * A key from the map. 67 | */ 68 | @nogc @safe pure nothrow 69 | @property ref inout(K) key() inout { 70 | return _key; 71 | } 72 | 73 | /** 74 | * A value from the map. 75 | */ 76 | @nogc @safe pure nothrow 77 | @property ref inout(V) value() inout { 78 | return _value; 79 | } 80 | } 81 | 82 | // TODO: Test different entry sizes. 83 | 84 | static if(__VERSION__ < 2066) { 85 | private alias SafeGetHashType = size_t delegate(const(void*)) pure nothrow; 86 | } else { 87 | // Use mixin to hide UDA errors from old D compilers, it will never 88 | // execute the mixin, so it won't report an error in syntax. 89 | mixin("private alias SafeGetHashType = size_t delegate(const(void*)) @nogc pure nothrow;"); 90 | } 91 | 92 | @nogc @trusted pure nothrow 93 | private size_t computeHash(K)(ref K key) if (!isHashIdentical!K) { 94 | // Cast so we can keep our function qualifiers. 95 | return (cast(SafeGetHashType) &(typeid(K).getHash))(&key); 96 | } 97 | 98 | @nogc @trusted pure nothrow 99 | private size_t castHash(K)(K key) if (isHashIdentical!K) { 100 | return cast(size_t) key; 101 | } 102 | 103 | private enum size_t minimumBucketListSize = 8; 104 | 105 | @nogc @safe pure nothrow 106 | private size_t newBucketListSize(size_t currentLength) { 107 | return currentLength * 2; 108 | } 109 | 110 | @trusted 111 | private size_t bucketListSearch(SearchFor searchFor, K, V) 112 | (ref const(Entry!(K, V)[]) bucketList, const(K) key) 113 | if (isHashIdentical!K) { 114 | size_t index = castHash(key) & (bucketList.length - 1); 115 | 116 | foreach(j; 1 .. bucketList.length) { 117 | static if (searchFor == SearchFor.notOccupied) { 118 | if (bucketList[index]._state != EntryState.occupied) { 119 | return index; 120 | } 121 | 122 | if (bucketList[index]._key == key) { 123 | return index; 124 | } 125 | } else { 126 | static if (searchFor & SearchFor.empty) { 127 | if (bucketList[index]._state == EntryState.empty) { 128 | return index; 129 | } 130 | } 131 | 132 | static if (searchFor & SearchFor.deleted) { 133 | if (bucketList[index]._state == EntryState.deleted 134 | && bucketList[index]._key == key) { 135 | return index; 136 | } 137 | } 138 | 139 | static if (searchFor & SearchFor.occupied) { 140 | if (bucketList[index]._state == EntryState.occupied 141 | && bucketList[index]._key == key) { 142 | return index; 143 | } 144 | } 145 | } 146 | 147 | index = (index + j) & (bucketList.length - 1); 148 | } 149 | 150 | assert(false, "Slot not found!"); 151 | } 152 | 153 | @trusted 154 | private size_t bucketListSearch(SearchFor searchFor, K, V) 155 | (ref const(Entry!(K, V)[]) bucketList, size_t hash, const(K) key) 156 | if (!isHashIdentical!K) { 157 | size_t index = hash & (bucketList.length - 1); 158 | 159 | foreach(j; 1 .. bucketList.length) { 160 | static if (searchFor == SearchFor.notOccupied) { 161 | if (bucketList[index]._state != EntryState.occupied) { 162 | return index; 163 | } 164 | 165 | if (bucketList[index]._hash == hash 166 | && bucketList[index]._key == key) { 167 | return index; 168 | } 169 | } else { 170 | static if (searchFor & SearchFor.empty) { 171 | if (bucketList[index]._state == EntryState.empty) { 172 | return index; 173 | } 174 | } 175 | 176 | static if (searchFor & SearchFor.deleted) { 177 | if (bucketList[index]._state == EntryState.deleted 178 | && bucketList[index]._hash == hash 179 | && bucketList[index]._key == key) { 180 | return index; 181 | } 182 | 183 | } 184 | 185 | static if (searchFor & SearchFor.occupied) { 186 | if (bucketList[index]._state == EntryState.occupied 187 | && bucketList[index]._hash == hash 188 | && bucketList[index]._key == key) { 189 | return index; 190 | } 191 | } 192 | } 193 | 194 | index = (index + j) & (bucketList.length - 1); 195 | } 196 | 197 | assert(false, "Slot not found!"); 198 | } 199 | 200 | // Add an entry into the bucket list. 201 | // memcpy is used here because some types have immutable members which cannot 202 | // be changed, so we have to force them into the array this way. 203 | @nogc @trusted pure nothrow 204 | private void setEntry(K, V)(ref Entry!(K, V)[] bucketList, 205 | size_t index, auto ref K key, auto ref V value) { 206 | enum valueOffset = alignedSize(K.sizeof); 207 | 208 | // Copy the key and value into the entry. 209 | memcpy(cast(void*) &bucketList[index], &key, K.sizeof); 210 | memcpy(cast(void*) &bucketList[index] + valueOffset, &value, V.sizeof); 211 | 212 | bucketList[index]._state = EntryState.occupied; 213 | } 214 | 215 | @nogc @trusted pure nothrow 216 | private void setEntry(K, V)(ref Entry!(K, V)[] bucketList, 217 | size_t index, size_t hash, auto ref K key, auto ref V value) { 218 | enum valueOffset = alignedSize(K.sizeof); 219 | 220 | // Copy the key and value into the entry. 221 | memcpy(cast(void*) &bucketList[index], &key, K.sizeof); 222 | memcpy(cast(void*) &bucketList[index] + valueOffset, &value, V.sizeof); 223 | 224 | bucketList[index]._hash = hash; 225 | bucketList[index]._state = EntryState.occupied; 226 | } 227 | 228 | // Update just the value for an entry. 229 | @nogc @trusted pure nothrow 230 | private void updateEntryValue(K, V)(ref Entry!(K, V)[] bucketList, 231 | size_t index, auto ref V value) { 232 | enum valueOffset = alignedSize(K.sizeof); 233 | 234 | memcpy(cast(void*) &bucketList[index] + valueOffset, &value, V.sizeof); 235 | } 236 | 237 | @nogc @trusted pure nothrow 238 | private void zeroEntryValue(K, V)(ref Entry!(K, V)[] bucketList, 239 | size_t index) { 240 | enum valueOffset = alignedSize(K.sizeof); 241 | 242 | memset(cast(void*) &bucketList[index] + valueOffset, 0, V.sizeof); 243 | } 244 | 245 | @nogc @trusted pure nothrow 246 | private bool thresholdPassed(size_t length, size_t bucketCount) { 247 | return length * 2 >= bucketCount; 248 | } 249 | 250 | /** 251 | * This struct implements a hashmap type, much like the standard associative 252 | * array type. 253 | * 254 | * This map should be almost totally usable in @safe pure nothrow functions. 255 | * 256 | * An empty map will be a valid object, and will not result in any allocations. 257 | */ 258 | struct HashMap(K, V) 259 | if(isAssignmentCopyable!(Unqual!K) && isAssignmentCopyable!(Unqual!V)) { 260 | alias ThisType = typeof(this); 261 | 262 | private Entry!(K, V)[] _bucketList; 263 | private size_t _length; 264 | 265 | /** 266 | * Construct a hashmap reserving a minimum of :minimumSize: space 267 | * for the bucket list. The actual space allocated may be some number 268 | * larger than the requested size, but it will be enough to fit 269 | * as many items as requested without another allocation. 270 | * 271 | * Params: 272 | * minimumSize = The minimum size for the hashmap. 273 | */ 274 | @safe pure nothrow 275 | this(size_t minimumSize) { 276 | if (minimumSize == 0) { 277 | // 0 is a special case. 278 | return; 279 | } 280 | 281 | if (minimumSize <= minimumBucketListSize / 2) { 282 | _bucketList = new Entry!(K, V)[](minimumBucketListSize); 283 | } else { 284 | // Find the next largest power of two which will fit this size. 285 | size_t size = 8; 286 | 287 | while (thresholdPassed(minimumSize, size)) { 288 | size *= 2; 289 | } 290 | 291 | _bucketList = new Entry!(K, V)[](newBucketListSize(size)); 292 | } 293 | } 294 | 295 | @trusted 296 | private void copyToBucketList(ref Entry!(K, V)[] newBucketList) const { 297 | foreach(ref entry; _bucketList) { 298 | if (entry._state != EntryState.occupied) { 299 | // Skip holes in the container. 300 | continue; 301 | } 302 | 303 | static if (isHashIdentical!K) { 304 | size_t index = 305 | bucketListSearch!(SearchFor.empty, K, V) 306 | (newBucketList, cast(K) entry._key); 307 | 308 | newBucketList.setEntry( 309 | index, 310 | cast(K) entry._key, 311 | cast(V) entry._value 312 | ); 313 | } else { 314 | size_t index = 315 | bucketListSearch!(SearchFor.empty, K, V) 316 | (newBucketList, entry._hash, cast(K) entry._key); 317 | 318 | newBucketList.setEntry( 319 | index, 320 | entry._hash, 321 | cast(K) entry._key, 322 | cast(V) entry._value 323 | ); 324 | } 325 | } 326 | } 327 | 328 | @trusted 329 | private void resize(size_t newBucketListLength) in { 330 | assert(newBucketListLength > _bucketList.length); 331 | } body { 332 | auto newBucketList = new Entry!(K, V)[](newBucketListLength); 333 | 334 | copyToBucketList(newBucketList); 335 | 336 | _bucketList = newBucketList; 337 | } 338 | 339 | /** 340 | * Set a value in the map. 341 | * 342 | * Params: 343 | * key = The key in the map. 344 | * value = A value to set in the map. 345 | */ 346 | void opIndexAssign(V value, K key) { 347 | static if (!isHashIdentical!K) { 348 | size_t hash = computeHash(key); 349 | } 350 | 351 | if (_bucketList.length == 0) { 352 | // 0 length is a special case. 353 | _length = 1; 354 | resize(minimumBucketListSize); 355 | 356 | static if (isHashIdentical!K) { 357 | size_t index = castHash(key) & (_bucketList.length - 1); 358 | 359 | _bucketList.setEntry(index, key, value); 360 | } else { 361 | size_t index = hash & (_bucketList.length - 1); 362 | 363 | _bucketList.setEntry(index, hash, key, value); 364 | } 365 | 366 | return; 367 | } 368 | 369 | static if (isHashIdentical!K) { 370 | size_t index = 371 | bucketListSearch!(SearchFor.notDeleted, K, V) 372 | (_bucketList, key); 373 | } else { 374 | size_t index = 375 | bucketListSearch!(SearchFor.notDeleted, K, V) 376 | (_bucketList, hash, key); 377 | } 378 | 379 | if (_bucketList[index]._state != EntryState.occupied) { 380 | // This slot is not occupied, so insert the entry here. 381 | static if (isHashIdentical!K) { 382 | _bucketList.setEntry(index, key, value); 383 | } else { 384 | _bucketList.setEntry(index, hash, key, value); 385 | } 386 | 387 | ++_length; 388 | 389 | if (thresholdPassed(_length, _bucketList.length)) { 390 | // Resize the bucketList, as it passed the threshold. 391 | resize(newBucketListSize(_bucketList.length)); 392 | } 393 | } else { 394 | // We have this key already, so update the value. 395 | _bucketList.updateEntryValue(index, value); 396 | } 397 | } 398 | 399 | /** 400 | * Implement the 'in' operator for a map. 401 | * 402 | * The in operator on a map will return a pointer to a value, which will 403 | * be null when no corresponding value is set for a given key. 404 | * 405 | * Params: 406 | * key = The key in the map. 407 | * 408 | * Returns: 409 | * A pointer to a value, a null pointer if a value is not set. 410 | */ 411 | inout(V)* opBinaryRight(string op)(K key) inout if (op == "in") { 412 | if (_bucketList.length == 0) 413 | return null; 414 | 415 | static if (isHashIdentical!K) { 416 | size_t index = 417 | bucketListSearch!(SearchFor.notDeleted, K, V) 418 | (_bucketList, key); 419 | } else { 420 | size_t index = 421 | bucketListSearch!(SearchFor.notDeleted, K, V) 422 | (_bucketList, computeHash(key), key); 423 | } 424 | 425 | if (_bucketList[index]._state == EntryState.empty) { 426 | return null; 427 | } 428 | 429 | return &(_bucketList[index]._value); 430 | } 431 | 432 | /** 433 | * Retrieve a value from the map. 434 | * 435 | * If a value is not set for the given key, a RangeError will be thrown. 436 | * 437 | * Params: 438 | * key = The key in the map. 439 | * 440 | * Returns: 441 | * A value from the map. 442 | */ 443 | ref inout(V) opIndex(K key) inout { 444 | static if (isHashIdentical!K) { 445 | size_t index = 446 | bucketListSearch!(SearchFor.notDeleted, K, V) 447 | (_bucketList, key); 448 | } else { 449 | size_t index = 450 | bucketListSearch!(SearchFor.notDeleted, K, V) 451 | (_bucketList, computeHash(key), key); 452 | } 453 | 454 | assert( 455 | _bucketList[index]._state != EntryState.empty, 456 | "Key not found in HashMap!" 457 | ); 458 | 459 | return _bucketList[index]._value; 460 | } 461 | 462 | /** 463 | * Get a value from the map, or return the given default value, which 464 | * is lazy-evaluated. 465 | * 466 | * Params: 467 | * key = The key in the map. 468 | * def = A lazy default value. 469 | * 470 | * Returns: 471 | * A value from the map, or the default value. 472 | */ 473 | V get(V2)(K key, lazy V2 def) const if(is(V2 : V)) { 474 | if (_bucketList.length == 0) 475 | return def; 476 | 477 | static if (isHashIdentical!K) { 478 | size_t index = 479 | bucketListSearch!(SearchFor.notDeleted, K, V) 480 | (_bucketList, key); 481 | } else { 482 | size_t index = 483 | bucketListSearch!(SearchFor.notDeleted, K, V) 484 | (_bucketList, computeHash(key), key); 485 | } 486 | 487 | if (_bucketList[index]._state == EntryState.empty) { 488 | return def(); 489 | } 490 | 491 | return _bucketList[index]._value; 492 | } 493 | 494 | /** 495 | * Get a value from the map, or return V.init if a value is not set for 496 | * a given key. 497 | * 498 | * Params: 499 | * key = The key in the map. 500 | * 501 | * Returns: 502 | * A value from the map, or the default value. 503 | */ 504 | inout(V) get(K key) inout { 505 | static if (isHashIdentical!K) { 506 | size_t index = 507 | bucketListSearch!(SearchFor.notDeleted, K, V) 508 | (_bucketList, key); 509 | } else { 510 | size_t index = 511 | bucketListSearch!(SearchFor.notDeleted, K, V) 512 | (_bucketList, computeHash(key), key); 513 | } 514 | 515 | 516 | if (_bucketList[index]._state == EntryState.empty) { 517 | return V.init; 518 | } 519 | 520 | return _bucketList[index]._value; 521 | } 522 | 523 | /** 524 | * Get or create a value from/in the map. 525 | * 526 | * Given a key, and a lazy evaluated default value, 527 | * attempt to retrieve a value from the map. If a value for the given 528 | * key is not set, set the provided default value in the map and 529 | * return that. 530 | * 531 | * The value will be returned by reference. 532 | * 533 | * Params: 534 | * key = The key in the map. 535 | * def = A lazy default value. 536 | * 537 | * Returns: 538 | * A reference to the value in the map. 539 | */ 540 | ref V setDefault(V2)(K key, lazy V2 value) if (is(V2 : V)) { 541 | static if (!isHashIdentical!K) { 542 | size_t hash = computeHash(key); 543 | } 544 | 545 | if (_bucketList.length == 0) { 546 | // 0 length is a special case. 547 | _length = 1; 548 | resize(minimumBucketListSize); 549 | 550 | static if (isHashIdentical!K) { 551 | size_t index = castHash(key) & (_bucketList.length - 1); 552 | 553 | _bucketList.setEntry(index, key, V.init); 554 | } else { 555 | size_t index = hash & (_bucketList.length - 1); 556 | 557 | _bucketList.setEntry(index, hash, key, V.init); 558 | } 559 | 560 | return _bucketList[index].value; 561 | } 562 | 563 | static if (isHashIdentical!K) { 564 | size_t index = 565 | bucketListSearch!(SearchFor.notDeleted, K, V) 566 | (_bucketList, key); 567 | } else { 568 | size_t index = 569 | bucketListSearch!(SearchFor.notDeleted, K, V) 570 | (_bucketList, hash, key); 571 | } 572 | 573 | if (_bucketList[index]._state == EntryState.empty) { 574 | // The entry is empty, so we can insert the value here. 575 | static if (isHashIdentical!K) { 576 | _bucketList.setEntry(index, key, value()); 577 | } else { 578 | _bucketList.setEntry(index, hash, key, value()); 579 | } 580 | 581 | ++_length; 582 | 583 | if (thresholdPassed(_length, _bucketList.length)) { 584 | // Resize the bucketList, as it passed the threshold. 585 | resize(newBucketListSize(_bucketList.length)); 586 | 587 | // Update the index, it has now changed. 588 | static if (isHashIdentical!K) { 589 | index = bucketListSearch!(SearchFor.notDeleted, K, V) 590 | (_bucketList, key); 591 | } else { 592 | index = bucketListSearch!(SearchFor.notDeleted, K, V) 593 | (_bucketList, hash, key); 594 | } 595 | } 596 | } 597 | 598 | // Return a reference to the value. 599 | return _bucketList[index]._value; 600 | } 601 | 602 | /** 603 | * Get or create a value from/in a hashmap. 604 | * 605 | * Given a key attempt to retrieve a value from the hashmap. 606 | * If a value for the given key is not set, set the value in 607 | * the associative array to the default value for the value's type. 608 | * 609 | * The value will be returned by reference. 610 | * 611 | * Params: 612 | * key = The key in the map. 613 | * 614 | * Returns: 615 | * A reference to the value in the map. 616 | */ 617 | ref V setDefault(K key) { 618 | static if (!isHashIdentical!K) { 619 | size_t hash = computeHash(key); 620 | } 621 | 622 | if (_bucketList.length == 0) { 623 | // 0 length is a special case. 624 | _length = 1; 625 | resize(minimumBucketListSize); 626 | 627 | static if (isHashIdentical!K) { 628 | size_t index = castHash(key) & (_bucketList.length - 1); 629 | 630 | _bucketList.setEntry(index, key, V.init); 631 | } else { 632 | size_t index = hash & (_bucketList.length - 1); 633 | 634 | _bucketList.setEntry(index, hash, key, V.init); 635 | } 636 | 637 | return _bucketList[index].value; 638 | } 639 | 640 | static if (isHashIdentical!K) { 641 | size_t index = 642 | bucketListSearch!(SearchFor.notDeleted, K, V) 643 | (_bucketList, key); 644 | } else { 645 | size_t index = 646 | bucketListSearch!(SearchFor.notDeleted, K, V) 647 | (_bucketList, hash, key); 648 | } 649 | 650 | if (_bucketList[index]._state == EntryState.empty) { 651 | // The entry is empty, so we can insert the value here. 652 | static if (isHashIdentical!K) { 653 | _bucketList.setEntry(index, key, V.init); 654 | } else { 655 | _bucketList.setEntry(index, hash, key, V.init); 656 | } 657 | 658 | ++_length; 659 | 660 | if (thresholdPassed(_length, _bucketList.length)) { 661 | // Resize the bucketList, as it passed the threshold. 662 | resize(newBucketListSize(_bucketList.length)); 663 | 664 | // Update the index, it has now changed. 665 | static if (isHashIdentical!K) { 666 | index = bucketListSearch!(SearchFor.notDeleted, K, V) 667 | (_bucketList, key); 668 | } else { 669 | index = bucketListSearch!(SearchFor.notDeleted, K, V) 670 | (_bucketList, hash, key); 671 | } 672 | } 673 | } 674 | 675 | // Return a reference to the value. 676 | return _bucketList[index]._value; 677 | } 678 | 679 | /** 680 | * Remove a entry from the map if it is set, given a key. 681 | * 682 | * Params: 683 | * key = The key in the map. 684 | * 685 | * Returns: 686 | * true if a value was removed, otherwise false. 687 | */ 688 | bool remove(K key) { 689 | if (_bucketList.length == 0) 690 | return false; 691 | 692 | static if (isHashIdentical!K) { 693 | size_t index = 694 | bucketListSearch!(SearchFor.any, K, V) 695 | (_bucketList, key); 696 | } else { 697 | size_t index = 698 | bucketListSearch!(SearchFor.any, K, V) 699 | (_bucketList, computeHash(key), key); 700 | } 701 | 702 | if (_bucketList[index]._state == EntryState.occupied) { 703 | --_length; 704 | 705 | // Zero the value and mark the slot as 'deleted', which is 706 | // treated often the same as 'empty', only we can skip over 707 | // deleted values to search for more values. 708 | _bucketList.zeroEntryValue(index); 709 | _bucketList[index]._state = EntryState.deleted; 710 | 711 | return true; 712 | } 713 | 714 | return false; 715 | } 716 | 717 | /** 718 | * The length of the map. 719 | * 720 | * Returns: The number of entries in the map, in constant time. 721 | */ 722 | @nogc @safe pure nothrow 723 | @property size_t length() const { 724 | return _length; 725 | } 726 | 727 | /** 728 | * Returns: True if this map is empty. 729 | */ 730 | @nogc @safe pure nothrow 731 | @property bool empty() const { 732 | return _length == 0; 733 | } 734 | 735 | /** 736 | * Implement boolean conversion for a map. 737 | * 738 | * Returns: True if this set is not empty. 739 | */ 740 | @nogc @safe pure nothrow 741 | bool opCast(T: bool)() const { 742 | return !empty; 743 | } 744 | 745 | static if(isDupable!K && isDupable!V) { 746 | /** 747 | * Copy an existing map into a new mutable map. 748 | * 749 | * Returns: 750 | * The fresh copy of the map. 751 | */ 752 | @safe pure nothrow 753 | HashMap!(K, V) dup() const { 754 | if (_length == 0) { 755 | // Just return nothing special for length 0. 756 | return HashMap!(K, V).init; 757 | } 758 | 759 | // Create a new map large enough to fit all of our values. 760 | auto newMap = HashMap!(K, V)(_length); 761 | newMap._length = _length; 762 | 763 | copyToBucketList(newMap._bucketList); 764 | 765 | return newMap; 766 | } 767 | } 768 | 769 | 770 | /** 771 | * Test if two maps are equal. 772 | * 773 | * Params: 774 | * otherMap = Another map. 775 | * 776 | * Returns: 777 | * true only if the maps are equal in length, keys, and values. 778 | */ 779 | bool opEquals(ref const(HashMap!(K, V)) otherMap) const { 780 | if (_length != otherMap._length) { 781 | return false; 782 | } 783 | 784 | foreach(ref entry; _bucketList) { 785 | if (entry._state != EntryState.occupied) { 786 | // Skip holes in the container. 787 | continue; 788 | } 789 | 790 | static if (isHashIdentical!K) { 791 | size_t index = 792 | bucketListSearch!(SearchFor.notDeleted, K, V) 793 | (otherMap._bucketList, entry._key); 794 | } else { 795 | size_t index = 796 | bucketListSearch!(SearchFor.notDeleted, K, V) 797 | (otherMap._bucketList, entry._hash, entry._key); 798 | } 799 | 800 | if (otherMap._bucketList[index]._state == EntryState.empty) { 801 | return false; 802 | } 803 | } 804 | 805 | return true; 806 | } 807 | 808 | /// ditto 809 | bool opEquals(const(HashMap!(K, V)) otherMap) const { 810 | return opEquals(otherMap); 811 | } 812 | } 813 | 814 | template HashMapKeyType(T) { 815 | alias HashMapKeyType = typeof(ElementType!(typeof(T._bucketList))._key); 816 | } 817 | 818 | template HashMapValueType(T) { 819 | alias HashMapValueType = typeof(ElementType!(typeof(T._bucketList))._value); 820 | } 821 | 822 | // Test that is is not possible to create a map with a key or a value type 823 | // which cannot be copy-assigned. 824 | unittest { 825 | struct NonCopyable { @disable this(this); } 826 | 827 | assert(!__traits(compiles, HashMap!(NonCopyable, int))); 828 | assert(__traits(compiles, HashMap!(NonCopyable*, int))); 829 | assert(!__traits(compiles, HashMap!(int, NonCopyable))); 830 | assert(__traits(compiles, HashMap!(int, NonCopyable*))); 831 | } 832 | 833 | // Check setting values, retrieval, removal, and lengths. 834 | unittest { 835 | HashMap!(int, string) map; 836 | 837 | map[1] = "a"; 838 | map[2] = "b"; 839 | map[3] = "c"; 840 | 841 | map[1] = "x"; 842 | map[2] = "y"; 843 | map[3] = "z"; 844 | 845 | assert(map.length == 3); 846 | 847 | assert(map[1] == "x"); 848 | assert(map[2] == "y"); 849 | assert(map[3] == "z"); 850 | 851 | assert(map.remove(3)); 852 | assert(map.remove(2)); 853 | assert(map.remove(1)); 854 | 855 | assert(!map.remove(1)); 856 | 857 | assert(map.length == 0); 858 | } 859 | 860 | unittest { 861 | HashMap!(int, string) map; 862 | 863 | map[1] = "a"; 864 | map[2] = "b"; 865 | map[3] = "c"; 866 | map[4] = "d"; 867 | map[5] = "e"; 868 | map[6] = "f"; 869 | map[7] = "g"; 870 | map[8] = "h"; 871 | map[9] = "i"; 872 | 873 | map[1] = "x"; 874 | map[2] = "y"; 875 | map[3] = "z"; 876 | 877 | assert(map.length == 9); 878 | 879 | assert(map[1] == "x"); 880 | assert(map[2] == "y"); 881 | assert(map[3] == "z"); 882 | assert(map[4] == "d"); 883 | assert(map[5] == "e"); 884 | assert(map[6] == "f"); 885 | assert(map[7] == "g"); 886 | assert(map[8] == "h"); 887 | assert(map[9] == "i"); 888 | 889 | assert(map.remove(3)); 890 | assert(map.remove(2)); 891 | assert(map.remove(1)); 892 | 893 | assert(!map.remove(1)); 894 | 895 | assert(map.length == 6); 896 | } 897 | 898 | // Test the map with heavy collisions. 899 | unittest { 900 | struct BadHashObject { 901 | int value; 902 | 903 | this(int value) { 904 | this.value = value; 905 | } 906 | 907 | @safe nothrow 908 | size_t toHash() const { 909 | return 0; 910 | } 911 | 912 | @nogc @safe nothrow pure 913 | bool opEquals(ref const BadHashObject other) const { 914 | return value == other.value; 915 | } 916 | } 917 | 918 | HashMap!(BadHashObject, string) map; 919 | enum size_t mapSize = 100; 920 | 921 | foreach(num; 0 .. mapSize) { 922 | map[BadHashObject(cast(int) num)] = "a"; 923 | } 924 | 925 | assert(map.length == mapSize); 926 | } 927 | 928 | // Test preallocated maps; 929 | unittest { 930 | auto map = HashMap!(int, string)(3); 931 | 932 | assert(map._bucketList.length == minimumBucketListSize); 933 | } 934 | 935 | // Test the 'in' operator. 936 | unittest { 937 | // We'll test that our attributes work. 938 | @safe pure nothrow 939 | void runTest() { 940 | HashMap!(int, string) map; 941 | 942 | map[1] = "a"; 943 | map[2] = "b"; 944 | map[3] = "c"; 945 | 946 | // Test that @nogc works. 947 | @nogc @safe pure nothrow 948 | void runNoGCPart(typeof(map) map) { 949 | assert((4 in map) is null); 950 | 951 | assert(*(1 in map) == "a"); 952 | assert(*(2 in map) == "b"); 953 | assert(*(3 in map) == "c"); 954 | } 955 | 956 | runNoGCPart(map); 957 | } 958 | 959 | runTest(); 960 | } 961 | 962 | // Test the map with a weird type which makes assignment harder. 963 | unittest { 964 | struct WeirdType { 965 | // The alignment could cause memory issues so we'll check for that. 966 | align(1): 967 | // This immutable member means we need to use memset above to set 968 | // keys or values. 969 | immutable(byte)* foo = null; 970 | size_t x = 3; 971 | 972 | @nogc @safe pure nothrow 973 | this(int value) { 974 | x = value; 975 | } 976 | 977 | @nogc @safe pure nothrow 978 | size_t toHash() const { 979 | return x; 980 | } 981 | 982 | @nogc @safe pure nothrow 983 | bool opEquals(ref const(WeirdType) other) const { 984 | return foo == other.foo && x == other.x; 985 | } 986 | 987 | @nogc @safe pure nothrow 988 | bool opEquals(const(WeirdType) other) const { 989 | return opEquals(other); 990 | } 991 | } 992 | 993 | @safe pure nothrow 994 | void runTest() { 995 | HashMap!(WeirdType, string) map; 996 | 997 | map[WeirdType(10)] = "a"; 998 | 999 | @nogc @safe pure nothrow 1000 | void runNoGCPart(typeof(map) map) { 1001 | assert(map[WeirdType(10)] == "a"); 1002 | } 1003 | 1004 | runNoGCPart(map); 1005 | } 1006 | 1007 | runTest(); 1008 | } 1009 | 1010 | // Test get with default init 1011 | unittest { 1012 | @safe pure nothrow 1013 | void runTest() { 1014 | HashMap!(int, string) map; 1015 | 1016 | map[1] = "a"; 1017 | 1018 | @nogc @safe pure nothrow 1019 | void runNoGCPart(typeof(map) map) { 1020 | assert(map.get(1) == "a"); 1021 | assert(map.get(2) is null); 1022 | } 1023 | runNoGCPart(map); 1024 | } 1025 | 1026 | runTest(); 1027 | 1028 | } 1029 | 1030 | // Test length, empty, and cast(bool) 1031 | unittest { 1032 | @safe pure nothrow 1033 | void runTest() { 1034 | HashMap!(int, string) map; 1035 | 1036 | @nogc @safe pure nothrow 1037 | void runNoGCPart1(typeof(map) map) { 1038 | assert(map.length == 0); 1039 | assert(map.empty); 1040 | 1041 | if (map) { 1042 | assert(false, "cast(bool) failed for an empty map"); 1043 | } 1044 | } 1045 | 1046 | @nogc @safe pure nothrow 1047 | void runNoGCPart2(typeof(map) map) { 1048 | assert(map.length == 1); 1049 | assert(!map.empty); 1050 | 1051 | if (!map) { 1052 | assert(false, "cast(bool) failed for an non-empty map"); 1053 | } 1054 | } 1055 | 1056 | @nogc @safe pure nothrow 1057 | void runNoGCPart3(typeof(map) map) { 1058 | assert(map.length == 0); 1059 | assert(map.empty); 1060 | 1061 | if (map) { 1062 | assert(false, "cast(bool) failed for an empty map"); 1063 | } 1064 | } 1065 | 1066 | runNoGCPart1(map); 1067 | map[1] = "a"; 1068 | runNoGCPart2(map); 1069 | map.remove(1); 1070 | runNoGCPart3(map); 1071 | } 1072 | 1073 | runTest(); 1074 | } 1075 | 1076 | // BUG: The lazy argument here cannot be made to be nothrow, @nogc, etc. 1077 | // Test get with a given default. 1078 | unittest { 1079 | HashMap!(int, string) map; 1080 | 1081 | map[1] = "a"; 1082 | 1083 | assert(map.get(1, "b") == "a"); 1084 | assert(map.get(2, "b") == "b"); 1085 | } 1086 | 1087 | // Test opEquals 1088 | unittest { 1089 | @safe pure nothrow 1090 | void runTest() { 1091 | HashMap!(string, string) leftMap; 1092 | HashMap!(string, string) rightMap; 1093 | 1094 | // Give the left one a bit more, and take away from it. 1095 | leftMap["a"] = "1"; 1096 | leftMap["b"] = "2"; 1097 | leftMap["c"] = "3"; 1098 | leftMap["d"] = "4"; 1099 | leftMap["e"] = "5"; 1100 | leftMap["f"] = "6"; 1101 | leftMap["g"] = "7"; 1102 | leftMap["h"] = "8"; 1103 | leftMap["i"] = "9"; 1104 | leftMap["j"] = "10"; 1105 | 1106 | rightMap["a"] = "1"; 1107 | rightMap["b"] = "2"; 1108 | rightMap["c"] = "3"; 1109 | 1110 | @nogc @safe pure nothrow 1111 | void runNoGCPart(typeof(leftMap) leftMap, typeof(rightMap) rightMap) { 1112 | // Remove the extra keys 1113 | leftMap.remove("d"); 1114 | leftMap.remove("e"); 1115 | leftMap.remove("f"); 1116 | leftMap.remove("g"); 1117 | leftMap.remove("h"); 1118 | leftMap.remove("i"); 1119 | leftMap.remove("j"); 1120 | 1121 | // Now the two maps should have different bucketLists, but they 1122 | // should still be considered equal. 1123 | assert(leftMap == rightMap); 1124 | } 1125 | 1126 | runNoGCPart(leftMap, rightMap); 1127 | } 1128 | 1129 | runTest(); 1130 | } 1131 | 1132 | // Test setDefault with default init 1133 | unittest { 1134 | @safe pure nothrow 1135 | void runTest() { 1136 | HashMap!(int, string) map; 1137 | 1138 | map[1] = "a"; 1139 | 1140 | // setDefault for basic types with no explicit default ought to 1141 | // be nothrow. 1142 | assert(map.setDefault(1) == "a"); 1143 | assert(map.setDefault(2) is null); 1144 | 1145 | assert(map.length == 2); 1146 | 1147 | assert(map[2] is null); 1148 | } 1149 | 1150 | runTest(); 1151 | } 1152 | 1153 | // Test setDefault with a given value. 1154 | unittest { 1155 | HashMap!(int, string) map; 1156 | 1157 | map[1] = "a"; 1158 | 1159 | assert(map.setDefault(1, "b") == "a"); 1160 | assert(map.setDefault(2, "b") == "b"); 1161 | 1162 | assert(map.length == 2); 1163 | 1164 | assert(map[2] == "b"); 1165 | } 1166 | 1167 | // Test setDefault with a given value which can be implicitly converted. 1168 | unittest { 1169 | HashMap!(int, long) map; 1170 | 1171 | map[1] = 2; 1172 | 1173 | assert(map.setDefault(1, 3) == 2); 1174 | 1175 | int x = 4; 1176 | 1177 | assert(map.setDefault(2, x) == 4); 1178 | 1179 | assert(map.length == 2); 1180 | 1181 | assert(map[2] == 4); 1182 | } 1183 | 1184 | // Test operations work with empty map (issue #3). 1185 | unittest { 1186 | HashMap!(int, string) emptyMap; 1187 | assert(0 !in emptyMap); 1188 | assert(!emptyMap.remove(0)); 1189 | assert(emptyMap.get(0, "a") == "a"); 1190 | } 1191 | 1192 | /** 1193 | * A range through a series of items in the map. 1194 | */ 1195 | struct KeyValueRange(K, V) { 1196 | private: 1197 | Entry!(K, V)[] _bucketList = null; 1198 | public: 1199 | @nogc @safe pure nothrow 1200 | this(Entry!(K, V)[] bucketList) { 1201 | foreach(index, ref entry; bucketList) { 1202 | if (entry._state == EntryState.occupied) { 1203 | // Use a slice of the bucketList starting here. 1204 | _bucketList = bucketList[index .. $]; 1205 | 1206 | return; 1207 | } 1208 | } 1209 | } 1210 | 1211 | @nogc @trusted pure nothrow 1212 | this(const(Entry!(K, V)[]) bucketList) { 1213 | this(cast(Entry!(K, V)[]) bucketList); 1214 | } 1215 | 1216 | @nogc @safe pure nothrow 1217 | inout(typeof(this)) save() inout { 1218 | return this; 1219 | } 1220 | 1221 | @nogc @safe pure nothrow 1222 | @property 1223 | bool empty() const { 1224 | // We can check that the bucketList is empty to check if this range is 1225 | // empty, because we will clear it after we pop the last item. 1226 | return _bucketList.length == 0; 1227 | } 1228 | 1229 | @nogc @safe pure nothrow 1230 | @property 1231 | ref inout(Entry!(K, V)) front() inout in { 1232 | assert(!empty()); 1233 | } body { 1234 | return _bucketList[0]; 1235 | } 1236 | 1237 | @nogc @safe pure nothrow 1238 | void popFront() in { 1239 | assert(!empty()); 1240 | } body { 1241 | foreach(index; 1 .. _bucketList.length) { 1242 | if (_bucketList[index]._state == EntryState.occupied) { 1243 | // Use a slice of the bucketList starting here. 1244 | _bucketList = _bucketList[index .. $]; 1245 | 1246 | return; 1247 | } 1248 | } 1249 | 1250 | // Clear the bucketList if we hit the end. 1251 | _bucketList = null; 1252 | } 1253 | } 1254 | 1255 | /** 1256 | * Produce a range through the items of a map. (A key-value pair) 1257 | * 1258 | * Params: 1259 | * map = A map. 1260 | * Returns: 1261 | * A range running through the items in the map. 1262 | */ 1263 | @nogc @safe pure nothrow 1264 | auto byKeyValue(K, V)(auto ref HashMap!(K, V) map) { 1265 | if (map.length == 0) { 1266 | // Empty ranges should not have to traverse the bucketList at all. 1267 | return KeyValueRange!(K, V).init; 1268 | } 1269 | 1270 | return KeyValueRange!(K, V)(map._bucketList); 1271 | } 1272 | 1273 | /// ditto 1274 | @nogc @trusted pure nothrow 1275 | auto byKeyValue(K, V)(auto ref const(HashMap!(K, V)) map) { 1276 | alias RealK = HashMapKeyType!(typeof(map)); 1277 | alias RealV = HashMapValueType!(typeof(map)); 1278 | 1279 | if (map.length == 0) { 1280 | return KeyValueRange!(RealK, RealV).init; 1281 | } 1282 | 1283 | return KeyValueRange!(RealK, RealV)( 1284 | cast(Entry!(RealK, RealV)[]) 1285 | map._bucketList 1286 | ); 1287 | } 1288 | 1289 | /// ditto 1290 | @nogc @trusted pure nothrow 1291 | auto byKeyValue(K, V)(auto ref immutable(HashMap!(K, V)) map) { 1292 | alias RealK = HashMapKeyType!(typeof(map)); 1293 | alias RealV = HashMapValueType!(typeof(map)); 1294 | 1295 | if (map.length == 0) { 1296 | return KeyValueRange!(RealK, RealV).init; 1297 | } 1298 | 1299 | return KeyValueRange!(RealK, RealV)( 1300 | cast(Entry!(RealK, RealV)[]) 1301 | map._bucketList 1302 | ); 1303 | } 1304 | 1305 | unittest { 1306 | HashMap!(int, string) map; 1307 | 1308 | map[1] = "a"; 1309 | map[2] = "b"; 1310 | map[3] = "c"; 1311 | 1312 | int[] keyList; 1313 | string[] valueList; 1314 | 1315 | foreach(item; map.byKeyValue()) { 1316 | keyList ~= item.key; 1317 | valueList ~= item.value; 1318 | } 1319 | 1320 | // From the way the bucketLists are distributed, we know we'll get this back. 1321 | assert(keyList == [1, 2, 3]); 1322 | assert(valueList == ["a", "b", "c"]); 1323 | } 1324 | 1325 | unittest { 1326 | HashMap!(string, string) mmap; 1327 | const(HashMap!(string, string)) cmap; 1328 | immutable(HashMap!(string, string)) imap; 1329 | 1330 | auto mItems = mmap.byKeyValue(); 1331 | auto cItems = cmap.byKeyValue(); 1332 | auto iItems = imap.byKeyValue(); 1333 | 1334 | assert(is(typeof(mItems.front.key) == string)); 1335 | assert(is(typeof(cItems.front.key) == const(string))); 1336 | assert(is(typeof(iItems.front.key) == immutable(string))); 1337 | assert(is(typeof(mItems.front.value) == string)); 1338 | assert(is(typeof(cItems.front.value) == const(string))); 1339 | assert(is(typeof(iItems.front.value) == immutable(string))); 1340 | } 1341 | 1342 | // Test that the ranges can be created from r-values. 1343 | unittest { 1344 | auto func() { 1345 | HashMap!(int, string) map; 1346 | 1347 | map[1] = "a"; 1348 | map[2] = "b"; 1349 | map[3] = "c"; 1350 | 1351 | return map; 1352 | } 1353 | 1354 | auto keyRange = func().byKey(); 1355 | auto valueRange = func().byValue(); 1356 | auto itemRange = func().byKeyValue(); 1357 | } 1358 | 1359 | /** 1360 | * This is a range which runs through a series of keys in map. 1361 | */ 1362 | struct KeyRange(K, V) { 1363 | private: 1364 | KeyValueRange!(K, V) _keyValueRange; 1365 | public: 1366 | @nogc @safe pure nothrow 1367 | private this()(auto ref Entry!(K, V)[] bucketList) { 1368 | _keyValueRange = KeyValueRange!(K, V)(bucketList); 1369 | } 1370 | 1371 | /// 1372 | @nogc @safe pure nothrow 1373 | inout(typeof(this)) save() inout { 1374 | return this; 1375 | } 1376 | 1377 | /// 1378 | @nogc @safe pure nothrow 1379 | @property 1380 | bool empty() const { 1381 | return _keyValueRange.empty; 1382 | } 1383 | 1384 | /// 1385 | @nogc @trusted pure nothrow 1386 | @property 1387 | ref inout(K) front() inout { 1388 | return _keyValueRange.front.key; 1389 | } 1390 | 1391 | /// 1392 | @nogc @safe pure nothrow 1393 | void popFront() { 1394 | _keyValueRange.popFront(); 1395 | } 1396 | } 1397 | 1398 | /** 1399 | * Produce a range through the keys of a map. 1400 | * 1401 | * Params: 1402 | * map = A map. 1403 | * Returns: 1404 | * A range running through the keys in the map. 1405 | */ 1406 | @nogc @safe pure nothrow 1407 | auto byKey(K, V)(auto ref HashMap!(K, V) map) { 1408 | if (map.length == 0) { 1409 | return KeyRange!(K, V).init; 1410 | } 1411 | 1412 | return KeyRange!(K, V)(map._bucketList); 1413 | } 1414 | 1415 | /// ditto 1416 | @nogc @trusted pure nothrow 1417 | auto byKey(K, V)(auto ref const(HashMap!(K, V)) map) { 1418 | alias RealK = HashMapKeyType!(typeof(map)); 1419 | alias RealV = HashMapValueType!(typeof(map)); 1420 | 1421 | if (map.length == 0) { 1422 | return KeyRange!(RealK, RealV).init; 1423 | } 1424 | 1425 | return KeyRange!(RealK, RealV)( 1426 | cast(Entry!(RealK, RealV)[]) 1427 | map._bucketList 1428 | ); 1429 | } 1430 | 1431 | /// ditto 1432 | @nogc @trusted pure nothrow 1433 | auto byKey(K, V)(auto ref immutable(HashMap!(K, V)) map) { 1434 | alias RealK = HashMapKeyType!(typeof(map)); 1435 | alias RealV = HashMapValueType!(typeof(map)); 1436 | 1437 | if (map.length == 0) { 1438 | return KeyRange!(RealK, RealV).init; 1439 | } 1440 | 1441 | return KeyRange!(RealK, RealV)( 1442 | cast(Entry!(RealK, RealV)[]) 1443 | map._bucketList 1444 | ); 1445 | } 1446 | 1447 | unittest { 1448 | HashMap!(int, string) map; 1449 | 1450 | map[1] = "a"; 1451 | map[2] = "b"; 1452 | map[3] = "c"; 1453 | 1454 | int[] keyList; 1455 | 1456 | foreach(ref key; map.byKey()) { 1457 | keyList ~= key; 1458 | } 1459 | 1460 | // From the way the bucketLists are distributed, we know we'll get this back. 1461 | assert(keyList == [1, 2, 3]); 1462 | } 1463 | 1464 | unittest { 1465 | HashMap!(string, string) mmap; 1466 | const(HashMap!(string, string)) cmap; 1467 | immutable(HashMap!(string, string)) imap; 1468 | 1469 | auto mKeys = mmap.byKey(); 1470 | auto cKeys = cmap.byKey(); 1471 | auto iKeys = imap.byKey(); 1472 | 1473 | assert(is(typeof(mKeys.front) == string)); 1474 | assert(is(typeof(cKeys.front) == const(string))); 1475 | assert(is(typeof(iKeys.front) == immutable(string))); 1476 | } 1477 | 1478 | /** 1479 | * This is a range which runs through a series of values in a map. 1480 | */ 1481 | struct ValueRange(K, V) { 1482 | private: 1483 | KeyValueRange!(K, V) _keyValueRange; 1484 | public: 1485 | @nogc @safe pure nothrow 1486 | private this()(auto ref Entry!(K, V)[] bucketList) { 1487 | _keyValueRange = KeyValueRange!(K, V)(bucketList); 1488 | } 1489 | 1490 | /// 1491 | @nogc @safe pure nothrow 1492 | inout(typeof(this)) save() inout { 1493 | return this; 1494 | } 1495 | 1496 | /// 1497 | @nogc @safe pure nothrow 1498 | @property 1499 | bool empty() const { 1500 | return _keyValueRange.empty; 1501 | } 1502 | 1503 | /// 1504 | @nogc @trusted pure nothrow 1505 | @property 1506 | ref inout(V) front() inout { 1507 | return _keyValueRange.front.value; 1508 | } 1509 | 1510 | /// 1511 | @nogc @safe pure nothrow 1512 | void popFront() { 1513 | _keyValueRange.popFront(); 1514 | } 1515 | } 1516 | 1517 | /** 1518 | * Produce a range through the values of a map. 1519 | * 1520 | * Params: 1521 | * map = A map. 1522 | * Returns: 1523 | * A range running through the values in the map. 1524 | */ 1525 | @nogc @safe pure nothrow 1526 | auto byValue(K, V)(auto ref HashMap!(K, V) map) { 1527 | if (map.length == 0) { 1528 | return ValueRange!(K, V).init; 1529 | } 1530 | 1531 | return ValueRange!(K, V)(map._bucketList); 1532 | } 1533 | 1534 | /// ditto 1535 | @nogc @trusted pure nothrow 1536 | auto byValue(K, V)(auto ref const(HashMap!(K, V)) map) { 1537 | alias RealK = HashMapKeyType!(typeof(map)); 1538 | alias RealV = HashMapValueType!(typeof(map)); 1539 | 1540 | if (map.length == 0) { 1541 | return ValueRange!(RealK, RealV).init; 1542 | } 1543 | 1544 | return ValueRange!(RealK, RealV)( 1545 | cast(Entry!(RealK, RealV)[]) 1546 | map._bucketList 1547 | ); 1548 | } 1549 | 1550 | /// ditto 1551 | @nogc @trusted pure nothrow 1552 | auto byValue(K, V)(auto ref immutable(HashMap!(K, V)) map) { 1553 | alias RealK = HashMapKeyType!(typeof(map)); 1554 | alias RealV = HashMapValueType!(typeof(map)); 1555 | 1556 | if (map.length == 0) { 1557 | return ValueRange!(RealK, RealV).init; 1558 | } 1559 | 1560 | return ValueRange!(RealK, RealV)( 1561 | cast(Entry!(RealK, RealV)[]) 1562 | map._bucketList 1563 | ); 1564 | } 1565 | 1566 | unittest { 1567 | HashMap!(int, string) map; 1568 | 1569 | map[1] = "a"; 1570 | map[2] = "b"; 1571 | map[3] = "c"; 1572 | 1573 | string[] valueList = []; 1574 | 1575 | foreach(ref value; map.byValue()) { 1576 | valueList ~= value; 1577 | } 1578 | 1579 | // From the way the buckets are distributed, we know we'll get this back. 1580 | assert(valueList == ["a", "b", "c"]); 1581 | } 1582 | 1583 | unittest { 1584 | HashMap!(string, string) mmap; 1585 | const(HashMap!(string, string)) cmap; 1586 | immutable(HashMap!(string, string)) imap; 1587 | 1588 | auto mValues = mmap.byValue(); 1589 | auto cValues = cmap.byValue(); 1590 | auto iValues = imap.byValue(); 1591 | 1592 | assert(is(typeof(mValues.front) == string)); 1593 | assert(is(typeof(cValues.front) == const(string))); 1594 | assert(is(typeof(iValues.front) == immutable(string))); 1595 | } 1596 | 1597 | unittest { 1598 | const(HashMap!(int, int)) createMap() { 1599 | HashMap!(int, int) map; 1600 | 1601 | map[3] = 4; 1602 | map[4] = 7; 1603 | 1604 | return map; 1605 | } 1606 | 1607 | auto map = createMap(); 1608 | auto newMap = map.dup; 1609 | // Test r-values. 1610 | auto thirdMap = createMap().dup(); 1611 | 1612 | assert(map == newMap); 1613 | } 1614 | 1615 | unittest { 1616 | HashMap!(int, void[0]) map; 1617 | 1618 | auto x = map.dup; 1619 | } 1620 | -------------------------------------------------------------------------------- /source/dstruct/matrix.d: -------------------------------------------------------------------------------- 1 | /** 2 | * This module defines a matrix data structure and various operations 3 | * on this data structure. All operations are @safe pure nothrow, 4 | * so they can be used in any such function, and the results of any 5 | * operation can be implicitly converted to an immutable type. 6 | */ 7 | module dstruct.matrix; 8 | 9 | import std.traits : isNumeric, Unqual; 10 | 11 | // A private implementation of matrix multiplication for use in types. 12 | private auto matrixMultiply(ResultType, T, U) 13 | (ref ResultType result, ref const(T) left, ref const(U) right) { 14 | foreach(row; 0 .. result.rowCount) { 15 | foreach(column; 0 .. result.columnCount) { 16 | Unqual!(typeof(result[0, 0])) value = left[row, 0] * right[0, column]; 17 | 18 | foreach(pivot; 1 .. left.columnCount) { 19 | value += left[row, pivot] * right[pivot, column]; 20 | } 21 | 22 | result[row, column] = value; 23 | } 24 | } 25 | 26 | return result; 27 | } 28 | 29 | /** 30 | * A matrix type. This is a 2D array of a guaranteed uniform size. 31 | */ 32 | struct Matrix(Number) if(isNumeric!Number) { 33 | private: 34 | Number[] _data; 35 | size_t _rowCount; 36 | size_t _columnCount; 37 | public: 38 | /** 39 | * Create an matrix from an array of data. 40 | * 41 | * Params: 42 | * rowCount = The number of rows for the matrix. 43 | * columnCount = The number of columns for the matrix. 44 | * data = The array of data for the matrix. 45 | */ 46 | @safe pure nothrow 47 | this(size_t rowCount, size_t columnCount, immutable(Number[]) data) immutable { 48 | _data = data; 49 | _rowCount = rowCount; 50 | _columnCount = columnCount; 51 | } 52 | 53 | // Copy-paste the constructors, because inout doesn't work with literals. 54 | 55 | /// ditto 56 | @safe pure nothrow 57 | this(size_t rowCount, size_t columnCount, const(Number[]) data) const { 58 | _data = data; 59 | _rowCount = rowCount; 60 | _columnCount = columnCount; 61 | } 62 | 63 | /// ditto 64 | @safe pure nothrow 65 | this(size_t rowCount, size_t columnCount, Number[] data) { 66 | _data = data; 67 | _rowCount = rowCount; 68 | _columnCount = columnCount; 69 | } 70 | 71 | /** 72 | * Create a matrix of a given size. 73 | * 74 | * Params: 75 | * rowCount = The number of rows for the matrix. 76 | * columnCount = The number of columns for the matrix. 77 | */ 78 | @safe pure nothrow 79 | this(size_t rowCount, size_t columnCount) { 80 | if (rowCount == 0 || columnCount == 0) { 81 | return; 82 | } 83 | 84 | _data = new Number[](rowCount * columnCount); 85 | 86 | _rowCount = rowCount; 87 | _columnCount = columnCount; 88 | } 89 | 90 | /** 91 | * Returns: A new duplicate of this matrix. 92 | */ 93 | @safe pure nothrow 94 | Matrix!Number dup() const { 95 | Matrix!Number mat; 96 | 97 | // We can't .dup in a nothrow function, but we can do this... 98 | mat._data = new Number[](_rowCount * _columnCount); 99 | mat._data[] = _data[]; 100 | 101 | mat._rowCount = _rowCount; 102 | mat._columnCount = _columnCount; 103 | 104 | return mat; 105 | } 106 | 107 | /** 108 | * Returns: A new immutable duplicate of this matrix. 109 | */ 110 | @safe pure nothrow 111 | immutable(Matrix!Number) idup() const { 112 | return dup(); 113 | } 114 | 115 | /** 116 | * When calling .idup on an already immutable matrix, the reference 117 | * to the same immutable matrix is returned. It should be safe to 118 | * share the immutable memory in this manner. 119 | * 120 | * Returns: A reference to this immutable matrix. 121 | */ 122 | @safe pure nothrow 123 | immutable(Matrix!Number) idup() immutable { 124 | // There's no need to copy immutable to immutable, share it! 125 | return this; 126 | } 127 | 128 | unittest { 129 | immutable m = immutable Matrix!int(1, 1); 130 | 131 | // Make sure this doesn't actually duplicate. 132 | assert(m._data is m._data); 133 | 134 | auto o = Matrix!int(1, 1); 135 | 136 | // Make sure this still does. 137 | assert(o.idup._data !is o._data); 138 | } 139 | 140 | /// Returns: True if the matrix is empty. 141 | @safe pure nothrow 142 | @property bool empty() const { 143 | return _data.length == 0; 144 | } 145 | 146 | /// Returns: The number of rows in this matrix. 147 | @safe pure nothrow 148 | @property size_t rowCount() const { 149 | return _rowCount; 150 | } 151 | 152 | /// Returns: The number of columns in this matrix. 153 | @safe pure nothrow 154 | @property size_t columnCount() const { 155 | return _columnCount; 156 | } 157 | 158 | /// Returns: true if the matrix is a square matrix. 159 | @safe pure nothrow 160 | @property bool isSquare() const { 161 | return _rowCount == _columnCount; 162 | } 163 | 164 | /** 165 | * Slice out a row from the matrix. Modifying this 166 | * slice will modify the matrix, unless it is copied. 167 | * 168 | * Params: 169 | * row = A row index. 170 | * 171 | * Returns: A slice of the row of the matrix. 172 | */ 173 | @trusted pure nothrow 174 | inout(Number[]) opIndex(size_t row) inout 175 | in { 176 | assert(row <= rowCount, "row out of bounds!"); 177 | } body { 178 | size_t offset = row * _columnCount; 179 | 180 | return _data[offset .. offset + _columnCount]; 181 | } 182 | 183 | /** 184 | * Params: 185 | * row = A row index. 186 | * column = A column index. 187 | * 188 | * Returns: A value from the matrix 189 | */ 190 | @safe pure nothrow 191 | ref inout(Number) opIndex(size_t row, size_t column) inout 192 | in { 193 | assert(column <= columnCount, "column out of bounds!"); 194 | } body { 195 | return _data[row * _columnCount + column]; 196 | } 197 | 198 | /** 199 | * Overload for foreach(rowIndex, columnIndex, value; matrix) {} 200 | */ 201 | @trusted 202 | int opApply(int delegate(ref size_t, ref size_t, ref Number) dg) { 203 | int result = 0; 204 | 205 | matrixLoop: foreach(row; 0 .. rowCount) { 206 | size_t offset = row * columnCount; 207 | 208 | foreach(column; 0 .. columnCount) { 209 | result = dg(row, column, _data[offset + column]); 210 | 211 | if (result) { 212 | break matrixLoop; 213 | } 214 | } 215 | } 216 | 217 | return result; 218 | } 219 | 220 | 221 | /** 222 | * Modify this matrix, adding/subtracting values from another matrix. 223 | * 224 | * Example: 225 | * --- 226 | * matrix += other_matrix; 227 | * matrix -= yet_another_matrix; 228 | * --- 229 | * 230 | * Params: 231 | * other = Another matrix with an implicitly convertible numeric type. 232 | */ 233 | @safe pure nothrow 234 | void opOpAssign(string op, OtherNumber) 235 | (const Matrix!OtherNumber other) 236 | if((op == "+" || op == "-") && is(OtherNumber : Number)) 237 | in { 238 | assert(this.rowCount == other.rowCount); 239 | assert(this.columnCount == other.columnCount); 240 | } body { 241 | foreach(i; 0.._data.length) { 242 | mixin(`_data[i]` ~ op ~ `= other._data[i];`); 243 | } 244 | } 245 | 246 | /** 247 | * Add or subtract two matrices, yielding a new matrix. 248 | * 249 | * Params: 250 | * other = The other matrix. 251 | * 252 | * Returns: A new matrix. 253 | */ 254 | @safe pure nothrow 255 | Matrix!Number opBinary(string op, OtherNumber) 256 | (ref const Matrix!OtherNumber other) const 257 | if((op == "+" || op == "-") && is(OtherNumber : Number)) in { 258 | assert(this.rowCount == other.rowCount); 259 | assert(this.columnCount == other.columnCount); 260 | } out(val) { 261 | assert(this.rowCount == val.rowCount); 262 | assert(this.rowCount == val.rowCount); 263 | } body { 264 | // Copy this matrix. 265 | auto result = this.dup; 266 | 267 | mixin(`result ` ~ op ~ `= other;`); 268 | 269 | return result; 270 | } 271 | 272 | /// ditto 273 | @safe pure nothrow 274 | Matrix!Number opBinary(string op, OtherNumber) 275 | (const Matrix!OtherNumber other) const 276 | if((op == "+" || op == "-") && is(OtherNumber : Number)) { 277 | opBinary!(op, OtherNumber)(other); 278 | } 279 | 280 | /** 281 | * Multiply two matrices. 282 | * 283 | * Given a matrix of size (m, n) and a matrix of size (o, p). 284 | * This operation can only work if n == o. 285 | * The resulting matrix will be size (m, p). 286 | * 287 | * Params: 288 | * other = Another matrix 289 | * 290 | * Returns: The product of two matrices. 291 | */ 292 | @safe pure nothrow 293 | Matrix!Number opBinary(string op, OtherNumber) 294 | (ref const Matrix!OtherNumber other) const 295 | if((op == "*") && is(OtherNumber : Number)) in { 296 | assert(this.columnCount == other.rowCount); 297 | } out(val) { 298 | assert(val.rowCount == this.rowCount); 299 | assert(val.columnCount == other.columnCount); 300 | } body { 301 | auto result = Matrix!Number(this.rowCount, other.columnCount); 302 | 303 | matrixMultiply(result, this, other); 304 | 305 | return result; 306 | } 307 | 308 | /// ditto 309 | @safe pure nothrow 310 | Matrix!Number opBinary(string op, OtherNumber) 311 | (const Matrix!OtherNumber other) const 312 | if((op == "*") && is(OtherNumber : Number)) in { 313 | opBinary!(op, OtherNumber)(other); 314 | } 315 | 316 | /// ditto 317 | @safe pure nothrow 318 | Matrix!OtherNumber opBinary(string op, OtherNumber) 319 | (ref const Matrix!OtherNumber other) const 320 | if((op == "*") && !is(Number == OtherNumber) && is(Number : OtherNumber)) in { 321 | assert(this.columnCount == other.rowCount); 322 | } out(val) { 323 | assert(val.rowCount == this.rowCount); 324 | assert(val.columnCount == other.columnCount); 325 | } body { 326 | auto result = Matrix!OtherNumber(this.rowCount, other.columnCount); 327 | 328 | matrixMultiply(result, this, other); 329 | 330 | return result; 331 | } 332 | 333 | /// ditto 334 | @safe pure nothrow 335 | Matrix!OtherNumber opBinary(string op, OtherNumber) 336 | (const Matrix!OtherNumber other) const 337 | if((op == "*") && !is(Number == OtherNumber) && is(Number : OtherNumber)) { 338 | opBinary!(op, OtherNumber)(other); 339 | } 340 | 341 | /** 342 | * Modify this matrix with a scalar value. 343 | */ 344 | @safe pure nothrow 345 | void opOpAssign(string op, OtherNumber)(OtherNumber other) 346 | if(op != "in" && op != "~" && is(OtherNumber : Number)) { 347 | foreach(i; 0.._data.length) { 348 | mixin(`_data[i]` ~ op ~ `= other;`); 349 | } 350 | } 351 | 352 | /** 353 | * Returns: A new matrix produce by combining a matrix and a scalar value. 354 | */ 355 | @safe pure nothrow 356 | Matrix!Number opBinary(string op, OtherNumber)(OtherNumber other) const 357 | if(op != "in" && op != "~" && is(OtherNumber : Number)) { 358 | // Copy this matrix. 359 | auto result = this.dup; 360 | 361 | mixin(`result ` ~ op ~ `= other;`); 362 | 363 | return result; 364 | } 365 | 366 | /** 367 | * Returns: true if two matrices are equal and have the same type. 368 | */ 369 | @safe pure nothrow 370 | bool opEquals(ref const Matrix!Number other) const { 371 | return _rowCount == other._rowCount 372 | && _columnCount == other._columnCount 373 | && _data == other._data; 374 | } 375 | 376 | /// ditto 377 | @safe pure nothrow 378 | bool opEquals(const Matrix!Number other) const { 379 | return opEquals(other); 380 | } 381 | } 382 | 383 | // Test basic matrix initialisation and foreach. 384 | unittest { 385 | size_t rowCount = 4; 386 | size_t columnCount = 3; 387 | int expectedValue = 42; 388 | 389 | auto mat = Matrix!int(rowCount, columnCount, [ 390 | 42, 42, 42, 391 | 42, 42, 42, 392 | 42, 42, 42, 393 | 42, 42, 42 394 | ]); 395 | 396 | size_t expectedRow = 0; 397 | size_t expectedColumn = 0; 398 | 399 | foreach(row, column, value; mat) { 400 | assert(row == expectedRow); 401 | assert(column == expectedColumn); 402 | assert(value == expectedValue); 403 | 404 | if (++expectedColumn == columnCount) { 405 | expectedColumn = 0; 406 | ++expectedRow; 407 | } 408 | } 409 | } 410 | 411 | // Test matrix referencing and copying 412 | unittest { 413 | auto mat = Matrix!int(3, 3); 414 | 415 | mat[0, 0] = 42; 416 | 417 | auto normalCopy = mat.dup; 418 | 419 | normalCopy[0, 0] = 27; 420 | 421 | assert(mat[0, 0] == 42, "Matrix .dup created a data reference!"); 422 | 423 | immutable immutCopy = mat.idup; 424 | } 425 | 426 | // Test modifying a matrix row externally. 427 | unittest { 428 | auto mat = Matrix!int(3, 3); 429 | 430 | auto row = mat[0]; 431 | 432 | row[0] = 3; 433 | row[1] = 4; 434 | row[2] = 7; 435 | 436 | assert(mat[0] == [3, 4, 7]); 437 | } 438 | 439 | // Test immutable initialisation for a matrix 440 | unittest { 441 | immutable mat = immutable Matrix!int(3, 3, [ 442 | 1, 2, 3, 443 | 4, 5, 6, 444 | 7, 8, 9 445 | ]); 446 | } 447 | 448 | // Test matrix addition/subtraction. 449 | unittest { 450 | void runtest(string op)() { 451 | import std.stdio; 452 | 453 | size_t rowCount = 4; 454 | size_t columnCount = 4; 455 | 456 | int leftValue = 8; 457 | byte rightValue = 10; 458 | 459 | auto left = Matrix!int(rowCount, columnCount, [ 460 | 8, 8, 8, 8, 461 | 8, 8, 8, 8, 462 | 8, 8, 8, 8, 463 | 8, 8, 8, 8, 464 | ]); 465 | 466 | auto right = Matrix!byte(rowCount, columnCount, [ 467 | 10, 10, 10, 10, 468 | 10, 10, 10, 10, 469 | 10, 10, 10, 10, 470 | 10, 10, 10, 10, 471 | ]); 472 | 473 | auto result = mixin(`left` ~ op ~ `right`); 474 | auto expectedScalar = mixin(`leftValue` ~ op ~ `rightValue`); 475 | 476 | foreach(row, column, value; result) { 477 | assert(value == expectedScalar, `Matrix op failed: ` ~ op); 478 | } 479 | } 480 | 481 | runtest!"+"; 482 | runtest!"-"; 483 | } 484 | 485 | // Text matrix-scalar operations. 486 | unittest { 487 | import std.stdio; 488 | 489 | void runtest(string op)() { 490 | size_t rowCount = 2; 491 | size_t columnCount = 3; 492 | 493 | // The results for these two values are always nonzero. 494 | long matrixValue = 1_234_567; 495 | int scalar = 11; 496 | 497 | auto matrix = Matrix!long(rowCount, columnCount, [ 498 | matrixValue, matrixValue, matrixValue, 499 | matrixValue, matrixValue, matrixValue, 500 | ]); 501 | 502 | auto result = mixin(`matrix` ~ op ~ `scalar`); 503 | 504 | auto expectedScalar = mixin(`matrixValue` ~ op ~ `scalar`); 505 | 506 | foreach(row, column, value; result) { 507 | assert(value == expectedScalar, `Matirix scalar op failed: ` ~ op); 508 | } 509 | } 510 | 511 | runtest!"+"; 512 | runtest!"-"; 513 | runtest!"*"; 514 | runtest!"/"; 515 | runtest!"%"; 516 | runtest!"^^"; 517 | runtest!"&"; 518 | runtest!"|"; 519 | runtest!"^"; 520 | runtest!"<<"; 521 | runtest!">>"; 522 | runtest!">>>"; 523 | } 524 | 525 | unittest { 526 | // Test matrix equality. 527 | 528 | auto left = Matrix!int(3, 3, [ 529 | 1, 2, 3, 530 | 4, 5, 6, 531 | 7, 8, 9 532 | ]); 533 | 534 | auto right = immutable Matrix!int(3, 3, [ 535 | 1, 2, 3, 536 | 4, 5, 6, 537 | 7, 8, 9 538 | ]); 539 | 540 | assert(left == right); 541 | } 542 | 543 | // Test matrix multiplication 544 | unittest { 545 | // Let's test the Wikipedia example, why not? 546 | auto left = Matrix!int(2, 3, [ 547 | 2, 3, 4, 548 | 1, 0, 0 549 | ]); 550 | 551 | auto right = Matrix!int(3, 2, [ 552 | 0, 1000, 553 | 1, 100, 554 | 0, 10 555 | ]); 556 | 557 | auto result = left * right; 558 | 559 | int[][] expected = [ 560 | [3, 2340], 561 | [0, 1000] 562 | ]; 563 | 564 | 565 | foreach(i; 0..2) { 566 | foreach(j; 0..2) { 567 | assert(result[i, j] == expected[i][j]); 568 | } 569 | } 570 | } 571 | 572 | // Test matrix multiplication, with a numeric type on the 573 | // left which implicitly converts to the right. 574 | unittest { 575 | auto left = Matrix!int(1, 1); 576 | auto right = Matrix!long(1, 1); 577 | 578 | Matrix!long result = left * right; 579 | } 580 | 581 | /** 582 | * This class defines a range of rows over a matrix. 583 | */ 584 | struct Rows(Number) if(isNumeric!Number) { 585 | private: 586 | const(Number)[] _data; 587 | size_t _columnCount; 588 | public: 589 | /** 590 | * Create a new rows range for a given matrix. 591 | */ 592 | @safe pure nothrow 593 | this(ref const Matrix!Number matrix) { 594 | _data = matrix._data; 595 | _columnCount = matrix._columnCount; 596 | } 597 | 598 | /// ditto 599 | @safe pure nothrow 600 | this(const Matrix!Number matrix) { 601 | this(matrix); 602 | } 603 | 604 | /// Returns: true if the range is empty. 605 | @safe pure nothrow 606 | @property bool empty() const { 607 | return _data.length == 0; 608 | } 609 | 610 | /// Advance to the next row. 611 | @safe pure nothrow 612 | void popFront() { 613 | assert(!empty, "Attempted popFront on an empty Rows range!"); 614 | 615 | _data = _data[_columnCount .. $]; 616 | } 617 | 618 | /// Returns: The current row. 619 | @safe pure nothrow 620 | @property const(Number[]) front() const { 621 | assert(!empty, "Cannot get the front of an empty Rows range!"); 622 | 623 | return this[0]; 624 | } 625 | 626 | /// Save a copy of this range. 627 | @safe pure nothrow 628 | Rows!Number save() const { 629 | return this; 630 | } 631 | 632 | /// Retreat a row backwards. 633 | @safe pure nothrow 634 | void popBack() { 635 | assert(!empty, "Attempted popBack on an empty Rows range!"); 636 | 637 | _data = _data[0 .. $ - _columnCount]; 638 | } 639 | 640 | /// Returns: The row at the end of the range. 641 | @safe pure nothrow 642 | @property const(Number[]) back() const { 643 | assert(!empty, "Cannot get the back of an empty Rows range!"); 644 | 645 | return this[$ - 1]; 646 | } 647 | 648 | /** 649 | * Params: 650 | * index = An index for a row in the range. 651 | * 652 | * Returns: A row at an index in the range. 653 | */ 654 | @safe pure nothrow 655 | @property const(Number[]) opIndex(size_t index) const in { 656 | assert(index >= 0, "Negative index given to Rows opIndex!"); 657 | assert(index < length, "Out of bounds index given to Rows opIndex!"); 658 | } body { 659 | size_t offset = index * _columnCount; 660 | 661 | return _data[offset .. offset + _columnCount]; 662 | } 663 | 664 | /// Returns: The current length of the range. 665 | @safe pure nothrow 666 | @property size_t length() const { 667 | if (_data.length == 0) { 668 | return 0; 669 | } 670 | 671 | return _data.length / _columnCount; 672 | } 673 | 674 | /// ditto 675 | @safe pure nothrow 676 | @property size_t opDollar() const { 677 | return length; 678 | } 679 | } 680 | 681 | /** 682 | * Returns: A range through a matrix's rows. 683 | */ 684 | @safe pure nothrow 685 | Rows!Number rows(Number)(Matrix!Number matrix) { 686 | return typeof(return)(matrix); 687 | } 688 | 689 | unittest { 690 | auto mat = Matrix!int(3, 3, [ 691 | 1, 2, 3, 692 | 4, 5, 6, 693 | 7, 8, 9 694 | ]); 695 | 696 | auto expected = [ 697 | [1, 2, 3], 698 | [4, 5, 6], 699 | [7, 8, 9] 700 | ]; 701 | 702 | size_t rowIndex = 0; 703 | 704 | // Test InputRange stuff 705 | for (auto range = mat.rows; !range.empty; range.popFront) { 706 | assert(range.front == expected[rowIndex++]); 707 | } 708 | 709 | // Test ForwardRange 710 | 711 | auto range1 = mat.rows; 712 | 713 | range1.popFront; 714 | range1.popBack; 715 | 716 | auto range2 = range1.save; 717 | 718 | range1.popFront; 719 | 720 | assert(range2.front == [4, 5, 6]); 721 | 722 | rowIndex = 3; 723 | 724 | // Test BidirectionalRange 725 | for (auto range = mat.rows; !range.empty; range.popBack) { 726 | assert(range.back == expected[--rowIndex]); 727 | } 728 | 729 | // Test RandomAccessRange 730 | 731 | auto range3 = mat.rows; 732 | 733 | range3.popFront; 734 | range3.popBack; 735 | 736 | assert(range3.length == 1); 737 | assert(range3[0] == [4, 5, 6]); 738 | } 739 | 740 | // Test 0 size Matrix rows 741 | unittest { 742 | Matrix!int mat; 743 | 744 | assert(mat.rows.length == 0); 745 | } 746 | 747 | /** 748 | * A static matrix type. This is a value matrix value type created directly 749 | * on the stack. 750 | */ 751 | struct Matrix(Number, size_t _rowCount, size_t _columnCount) 752 | if(isNumeric!Number && _rowCount > 0 && _columnCount > 0) { 753 | /// The number of rows in this matrix. 754 | enum rowCount = _rowCount; 755 | /// The number of columns in this matrix. 756 | enum columnCount = _columnCount; 757 | /// true if this matrix is a zero-sized matrix. 758 | enum empty = false; 759 | /// true if this matrix is a square matrix. 760 | enum isSquare = rowCount == columnCount; 761 | 762 | /// The data backing this matrix. 763 | Number[columnCount][rowCount] array2D; 764 | 765 | alias array2D this; 766 | 767 | /** 768 | * Construct this matrix from a 2 dimensional static array. 769 | * 770 | * Params: 771 | * array2D = A 2 dimension array of the same size. 772 | */ 773 | @safe pure nothrow 774 | this(ref const(Number[columnCount][rowCount]) data) inout { 775 | array2D = data; 776 | } 777 | 778 | /// ditto 779 | @safe pure nothrow 780 | this(const(Number[columnCount][rowCount]) data) inout { 781 | array2D = data; 782 | } 783 | 784 | /** 785 | * Construct this matrix directly from a series of numbers. 786 | * This constructor is designed to be executed at compile time. 787 | * 788 | * Params: 789 | * numbers... = A series of numbers to initialise the matrix with. 790 | */ 791 | @safe pure nothrow 792 | this(Number[rowCount * columnCount] numbers...) { 793 | foreach(row; 0 .. rowCount) { 794 | foreach(column; 0 .. columnCount) { 795 | array2D[row][column] = numbers[row * columnCount + column]; 796 | } 797 | } 798 | } 799 | 800 | /** 801 | * Returns: A reference to this matrix's data as a 1D array. 802 | */ 803 | @trusted pure nothrow 804 | @property 805 | ref inout(Number[rowCount * columnCount]) array1D() inout { 806 | return (cast(Number*)array2D.ptr)[0 .. rowCount * columnCount]; 807 | } 808 | 809 | // Even with alias this, we still need this overload. 810 | /** 811 | * Params: 812 | * row = A row index. 813 | * 814 | * Returns: A row from the matrix. 815 | */ 816 | @safe pure nothrow 817 | ref inout(Number[columnCount]) opIndex(size_t row) inout { 818 | return array2D[row]; 819 | } 820 | 821 | /** 822 | * Params: 823 | * row = A row index. 824 | * column = A column index. 825 | * 826 | * Returns: A value from the matrix 827 | */ 828 | @safe pure nothrow 829 | ref inout(Number) opIndex(size_t row, size_t column) inout { 830 | return array2D[row][column]; 831 | } 832 | 833 | /** 834 | * Overload for foreach(rowIndex, columnIndex, value; matrix) {} 835 | */ 836 | @trusted 837 | int opApply(int delegate(ref size_t, ref size_t, ref Number) dg) { 838 | int result = 0; 839 | 840 | matrixLoop: foreach(row, rowArray; array2D) { 841 | foreach(column, value; rowArray) { 842 | result = dg(row, column, value); 843 | 844 | if (result) { 845 | break matrixLoop; 846 | } 847 | } 848 | } 849 | 850 | return result; 851 | } 852 | 853 | /** 854 | * Modify this matrix, adding/subtracting values from another matrix. 855 | * 856 | * Example: 857 | * --- 858 | * matrix += other_matrix; 859 | * matrix -= yet_another_matrix; 860 | * --- 861 | * 862 | * Params: 863 | * other = Another matrix with an implicitly convertible numeric type. 864 | */ 865 | @safe pure nothrow 866 | void opOpAssign(string op, OtherNumber) 867 | (ref const(Matrix!(OtherNumber, rowCount, columnCount)) other) 868 | if((op == "+" || op == "-") && is(OtherNumber : Number)) { 869 | foreach(i; 0 .. rowCount) { 870 | foreach(j; 0 .. columnCount) { 871 | mixin(`array2D[i][j]` ~ op ~ `= other.array2D[i][j];`); 872 | } 873 | } 874 | } 875 | 876 | /// ditto 877 | @safe pure nothrow 878 | void opOpAssign(string op, OtherNumber) 879 | (const(Matrix!(OtherNumber, rowCount, columnCount)) other) 880 | if((op == "+" || op == "-") && is(OtherNumber : Number)) { 881 | opOpAssign(other); 882 | } 883 | 884 | /** 885 | * Add or subtract two matrices, yielding a new matrix. 886 | * 887 | * Params: 888 | * other = The other matrix. 889 | * 890 | * Returns: A new matrix. 891 | */ 892 | @safe pure nothrow 893 | Matrix!(Number, rowCount, columnCount) opBinary(string op, OtherNumber) 894 | (ref const(Matrix!(OtherNumber, rowCount, columnCount)) other) 895 | if((op == "+" || op == "-") && is(OtherNumber : Number)) { 896 | // Copy this matrix. 897 | typeof(return) result = this; 898 | 899 | mixin(`result ` ~ op ~ `= other;`); 900 | 901 | return result; 902 | } 903 | 904 | /// ditto 905 | @safe pure nothrow 906 | Matrix!(Number, rowCount, columnCount) opBinary(string op, OtherNumber) 907 | (const(Matrix!(OtherNumber, rowCount, columnCount)) other) 908 | if((op == "+" || op == "-") && is(OtherNumber : Number)) { 909 | return opBinary(other); 910 | } 911 | 912 | /** 913 | * Multiply two matrices. 914 | * 915 | * Given a matrix of size (m, n) and a matrix of size (o, p). 916 | * This operation can only work if n == o. 917 | * The resulting matrix will be size (m, p). 918 | * 919 | * Params: 920 | * other = Another matrix 921 | * 922 | * Returns: The product of two matrices. 923 | */ 924 | @safe pure nothrow 925 | Matrix!(Number, rowCount, otherColumnCount) 926 | opBinary(string op, OtherNumber, size_t otherRowCount, size_t otherColumnCount) 927 | (ref const(Matrix!(OtherNumber, otherRowCount, otherColumnCount)) other) const 928 | if((op == "*") && (columnCount == otherRowCount) && is(OtherNumber : Number)) { 929 | typeof(return) result; 930 | 931 | matrixMultiply(result, this, other); 932 | 933 | return result; 934 | } 935 | 936 | /// ditto 937 | @safe pure nothrow 938 | Matrix!(Number, rowCount, otherColumnCount) 939 | opBinary(string op, OtherNumber, size_t otherRowCount, size_t otherColumnCount) 940 | (const(Matrix!(OtherNumber, otherRowCount, otherColumnCount)) other) const 941 | if((op == "*") && (columnCount == otherRowCount) && is(OtherNumber : Number)) in { 942 | return opBinary!(op, OtherNumber, otherRowCount, otherColumnCount)(other); 943 | } 944 | 945 | @safe pure nothrow 946 | Matrix!(OtherNumber, rowCount, otherColumnCount) 947 | opBinary(string op, OtherNumber, size_t otherRowCount, size_t otherColumnCount) 948 | (ref const(Matrix!(OtherNumber, otherRowCount, otherColumnCount)) other) const 949 | if((op == "*") && (columnCount == otherRowCount) && !is(Number == OtherNumber) && is(Number : OtherNumber)) { 950 | typeof(return) result; 951 | 952 | matrixMultiply(result, this, other); 953 | 954 | return result; 955 | } 956 | 957 | @safe pure nothrow 958 | Matrix!(OtherNumber, rowCount, otherColumnCount) 959 | opBinary(string op, OtherNumber, size_t otherRowCount, size_t otherColumnCount) 960 | (const(Matrix!(OtherNumber, otherRowCount, otherColumnCount)) other) const 961 | if((op == "*") && (columnCount == otherRowCount) && !is(Number == OtherNumber) && is(Number : OtherNumber)) { 962 | return opBinary!(op, OtherNumber, otherRowCount, otherColumnCount)(other); 963 | } 964 | } 965 | 966 | // 0 size matrices are special case. 967 | 968 | /// ditto 969 | struct Matrix(Number, size_t _rowCount, size_t _columnCount) 970 | if(isNumeric!Number && _rowCount == 0 && _columnCount == 0) { 971 | /// The number of rows in this matrix. 972 | enum rowCount = _rowCount; 973 | /// The number of columns in this matrix. 974 | enum columnCount = _columnCount; 975 | /// True if this matrix is a zero-sized matrix. 976 | enum empty = true; 977 | /// true if this matrix is a square matrix. 978 | enum isSquare = true; 979 | } 980 | 981 | /** 982 | * Alias all (M, 0), (0, N) size static matrices into one single zero-sized 983 | * type of size (0, 0). 984 | */ 985 | template Matrix(Number, size_t rowCount, size_t columnCount) 986 | if ((rowCount > 0 && columnCount == 0) || (rowCount == 0 && columnCount > 0)) { 987 | alias Matrix = Matrix!(Number, 0, 0); 988 | } 989 | 990 | // Test copy constructor for 2D arrays. 991 | unittest { 992 | int[3][2] data = [ 993 | [1, 2, 3], 994 | [4, 5, 6], 995 | ]; 996 | 997 | Matrix!(int, 2, 3) matrix = data; 998 | 999 | assert(data == matrix); 1000 | } 1001 | 1002 | // Test move constructor for 2D arrays. 1003 | unittest { 1004 | auto matrix = Matrix!(int, 3, 3)([ 1005 | [1, 2, 3], 1006 | [4, 5, 6], 1007 | [7, 8, 9] 1008 | ]); 1009 | } 1010 | 1011 | // Test immutable too. 1012 | unittest { 1013 | immutable(int[3][3]) data = [ 1014 | [1, 2, 3], 1015 | [4, 5, 6], 1016 | [7, 8, 9] 1017 | ]; 1018 | 1019 | Matrix!(int, 3, 3) matrix = data; 1020 | 1021 | assert(data == matrix); 1022 | } 1023 | 1024 | unittest { 1025 | int[3][3] data = [ 1026 | [1, 2, 3], 1027 | [4, 5, 6], 1028 | [7, 8, 9] 1029 | ]; 1030 | 1031 | immutable(Matrix!(int, 3, 3,)) matrix = data; 1032 | 1033 | assert(data == matrix); 1034 | } 1035 | 1036 | // Test 1D array matrix slicing. 1037 | unittest { 1038 | auto matrix = Matrix!(int, 3, 3)([ 1039 | [1, 2, 3], 1040 | [4, 5, 6], 1041 | [7, 8, 9] 1042 | ]); 1043 | 1044 | assert(matrix.array1D == [1, 2, 3, 4, 5, 6, 7, 8, 9]); 1045 | 1046 | matrix.array1D[0] = 347; 1047 | 1048 | assert(matrix[0][0] == 347); 1049 | } 1050 | 1051 | // Test that copy semantics work property for 1D arrays. 1052 | unittest { 1053 | auto matrix = Matrix!(int, 3, 3)([ 1054 | [1, 2, 3], 1055 | [4, 5, 6], 1056 | [7, 8, 9] 1057 | ]); 1058 | 1059 | auto arr = matrix.array1D; 1060 | 1061 | arr[0] = 347; 1062 | 1063 | // The value should not have changed. 1064 | assert(matrix[0][0] == 1); 1065 | } 1066 | 1067 | // Test that reference semantics work property for 1D arrays. 1068 | unittest { 1069 | auto matrix = Matrix!(int, 3, 3)([ 1070 | [1, 2, 3], 1071 | [4, 5, 6], 1072 | [7, 8, 9] 1073 | ]); 1074 | 1075 | void foo(ref int[9] arr) { 1076 | arr[0] = 347; 1077 | } 1078 | 1079 | foo(matrix.array1D); 1080 | 1081 | // The should have changed. 1082 | assert(matrix[0][0] == 347); 1083 | } 1084 | 1085 | // Test const and immutable 1D array, just in case. 1086 | unittest { 1087 | const matrix = Matrix!(int, 3, 3)([ 1088 | [1, 2, 3], 1089 | [4, 5, 6], 1090 | [7, 8, 9] 1091 | ]); 1092 | 1093 | assert(matrix.array1D == [1, 2, 3, 4, 5, 6, 7, 8, 9]); 1094 | assert(is(typeof(matrix.array1D) == const int[9])); 1095 | } 1096 | 1097 | unittest { 1098 | immutable matrix = Matrix!(int, 3, 3)([ 1099 | [1, 2, 3], 1100 | [4, 5, 6], 1101 | [7, 8, 9] 1102 | ]); 1103 | 1104 | assert(matrix.array1D == [1, 2, 3, 4, 5, 6, 7, 8, 9]); 1105 | assert(is(typeof(matrix.array1D) == immutable int[9])); 1106 | } 1107 | 1108 | // Test compile time init with numbers. 1109 | unittest { 1110 | enum matrix = Matrix!(int, 3, 3)( 1111 | 1, 2, 3, 1112 | 4, 5, 6, 1113 | 7, 8, 9 1114 | ); 1115 | 1116 | assert(matrix.array2D[0][0] == 1); 1117 | assert(matrix.array2D[0][1] == 2); 1118 | assert(matrix.array2D[0][2] == 3); 1119 | assert(matrix.array2D[1][0] == 4); 1120 | assert(matrix.array2D[1][1] == 5); 1121 | assert(matrix.array2D[1][2] == 6); 1122 | assert(matrix.array2D[2][0] == 7); 1123 | assert(matrix.array2D[2][1] == 8); 1124 | assert(matrix.array2D[2][2] == 9); 1125 | } 1126 | 1127 | // Test zero sized matrices 1128 | unittest { 1129 | Matrix!(int, 0, 0) nothing; 1130 | Matrix!(int, 1, 1) scalar; 1131 | 1132 | assert(nothing.empty); 1133 | assert(!scalar.empty); 1134 | 1135 | Matrix!(int, 0, 1) noRows; 1136 | Matrix!(int, 1, 0) noColumns; 1137 | 1138 | assert(is(typeof(nothing) == typeof(noRows))); 1139 | assert(is(typeof(noRows) == typeof(noColumns))); 1140 | } 1141 | 1142 | // Test index and assignment for cells. 1143 | unittest { 1144 | Matrix!(int, 3, 3) matrix; 1145 | 1146 | assert(matrix[0, 0] == 0); 1147 | 1148 | matrix[0, 0] = 3; 1149 | 1150 | assert(matrix[0, 0] == 3); 1151 | 1152 | matrix[0, 0] *= 3; 1153 | 1154 | assert(matrix[0, 0] == 9); 1155 | } 1156 | 1157 | /** 1158 | * Transpose (flip) a matrix. 1159 | * 1160 | * Params: 1161 | * matrix = The matrix to produce a transpose for. 1162 | * 1163 | * Returns: A new matrix which is the transpose of the given matrix. 1164 | */ 1165 | @safe pure nothrow 1166 | Matrix!Number transpose(Number)(const Matrix!Number matrix) 1167 | out(val) { 1168 | assert(matrix.columnCount == val.rowCount); 1169 | assert(matrix.rowCount == val.columnCount); 1170 | } body { 1171 | auto result = typeof(return)(matrix.columnCount, matrix.rowCount); 1172 | 1173 | foreach(row; 0 .. matrix.rowCount) { 1174 | foreach(col; 0 .. matrix.columnCount) { 1175 | result[col, row] = matrix[row, col]; 1176 | } 1177 | } 1178 | 1179 | return result; 1180 | } 1181 | 1182 | /// ditto 1183 | @safe pure nothrow 1184 | Matrix!(Number, columnCount, rowCount) 1185 | transpose(Number, size_t rowCount, size_t columnCount) 1186 | (ref const Matrix!(Number, rowCount, columnCount) matrix) { 1187 | typeof(return) result; 1188 | 1189 | foreach(row; 0 .. matrix.rowCount) { 1190 | foreach(col; 0 .. matrix.columnCount) { 1191 | result[col, row] = matrix[row, col]; 1192 | } 1193 | } 1194 | 1195 | return result; 1196 | } 1197 | 1198 | /// ditto 1199 | @safe pure nothrow 1200 | Matrix!(Number, columnCount, rowCount) 1201 | transpose(Number, size_t rowCount, size_t columnCount) 1202 | (const Matrix!(Number, rowCount, columnCount) matrix) { 1203 | return transpose(matrix); 1204 | } 1205 | 1206 | unittest { 1207 | auto matrix = Matrix!int(2, 3, [ 1208 | 1, 2, 3, 1209 | 0, -6, 7 1210 | ]); 1211 | 1212 | int[] expected = [1, 0, 2, -6, 3, 7]; 1213 | 1214 | auto result = matrix.transpose; 1215 | 1216 | assert(result.rowCount == matrix.columnCount); 1217 | assert(result.columnCount == matrix.rowCount); 1218 | assert(result._data == expected); 1219 | } 1220 | 1221 | unittest { 1222 | // When transposed twice, we should get the same matrix. 1223 | auto matrix = Matrix!int(2, 3, [ 1224 | 1, 2, 3, 1225 | 0, -6, 7 1226 | ]); 1227 | 1228 | assert(matrix == matrix.transpose.transpose); 1229 | } 1230 | 1231 | unittest { 1232 | auto matrix = Matrix!(int, 2, 3)( 1233 | 1, 2, 3, 1234 | 0, -6, 7 1235 | ); 1236 | 1237 | assert(matrix == matrix.transpose.transpose); 1238 | } 1239 | 1240 | // Test foreach on static matrices. 1241 | unittest { 1242 | auto matrix = Matrix!(int, 2, 3)( 1243 | 1, 2, 3, 1244 | 0, -6, 7 1245 | ); 1246 | 1247 | foreach(row, col, value; matrix) { 1248 | if (row == 0) { 1249 | if (col == 0) { 1250 | assert(value == 1); 1251 | } else if (col == 1) { 1252 | assert(value == 2); 1253 | } else { 1254 | assert(value == 3); 1255 | } 1256 | } else { 1257 | if (col == 0) { 1258 | assert(value == 0); 1259 | } else if (col == 1) { 1260 | assert(value == -6); 1261 | } else { 1262 | assert(value == 7); 1263 | } 1264 | } 1265 | } 1266 | } 1267 | 1268 | // Test binary modifying operations on static matrices. 1269 | unittest { 1270 | auto left = Matrix!(int, 2, 3)( 1271 | 1, 2, 3, 1272 | 4, 5, 6 1273 | ); 1274 | 1275 | auto right = Matrix!(int, 2, 3)( 1276 | 1, 2, 3, 1277 | 4, 5, 6 1278 | ); 1279 | 1280 | left -= right; 1281 | 1282 | foreach(row, col, value; left) { 1283 | assert(value == 0); 1284 | } 1285 | 1286 | left -= right; 1287 | left += right; 1288 | 1289 | foreach(row, col, value; left) { 1290 | assert(value == 0); 1291 | } 1292 | } 1293 | 1294 | // Test binary copying operations on static matrices. 1295 | unittest { 1296 | auto left = Matrix!(int, 2, 3)( 1297 | 1, 2, 3, 1298 | 4, 5, 6 1299 | ); 1300 | 1301 | auto right = Matrix!(int, 2, 3)( 1302 | 1, 2, 3, 1303 | 4, 5, 6 1304 | ); 1305 | 1306 | auto newMatrix = left - right; 1307 | 1308 | foreach(row, col, value; newMatrix) { 1309 | assert(value == 0); 1310 | } 1311 | 1312 | auto finalMatrix = newMatrix + left; 1313 | 1314 | finalMatrix -= left; 1315 | 1316 | foreach(row, col, value; finalMatrix) { 1317 | assert(value == 0); 1318 | } 1319 | } 1320 | 1321 | // Test matrix multiplication on static matrices 1322 | unittest { 1323 | auto left = Matrix!(int, 2, 3)( 1324 | 2, 3, 4, 1325 | 1, 0, 0 1326 | ); 1327 | 1328 | auto right = Matrix!(int, 3, 2)( 1329 | 0, 1000, 1330 | 1, 100, 1331 | 0, 10 1332 | ); 1333 | 1334 | auto result = left * right; 1335 | 1336 | int[][] expected = [ 1337 | [3, 2340], 1338 | [0, 1000] 1339 | ]; 1340 | 1341 | foreach(i; 0..2) { 1342 | foreach(j; 0..2) { 1343 | assert(result[i, j] == expected[i][j]); 1344 | } 1345 | } 1346 | } 1347 | 1348 | // Test matrix multiplication, with a numeric type on the 1349 | // left which implicitly converts to the right. 1350 | unittest { 1351 | Matrix!(int, 1, 1) left; 1352 | Matrix!(long, 1, 1) right; 1353 | 1354 | Matrix!(long, 1, 1) result = left * right; 1355 | } 1356 | 1357 | -------------------------------------------------------------------------------- /source/dstruct/option.d: -------------------------------------------------------------------------------- 1 | /** 2 | * This module defines an Option!T type and a related Some!T type for 3 | * dealing nullable data in a safe manner. 4 | */ 5 | module dstruct.option; 6 | 7 | import dstruct.support; 8 | 9 | import std.traits; 10 | import std.typecons; 11 | 12 | private struct SomeTypeMarker {} 13 | 14 | private enum isSomeType(T) = is(typeof(T.marker) == SomeTypeMarker); 15 | 16 | /** 17 | * This type represents a value which cannot be null by its contracts. 18 | */ 19 | struct Some(T) if (is(T == class) || isPointer!T) { 20 | private: 21 | enum marker = SomeTypeMarker.init; 22 | T _value; 23 | public: 24 | /// Disable default construction for Some!T types. 25 | @disable this(); 26 | 27 | /** 28 | * Construct this object by wrapping a given value. 29 | * 30 | * Params: 31 | * value = The value to create the object with. 32 | */ 33 | @nogc @safe pure nothrow 34 | this(U)(inout(U) value) inout if(is(U : T)) 35 | in { 36 | assert(value !is null, "A null value was given to Some."); 37 | } body { 38 | static assert( 39 | !is(U == typeof(null)), 40 | "Some!(" ~ T.stringof ~ ") cannot be constructed with null." 41 | ); 42 | 43 | _value = value; 44 | } 45 | 46 | /** 47 | * Get the value from this object. 48 | * 49 | * Returns: The value wrapped by this object. 50 | */ 51 | @nogc @safe pure nothrow 52 | @property inout(T) get() inout 53 | out(value) { 54 | assert(value !is null, "Some returned null!"); 55 | } body { 56 | return _value; 57 | } 58 | 59 | /** 60 | * Assign another value to this object. 61 | * 62 | * Params: 63 | * value = The value to set. 64 | */ 65 | @nogc @safe pure nothrow 66 | void opAssign(U)(U value) if(is(U : T)) 67 | in { 68 | assert(value !is null, "A null value was given to Some."); 69 | } body { 70 | static assert( 71 | !is(U == typeof(null)), 72 | "Some!(" ~ T.stringof ~ ") cannot be assigned to with null." 73 | ); 74 | 75 | _value = value; 76 | } 77 | 78 | /// Implicitly convert Some!T objects to T. 79 | alias get this; 80 | } 81 | 82 | /** 83 | * A helper function for constructing Some!T values. 84 | * 85 | * Params: 86 | * value = A value to wrap. 87 | * 88 | * Returns: The value wrapped in a non-nullable type. 89 | */ 90 | @nogc @safe pure nothrow 91 | inout(Some!T) some(T)(inout(T) value) if (is(T == class) || isPointer!T) { 92 | return inout(Some!T)(value); 93 | } 94 | 95 | // Test basic usage. 96 | unittest { 97 | class Klass {} 98 | struct Struct {} 99 | 100 | Some!Klass k = new Klass(); 101 | k = new Klass(); 102 | 103 | Klass k2 = k; 104 | 105 | Some!(Struct*) s = new Struct(); 106 | 107 | Struct* s1 = s; 108 | } 109 | 110 | // Test immutable 111 | unittest { 112 | class Klass {} 113 | 114 | immutable(Some!Klass) k = new immutable Klass(); 115 | } 116 | 117 | // Test class hierarchies. 118 | unittest { 119 | class Animal {} 120 | class Dog : Animal {} 121 | 122 | Some!Animal a = new Animal(); 123 | a = new Dog(); 124 | 125 | auto d = new Dog(); 126 | 127 | d = cast(Dog) a; 128 | 129 | assert(d !is null); 130 | 131 | Some!Dog d2 = new Dog(); 132 | 133 | Animal a2 = d2; 134 | } 135 | 136 | // Test conversion between wrapper types when wrapped types are compatible. 137 | unittest { 138 | class Animal {} 139 | class Dog : Animal {} 140 | 141 | Some!Animal a = new Animal(); 142 | Some!Dog d = new Dog(); 143 | 144 | a = d; 145 | } 146 | 147 | // Test the wrapper function. 148 | unittest { 149 | class Klass {} 150 | 151 | auto m = some(new Klass()); 152 | auto c = some(new const Klass()); 153 | auto i = some(new immutable Klass()); 154 | 155 | assert(is(typeof(m) == Some!Klass)); 156 | assert(is(typeof(c) == const(Some!Klass))); 157 | assert(is(typeof(i) == immutable(Some!Klass))); 158 | } 159 | 160 | /** 161 | * This type represents an optional value for T. 162 | * 163 | * This is a means of explicitly dealing with null values in every case. 164 | */ 165 | struct Option(T) if(is(T == class) || isPointer!T) { 166 | private: 167 | T _value; 168 | public: 169 | /** 170 | * Construct this object by wrapping a given value. 171 | * 172 | * Params: 173 | * value = The value to create the object with. 174 | */ 175 | @nogc @safe pure nothrow 176 | this(U)(inout(U) value) inout if(is(U : T)) { 177 | _value = value; 178 | } 179 | 180 | /** 181 | * Get the value from this object. 182 | * 183 | * Contracts ensure this value isn't null. 184 | * 185 | * Returns: Some value from this object. 186 | */ 187 | @nogc @safe pure nothrow 188 | @property Some!T get() 189 | in { 190 | assert(_value !is null, "get called for a null Option type!"); 191 | } body { 192 | return Some!T(_value); 193 | } 194 | 195 | /// ditto 196 | @nogc @trusted pure nothrow 197 | @property const(Some!T) get() const 198 | in { 199 | assert(_value !is null, "get called for a null Option type!"); 200 | } body { 201 | return Some!T(cast(T) _value); 202 | } 203 | 204 | /// ditto 205 | @nogc @trusted pure nothrow 206 | @property immutable(Some!T) get() immutable 207 | in { 208 | assert(_value !is null, "get called for a null Option type!"); 209 | } body { 210 | return Some!T(cast(T) _value); 211 | } 212 | 213 | static if(is(T == class)) { 214 | /** 215 | * Given some type U, perform a dynamic cast on the class reference 216 | * held within this optional value, and return a new optional value 217 | * which may be null if the cast fails. 218 | * 219 | * Returns: A casted optional value. 220 | */ 221 | @nogc pure nothrow 222 | inout(Option!U) dynamicCast(U)() inout { 223 | return Option!U(cast(U) _value); 224 | } 225 | } 226 | 227 | /** 228 | * Return some value from this reference, or a default value 229 | * by calling a callable argument. (function pointer, delegate, etc.) 230 | * 231 | * The delegate can return a nullable type, but if the type is null 232 | * an assertion error will be triggered, supposing the program is 233 | * running in debug mode. The delegate may also return a Some type. 234 | * 235 | * Params: 236 | * dg = Some delegate returning a value. 237 | * 238 | * Returns: This value, if the delegate's value if it is null. 239 | */ 240 | Some!T or(DG)(DG dg) 241 | if ( 242 | isCallable!DG 243 | && (ParameterTypeTuple!DG).length == 0 244 | && is(ReturnType!DG : T) 245 | ) { 246 | if (_value is null) { 247 | static if(isSomeType!(ReturnType!DG)) { 248 | return cast(Some!T) dg(); 249 | } else { 250 | return Some!T(dg()); 251 | } 252 | } 253 | 254 | return some(_value); 255 | } 256 | 257 | /// ditto 258 | const(Some!T) or(DG)(DG dg) const 259 | if ( 260 | isCallable!DG 261 | && (ParameterTypeTuple!DG).length == 0 262 | && is(Unqual!(ReturnType!DG) : Unqual!T) 263 | ) { 264 | if (_value is null) { 265 | static if(isSomeType!(ReturnType!DG)) { 266 | return cast(const(Some!T)) dg(); 267 | } else { 268 | return const(Some!T)(dg()); 269 | } 270 | } 271 | 272 | return some(_value); 273 | } 274 | 275 | /// ditto 276 | immutable(Some!T) or(DG)(DG dg) immutable 277 | if ( 278 | isCallable!DG 279 | && (ParameterTypeTuple!DG).length == 0 280 | && is(Unqual!(ReturnType!DG) : Unqual!T) 281 | ) { 282 | if (_value is null) { 283 | static if(isSomeType!(ReturnType!DG)) { 284 | return cast(immutable(Some!T)) dg(); 285 | } else { 286 | return immutable(Some!T)(dg()); 287 | } 288 | } 289 | 290 | return some(_value); 291 | } 292 | 293 | /** 294 | * Returns: True if the value this option type does not hold a value. 295 | */ 296 | @nogc @safe pure nothrow 297 | @property bool isNull() const { 298 | return _value is null; 299 | } 300 | 301 | /** 302 | * Assign another value to this object. 303 | * 304 | * Params: 305 | * value = The value to set. 306 | */ 307 | @nogc @safe pure nothrow 308 | void opAssign(U)(U value) if(is(U : T)) { 309 | _value = value; 310 | } 311 | } 312 | 313 | /** 314 | * A helper function for constructing Option!T values. 315 | * 316 | * Params: 317 | * value = A value to wrap. 318 | * 319 | * Returns: The value wrapped in an option type. 320 | */ 321 | @nogc @safe pure nothrow 322 | inout(Option!T) option(T)(inout(T) value) if (is(T == class) || isPointer!T) { 323 | return inout(Option!T)(value); 324 | } 325 | 326 | /// ditto 327 | @nogc @safe pure nothrow 328 | inout(Option!T) option(T)(inout(Some!T) value) { 329 | return option(value._value); 330 | } 331 | 332 | // Test basic usage for Option 333 | unittest { 334 | class Klass {} 335 | struct Struct {} 336 | 337 | Option!Klass k = new Klass(); 338 | k = new Klass(); 339 | 340 | Klass k2 = k.get; 341 | 342 | Option!(Struct*) s = new Struct(); 343 | 344 | Struct* s1 = s.get; 345 | } 346 | 347 | // Test class hierarchies for Option 348 | unittest { 349 | class Animal {} 350 | class Dog : Animal {} 351 | 352 | Option!Animal a = new Animal(); 353 | a = new Dog(); 354 | 355 | auto d = new Dog(); 356 | 357 | d = cast(Dog) a.get; 358 | 359 | assert(d !is null); 360 | 361 | Option!Dog d2 = new Dog(); 362 | 363 | Animal a2 = d2.get; 364 | } 365 | 366 | // Test get across constness. 367 | unittest { 368 | class Klass {} 369 | 370 | Option!Klass m = new Klass(); 371 | const Option!Klass c = new const Klass(); 372 | immutable Option!Klass i = new immutable Klass(); 373 | 374 | auto someM = m.get(); 375 | auto someC = c.get(); 376 | auto someI = i.get(); 377 | 378 | assert(is(typeof(someM) == Some!Klass)); 379 | assert(is(typeof(someC) == const(Some!Klass))); 380 | assert(is(typeof(someI) == immutable(Some!Klass))); 381 | } 382 | 383 | // Test dynamicCast across constness. 384 | unittest { 385 | class Klass {} 386 | class SubKlass {} 387 | 388 | Option!Klass m = new Klass(); 389 | const Option!Klass c = new const Klass(); 390 | immutable Option!Klass i = new immutable Klass(); 391 | 392 | auto subM = m.dynamicCast!SubKlass; 393 | auto subC = c.dynamicCast!SubKlass; 394 | auto subI = i.dynamicCast!SubKlass; 395 | 396 | assert(is(typeof(subM) == Option!SubKlass)); 397 | assert(is(typeof(subC) == const(Option!SubKlass))); 398 | assert(is(typeof(subI) == immutable(Option!SubKlass))); 399 | } 400 | 401 | // Test .or, with the nice type qualifiers. 402 | unittest { 403 | class Klass {} 404 | 405 | @safe pure nothrow 406 | void runTest() { 407 | Option!Klass m; 408 | const Option!Klass c; 409 | immutable Option!Klass i; 410 | 411 | auto someM = m.or(()=> some(new Klass())); 412 | auto someC = c.or(()=> some(new const(Klass)())); 413 | auto someI = i.or(()=> some(new immutable(Klass)())); 414 | 415 | assert(is(typeof(someM) == Some!Klass)); 416 | assert(is(typeof(someC) == const(Some!Klass))); 417 | assert(is(typeof(someI) == immutable(Some!Klass))); 418 | 419 | auto someOtherM = m.or(()=> new Klass()); 420 | auto someOtherC = c.or(()=> new const(Klass)()); 421 | auto someOtherI = i.or(()=> new immutable(Klass)()); 422 | 423 | assert(is(typeof(someOtherM) == Some!Klass)); 424 | assert(is(typeof(someOtherC) == const(Some!Klass))); 425 | assert(is(typeof(someOtherI) == immutable(Some!Klass))); 426 | } 427 | 428 | runTest(); 429 | } 430 | 431 | // Test .or with subclasses 432 | unittest { 433 | class Klass {} 434 | class SubKlass : Klass {} 435 | 436 | @safe pure nothrow 437 | void runTest() { 438 | Option!Klass m; 439 | const Option!Klass c; 440 | immutable Option!Klass i; 441 | 442 | auto someM = m.or(()=> some(new SubKlass())); 443 | auto someC = c.or(()=> some(new const(SubKlass)())); 444 | auto someI = i.or(()=> some(new immutable(SubKlass)())); 445 | 446 | assert(is(typeof(someM) == Some!Klass)); 447 | assert(is(typeof(someC) == const(Some!Klass))); 448 | assert(is(typeof(someI) == immutable(Some!Klass))); 449 | 450 | auto someOtherM = m.or(()=> new SubKlass()); 451 | auto someOtherC = c.or(()=> new const(SubKlass)()); 452 | auto someOtherI = i.or(()=> new immutable(SubKlass)()); 453 | 454 | assert(is(typeof(someOtherM) == Some!Klass)); 455 | assert(is(typeof(someOtherC) == const(Some!Klass))); 456 | assert(is(typeof(someOtherI) == immutable(Some!Klass))); 457 | } 458 | 459 | runTest(); 460 | } 461 | 462 | // Test .or with bad functions 463 | unittest { 464 | class Klass {} 465 | 466 | @system 467 | Klass mutFunc() { 468 | if (1 == 2) { 469 | throw new Exception(""); 470 | } 471 | 472 | return new Klass(); 473 | } 474 | 475 | @system 476 | const(Klass) constFunc() { 477 | if (1 == 2) { 478 | throw new Exception(""); 479 | } 480 | 481 | return new const(Klass)(); 482 | } 483 | 484 | @system 485 | immutable(Klass) immutableFunc() { 486 | if (1 == 2) { 487 | throw new Exception(""); 488 | } 489 | 490 | return new immutable(Klass)(); 491 | } 492 | 493 | Option!Klass m; 494 | const Option!Klass c; 495 | immutable Option!Klass i; 496 | 497 | auto someM = m.or(&mutFunc); 498 | auto someC = c.or(&constFunc); 499 | auto someI = i.or(&immutableFunc); 500 | 501 | assert(is(typeof(someM) == Some!Klass)); 502 | assert(is(typeof(someC) == const(Some!Klass))); 503 | assert(is(typeof(someI) == immutable(Some!Klass))); 504 | } 505 | 506 | // Test setting Option from Some 507 | unittest { 508 | class Klass {} 509 | 510 | Option!Klass m = some(new Klass()); 511 | const Option!Klass c = some(new const Klass()); 512 | immutable Option!Klass i = some(new immutable Klass()); 513 | 514 | Option!Klass m2 = option(some(new Klass())); 515 | const Option!Klass c2 = option(some(new const Klass())); 516 | immutable Option!Klass i2 = option(some(new immutable Klass())); 517 | 518 | Option!Klass m3; 519 | 520 | m3 = some(new Klass()); 521 | } 522 | 523 | // Test isNull 524 | unittest { 525 | class Klass {} 526 | 527 | Option!Klass m; 528 | 529 | assert(m.isNull); 530 | 531 | m = new Klass(); 532 | 533 | assert(!m.isNull); 534 | } 535 | 536 | /** 537 | * This type represents a range over an optional type. 538 | * 539 | * This is a RandomAccessRange. 540 | */ 541 | struct OptionRange(T) if(is(T == class) || isPointer!T) { 542 | private: 543 | T _value; 544 | public: 545 | /** 546 | * Construct this range by wrapping a given value. 547 | * 548 | * Params: 549 | * value = The value to create the range with. 550 | */ 551 | @nogc @safe pure nothrow 552 | this(U)(U value) if(is(U : T)) { 553 | _value = value; 554 | } 555 | 556 | /// 557 | @nogc @trusted pure nothrow 558 | void popFront() 559 | in { 560 | assert(_value !is null, "Attempted to pop an empty range!"); 561 | } body { 562 | static if(is(T == const) || is(T == immutable)) { 563 | // Force the pointer held here into being null. 564 | *(cast(void**) &_value) = null; 565 | } else { 566 | _value = null; 567 | } 568 | } 569 | 570 | /// 571 | alias popBack = popFront; 572 | 573 | /// 574 | @nogc @safe pure nothrow 575 | @property inout(T) front() inout { 576 | return _value; 577 | } 578 | 579 | /// 580 | alias back = front; 581 | 582 | /// 583 | @nogc @safe pure nothrow 584 | @property bool empty() const { 585 | return _value is null; 586 | } 587 | 588 | /// 589 | @nogc @safe pure nothrow 590 | @property typeof(this) save() { 591 | return this; 592 | } 593 | 594 | /// 595 | @nogc @safe pure nothrow 596 | @property size_t length() const { 597 | return _value !is null ? 1 : 0; 598 | } 599 | 600 | /// 601 | @nogc @safe pure nothrow 602 | inout(T) opIndex(size_t index) inout 603 | in { 604 | assert(index <= length, "Index out of bounds!"); 605 | } body { 606 | return _value; 607 | } 608 | } 609 | 610 | /** 611 | * Create an OptionRange from an Option type. 612 | * 613 | * The range shall be empty when the option has no value, 614 | * and it shall have one item when the option has a value. 615 | * 616 | * Params: 617 | * optionalValue = An optional value. 618 | * 619 | * Returns: A range of 0 or 1 values. 620 | */ 621 | @nogc @safe pure nothrow 622 | OptionRange!T range(T)(Option!T optionalValue) { 623 | if (optionalValue.isNull) { 624 | return typeof(return).init; 625 | } 626 | 627 | return OptionRange!T(optionalValue.get); 628 | } 629 | 630 | /// ditto 631 | @nogc @trusted pure nothrow 632 | OptionRange!(const(T)) range(T)(const(Option!T) optionalValue) { 633 | return cast(typeof(return)) range(cast(Option!T)(optionalValue)); 634 | } 635 | 636 | /// ditto 637 | @nogc @trusted pure nothrow 638 | OptionRange!(immutable(T)) range(T)(immutable(Option!T) optionalValue) { 639 | return cast(typeof(return)) range(cast(Option!T)(optionalValue)); 640 | } 641 | 642 | // Test creating ranges from option types. 643 | unittest { 644 | class Klass {} 645 | 646 | Option!Klass m = new Klass(); 647 | const(Option!Klass) c = new const Klass(); 648 | immutable(Option!Klass) i = new immutable Klass(); 649 | 650 | auto mRange = m.range; 651 | auto cRange = c.range; 652 | auto iRange = i.range; 653 | 654 | assert(!mRange.empty); 655 | assert(!cRange.empty); 656 | assert(!iRange.empty); 657 | 658 | assert(mRange.length == 1); 659 | assert(cRange.length == 1); 660 | assert(iRange.length == 1); 661 | 662 | assert(mRange[0] is m.get); 663 | assert(cRange[0] is c.get); 664 | assert(iRange[0] is i.get); 665 | 666 | assert(mRange.front is mRange.back); 667 | assert(cRange.front is cRange.back); 668 | assert(iRange.front is iRange.back); 669 | 670 | auto mRangeSave = mRange.save; 671 | auto cRangeSave = cRange.save; 672 | auto iRangeSave = iRange.save; 673 | 674 | mRange.popFront(); 675 | cRange.popFront(); 676 | iRange.popFront(); 677 | 678 | assert(mRange.empty); 679 | assert(cRange.empty); 680 | assert(iRange.empty); 681 | 682 | assert(mRange.length == 0); 683 | assert(cRange.length == 0); 684 | assert(iRange.length == 0); 685 | 686 | assert(!mRangeSave.empty); 687 | assert(!cRangeSave.empty); 688 | assert(!iRangeSave.empty); 689 | } 690 | 691 | unittest { 692 | import std.range; 693 | 694 | // Test that all of the essential properties hold for this type. 695 | static assert(isInputRange!(OptionRange!(void*))); 696 | static assert(isForwardRange!(OptionRange!(void*))); 697 | static assert(isBidirectionalRange!(OptionRange!(void*))); 698 | static assert(isRandomAccessRange!(OptionRange!(void*))); 699 | static assert(!isInfinite!(OptionRange!(void*))); 700 | } 701 | 702 | 703 | // Test std.algorithm integration 704 | unittest { 705 | class Klass { 706 | int x = 3; 707 | } 708 | 709 | import std.algorithm; 710 | 711 | Option!Klass foo = new Klass(); 712 | 713 | auto squareSum(R)(R range) { 714 | return reduce!((x, y) => x + y)(0, range.map!(val => val.x * val.x)); 715 | } 716 | 717 | auto fooSum = squareSum(foo.range); 718 | 719 | import std.stdio; 720 | 721 | assert(fooSum == 9); 722 | 723 | Option!Klass bar; 724 | 725 | auto barSum = squareSum(bar.range); 726 | 727 | assert(barSum == 0); 728 | } 729 | 730 | unittest { 731 | class Klass {} 732 | class SubKlass : Klass {} 733 | class SubSubKlass : SubKlass {} 734 | 735 | Some!Klass nonNullValue = some(new Klass()); 736 | 737 | Option!Klass optionalValue; 738 | 739 | // You can check if the value is null. 740 | assert(optionalValue.isNull); 741 | 742 | // You can assign Some!T values to it. 743 | optionalValue = nonNullValue; 744 | 745 | assert(!optionalValue.isNull); 746 | 747 | // You can assign regular values, including derived types. 748 | // Dervied Some!T values will work too. 749 | optionalValue = new SubKlass(); 750 | 751 | assert(!optionalValue.isNull); 752 | 753 | // You can get the value out as a type Some!T from it and cast it 754 | // to the class type. The dynamic cast will be used. 755 | assert(cast(SubKlass) optionalValue.get() !is null); 756 | 757 | // Using the right dynamic cast means that the value from that can be null, 758 | // when the dynamic cast fails. 759 | assert(cast(SubSubKlass) optionalValue.get() is null); 760 | 761 | // Or create a new optional value with a cast, which will also work 762 | // when the optional value is null. 763 | // 764 | // This method will not exist for optional pointers. 765 | Option!SubSubKlass subValue = optionalValue.dynamicCast!SubSubKlass; 766 | 767 | assert(subValue.isNull); 768 | 769 | // We can assign back to a regular class reference. 770 | Klass regularReference; 771 | 772 | // When the optional value is null, the range will by empty. 773 | optionalValue = null; 774 | 775 | foreach(value; optionalValue.range) { 776 | regularReference = value; 777 | } 778 | 779 | assert(regularReference is null); 780 | 781 | // We there's a value, the range will have length 1. 782 | optionalValue = new Klass(); 783 | 784 | foreach(value; optionalValue.range) { 785 | regularReference = value; 786 | } 787 | 788 | assert(regularReference !is null); 789 | 790 | optionalValue = null; 791 | 792 | // Finally, we can use a method to use a default value. 793 | // If the default is null, an assertion error will be thrown 794 | // in debug mode. Any callable will work with .or, and the callable 795 | // can also return a Some!T type. 796 | Some!Klass someOtherValue = optionalValue.or(() => new SubKlass()); 797 | } 798 | -------------------------------------------------------------------------------- /source/dstruct/package.d: -------------------------------------------------------------------------------- 1 | module dstruct; 2 | 3 | public import dstruct.option; 4 | public import dstruct.weak_reference; 5 | public import dstruct.map; 6 | public import dstruct.set; 7 | public import dstruct.matrix; 8 | public import dstruct.graph; 9 | 10 | -------------------------------------------------------------------------------- /source/dstruct/set.d: -------------------------------------------------------------------------------- 1 | /** 2 | * This module defines a hash set data structure and various operations 3 | * on it. 4 | */ 5 | module dstruct.set; 6 | 7 | import std.traits : Unqual; 8 | 9 | import dstruct.support; 10 | import dstruct.map; 11 | 12 | /** 13 | * A garbage collected implementation of a hash set. 14 | * 15 | * Because this type is a struct, it can never be null. 16 | */ 17 | struct HashSet(T) if (isAssignmentCopyable!(Unqual!T)) { 18 | private: 19 | HashMap!(T, void[0]) _map; 20 | public: 21 | /** 22 | * Construct a set reserving a minimum of :minimumSize: space 23 | * for the bucket list. The actual space allocated may be some number 24 | * larger than the requested size, but it will be enough to fit 25 | * as many items as requested without another allocation. 26 | * 27 | * Params: 28 | * minimumSize = The minimum size for the hashmap. 29 | */ 30 | @safe pure nothrow 31 | this(size_t minimumSize) { 32 | _map = typeof(_map)(minimumSize); 33 | } 34 | 35 | /** 36 | * Add an element to this set if needed. 37 | * 38 | * Params: 39 | * value = The value to add to the set. 40 | */ 41 | @safe pure nothrow 42 | void add(ref T value) { 43 | _map[value] = (void[0]).init; 44 | } 45 | 46 | 47 | /// ditto 48 | @safe pure nothrow 49 | void add(T value) { 50 | add(value); 51 | } 52 | 53 | /// A HashSet is an OutputRange. 54 | alias put = add; 55 | 56 | /** 57 | * Remove an element from this set if present. 58 | * 59 | * Params: 60 | * value = The value to remove from the set. 61 | * 62 | * Returns: true if a value was removed. 63 | */ 64 | @nogc @safe pure nothrow 65 | bool remove(ref T value) { 66 | return _map.remove(value); 67 | } 68 | 69 | /// ditto 70 | @nogc @safe pure nothrow 71 | bool remove(T value) { 72 | return remove(value); 73 | } 74 | 75 | /** 76 | * Returns: The number of elements in this set. 77 | */ 78 | @nogc @safe pure nothrow 79 | @property size_t length() const { 80 | return _map.length; 81 | } 82 | 83 | /** 84 | * Returns: True if this set is empty. 85 | */ 86 | @nogc @safe pure nothrow 87 | @property bool empty() const { 88 | return _map.empty; 89 | } 90 | 91 | /** 92 | * Implement boolean conversion for a set. 93 | * 94 | * Returns: True if this set is not empty. 95 | */ 96 | @nogc @safe pure nothrow 97 | bool opCast(T: bool)() const { 98 | return !empty; 99 | } 100 | 101 | /** 102 | * Provide the 'in' operator for sets. 103 | * 104 | * Test if the given value is present in the set. 105 | * 106 | * Params: 107 | * value = The value to test with. 108 | * 109 | * 110 | * Returns: true if the value is in the set. 111 | */ 112 | @nogc @safe pure nothrow 113 | bool opBinaryRight(string op, T)(ref T value) const if(op == "in") { 114 | return cast(bool)(value in _map); 115 | } 116 | 117 | /// ditto 118 | @nogc @safe pure nothrow 119 | bool opBinaryRight(string op, T)(T value) const if(op == "in") { 120 | return opBinaryRight!("in", T)(value); 121 | } 122 | 123 | /** 124 | * Returns: True if two sets contain all equal values. 125 | */ 126 | bool opEquals(U)(const(HashSet!U) otherSet) const 127 | if (is(U : T) || is(T : U)) { 128 | static if (is(U : T)) { 129 | if (this.length != otherSet.length) { 130 | return false; 131 | } 132 | 133 | foreach(value; otherSet._map.byKey()) { 134 | if (value !in this) { 135 | return false; 136 | } 137 | } 138 | 139 | return true; 140 | } else { 141 | // Implement equality the other way by flipping things around. 142 | return otherSet == this; 143 | } 144 | } 145 | 146 | static if(isDupable!T) { 147 | /** 148 | * Returns: A mutable copy of this set. 149 | */ 150 | @safe pure nothrow 151 | HashSet!T dup() const { 152 | HashSet!T newSet; 153 | newSet._map = _map.dup; 154 | 155 | return newSet; 156 | } 157 | } 158 | } 159 | 160 | // Test that is is not possible to create a set with an element type which 161 | // cannot be copy assigned. 162 | unittest { 163 | struct NonCopyable { @disable this(this); } 164 | 165 | assert(!__traits(compiles, HashSet!NonCopyable)); 166 | assert(__traits(compiles, HashSet!int)); 167 | } 168 | 169 | // Test add and in. 170 | unittest { 171 | HashSet!int set; 172 | 173 | set.add(3); 174 | 175 | assert(3 in set); 176 | } 177 | 178 | // Test !in 179 | unittest { 180 | HashSet!int set; 181 | 182 | set.add(3); 183 | 184 | assert(4 !in set); 185 | } 186 | 187 | // Test remove 188 | unittest { 189 | HashSet!int set; 190 | 191 | set.add(4); 192 | 193 | assert(set.remove(4)); 194 | assert(!set.remove(3)); 195 | } 196 | 197 | // Test set length 198 | unittest { 199 | HashSet!int set; 200 | 201 | set.add(1); 202 | set.add(2); 203 | set.add(3); 204 | 205 | assert(set.length == 3); 206 | } 207 | 208 | // Test empty length 209 | unittest { 210 | HashSet!int set; 211 | 212 | assert(set.length == 0); 213 | } 214 | 215 | // Set cast(bool) for a set 216 | unittest { 217 | @safe pure nothrow 218 | void runTest() { 219 | HashSet!int set; 220 | 221 | @nogc @safe pure nothrow 222 | void runNoGCPart1(typeof(set) set) { 223 | if (set) { 224 | assert(false, "cast(bool) failed for an empty set"); 225 | } 226 | } 227 | 228 | @nogc @safe pure nothrow 229 | void runNoGCPart2(typeof(set) set) { 230 | if (!set) { 231 | assert(false, "cast(bool) failed for an non-empty set"); 232 | } 233 | } 234 | 235 | @nogc @safe pure nothrow 236 | void runNoGCPart3(typeof(set) set) { 237 | if (set) { 238 | assert(false, "cast(bool) failed for an empty set"); 239 | } 240 | } 241 | 242 | runNoGCPart1(set); 243 | set.add(1); 244 | runNoGCPart2(set); 245 | set.remove(1); 246 | runNoGCPart3(set); 247 | } 248 | 249 | runTest(); 250 | } 251 | 252 | // Test basic equality. 253 | unittest { 254 | @safe pure nothrow 255 | void runTest() { 256 | HashSet!int leftSet; 257 | leftSet.add(2); 258 | leftSet.add(3); 259 | 260 | HashSet!int rightSet; 261 | rightSet.add(2); 262 | rightSet.add(3); 263 | 264 | // Test that @nogc works. 265 | @nogc @safe pure nothrow 266 | void runNoGCPart(typeof(leftSet) leftSet, typeof(rightSet) rightSet) { 267 | assert(leftSet == rightSet); 268 | } 269 | } 270 | 271 | runTest(); 272 | } 273 | 274 | // Test implicit conversion equality left to right. 275 | unittest { 276 | HashSet!int leftSet; 277 | leftSet.add(2); 278 | leftSet.add(3); 279 | 280 | HashSet!float rightSet; 281 | rightSet.add(2.0); 282 | rightSet.add(3.0); 283 | 284 | assert(leftSet == rightSet); 285 | } 286 | 287 | // Test implicit conversion equality right to left. 288 | unittest { 289 | HashSet!float leftSet; 290 | leftSet.add(2.0); 291 | leftSet.add(3.0); 292 | 293 | HashSet!int rightSet; 294 | rightSet.add(2); 295 | rightSet.add(3); 296 | 297 | assert(leftSet == rightSet); 298 | } 299 | 300 | /** 301 | * Produce a range through all the entries of a set. 302 | * 303 | * Params: 304 | * set = A set. 305 | * 306 | * Returns: 307 | * A ForwardRange over all the entries in the set. 308 | */ 309 | @nogc @safe pure nothrow 310 | auto entries(U)(auto ref inout(HashSet!U) set) { 311 | return set._map.byKey(); 312 | } 313 | 314 | unittest { 315 | const(HashSet!int) createSet() { 316 | HashSet!int set; 317 | 318 | set.add(1); 319 | set.add(2); 320 | 321 | return set; 322 | } 323 | 324 | auto set = createSet(); 325 | auto newSet = set.dup; 326 | // Test r-values. 327 | auto thirdSet = createSet().dup(); 328 | 329 | assert(set == newSet); 330 | } 331 | 332 | unittest { 333 | import std.range; 334 | import std.algorithm; 335 | 336 | HashSet!int set; 337 | 338 | repeat(cast(int) 3).take(3).copy(&set); 339 | repeat(cast(int) 4).take(3).copy(&set); 340 | 341 | assert(set.length == 2); 342 | assert(3 in set); 343 | assert(4 in set); 344 | } 345 | -------------------------------------------------------------------------------- /source/dstruct/support.d: -------------------------------------------------------------------------------- 1 | module dstruct.support; 2 | 3 | import std.traits; 4 | 5 | // Define a do-nothing nogc attribute so @nogc can be used, 6 | // but functions tagged with it will still compile in 7 | // older D compiler versions. 8 | static if (__VERSION__ < 2066) { enum nogc = 1; } 9 | 10 | /** 11 | * true if a type T can be duplicated through some means. 12 | */ 13 | template isDupable(T) { 14 | enum isDupable = 15 | // Implicit conversion from const to non-const is allowed. 16 | is(const(Unqual!T) : Unqual!T); 17 | } 18 | 19 | enum isAssignmentCopyable(T) = is(typeof( 20 | (inout int _ = 0) { 21 | T value = T.init; 22 | 23 | T value2 = value; 24 | } 25 | )); 26 | 27 | -------------------------------------------------------------------------------- /source/dstruct/weak_reference.d: -------------------------------------------------------------------------------- 1 | /** 2 | * This module implements weak references. 3 | * 4 | * Authors: Alex Rønne Petersen, w0rp 5 | * 6 | * Credit and thanks goes to Alex Rønne Petersen for implementing 7 | * another weak reference type. This type is pretty much a fork of his 8 | * code, some of it still surviving. 9 | */ 10 | module dstruct.weak_reference; 11 | 12 | import core.memory; 13 | import core.atomic; 14 | 15 | private alias void delegate(Object) DEvent; 16 | private extern (C) void rt_attachDisposeEvent(Object h, DEvent e); 17 | private extern (C) void rt_detachDisposeEvent(Object h, DEvent e); 18 | 19 | private alias extern(C) void 20 | function(Object, DEvent) @system pure nothrow 21 | PureEventFunc; 22 | 23 | /** 24 | * This class implements a weak reference wrapper for class T. 25 | * 26 | * A weak reference will not prevent the object from being collected 27 | * in a garbage collection cycle. If and when the object is collected, 28 | * the internal reference to object will become null. 29 | * 30 | * This weak reference wrapper is thread safe. 31 | * 32 | * Params: 33 | * T = The type of class. 34 | */ 35 | final class WeakReference(T) if(is(T == class)) { 36 | private: 37 | shared size_t _ptr; 38 | 39 | @trusted pure nothrow 40 | private void freeWrapper() { 41 | if (_ptr == 0) { 42 | // We already cleaned up, don't do it again. 43 | return; 44 | } 45 | 46 | // Detach the previously attached dispose event, it is done. 47 | (cast(PureEventFunc) &rt_detachDisposeEvent)( 48 | cast(Object) cast(void*) _ptr, 49 | &disposed 50 | ); 51 | 52 | // Set the invalid pointer to null so we know it's gone. 53 | atomicStore(_ptr, cast(size_t) 0); 54 | } 55 | 56 | private void disposed(Object) { 57 | freeWrapper(); 58 | } 59 | public: 60 | /** 61 | * Create a weak reference wrapper for a given object. 62 | * 63 | * Params: 64 | * object = The object to hold a reference to. 65 | */ 66 | @trusted pure nothrow 67 | this(T object) { 68 | if (object is null) { 69 | // No work needs to be done for null. 70 | return; 71 | } 72 | 73 | // Set the pointer atomically in a size_t so it's not a valid pointer. 74 | // Use cast(void**) to avoid opCast problems. 75 | atomicStore(_ptr, cast(size_t) cast(void**) object); 76 | 77 | // Stop the GC from scanning inside this class. 78 | // This will make the interior reference a weak reference. 79 | GC.setAttr(cast(void*) this, GC.BlkAttr.NO_SCAN); 80 | 81 | // Call a special D runtime function for nulling our reference 82 | // to the object when the object is destroyed. 83 | (cast(PureEventFunc) &rt_attachDisposeEvent)( 84 | cast(Object) object, 85 | &disposed 86 | ); 87 | } 88 | 89 | @trusted pure nothrow 90 | ~this() { 91 | freeWrapper(); 92 | } 93 | 94 | /** 95 | * Return the referenced object held in this weak reference wrapper. 96 | * If and when the object is collected, this function will return null. 97 | * 98 | * Returns: The referenced object. 99 | */ 100 | @trusted pure nothrow 101 | T get() inout { 102 | auto ptr = cast(void*) atomicLoad(_ptr); 103 | 104 | // Check if the object is still alive before we return it. 105 | // It might be killed in another thread. 106 | return GC.addrOf(ptr) ? cast(T) ptr : null; 107 | } 108 | 109 | /** 110 | * Params: 111 | * other = Another object. 112 | * 113 | * Returns: True the other object is a weak reference to the same object. 114 | */ 115 | @safe pure nothrow 116 | override bool opEquals(Object other) const { 117 | if (other is this) { 118 | return true; 119 | } 120 | 121 | if (auto otherWeak = cast(WeakReference!T) other) { 122 | return _ptr == otherWeak._ptr; 123 | } 124 | 125 | return false; 126 | } 127 | 128 | /// ditto 129 | @trusted pure nothrow 130 | final bool opEquals(const(Object) other) const { 131 | return this.opEquals(cast(Object) other); 132 | } 133 | } 134 | 135 | /** 136 | * This is a convenience function for creating a new 137 | * weak reference wrapper for a given object. 138 | * 139 | * Params: 140 | * object = The object to create a reference for. 141 | * 142 | * Returns: A new weak reference to the given object. 143 | */ 144 | WeakReference!T weak(T)(T object) if(is(T == class)) { 145 | return new WeakReference!T(object); 146 | } 147 | 148 | // Test that the reference is held. 149 | unittest { 150 | class SomeType { } 151 | 152 | SomeType x = new SomeType(); 153 | auto y = weak(x); 154 | 155 | assert(y.get() is x); 156 | } 157 | 158 | // Test that the reference is removed when the object is destroyed. 159 | unittest { 160 | class SomeType { } 161 | 162 | SomeType x = new SomeType(); 163 | auto y = weak(x); 164 | 165 | destroy(x); 166 | 167 | assert(y.get() is null); 168 | } 169 | 170 | // Test equality based on the reference held in the wrappers. 171 | unittest { 172 | class SomeType { } 173 | 174 | SomeType x = new SomeType(); 175 | auto y = weak(x); 176 | auto z = weak(x); 177 | 178 | assert(y !is z); 179 | assert(y == z); 180 | } 181 | 182 | // Test equality after nulling things. 183 | unittest { 184 | class SomeType { } 185 | 186 | SomeType x = new SomeType(); 187 | auto y = weak(x); 188 | auto z = weak(x); 189 | 190 | destroy(x); 191 | 192 | assert(y !is z); 193 | assert(y == z); 194 | } 195 | 196 | // Test tail-const weak references. 197 | unittest { 198 | class SomeType { } 199 | 200 | const x = new SomeType(); 201 | const y = new SomeType(); 202 | 203 | auto z = weak(x); 204 | z = weak(y); 205 | } 206 | --------------------------------------------------------------------------------