1 module collections.linkedhashset; 2 3 import collections.commons; 4 import collections.linkedlist; 5 import std.traits : ReturnType, hasFunctionAttributes; 6 7 /** 8 * Implements linked hashset with a linked set as a backend. 9 * Uses an equal method for comparison, meaning it can use complex keys. 10 * Has poorer access times compared to the hashset with a binary search tree as a backend, but less costly insertion. 11 * Elements cannot be accessed directly, instead it can check whether an element is within it or not. 12 * Backend's foreach capability is exposed to iterate over hashcodes. 13 */ 14 public struct LinkedHashSet(K, alias hashFunc = defaultHash128!(K), alias equal = "a == b") { 15 static enum bool nogcIndexing = hasFunctionAttributes!(hashFunc, "@nogc"); 16 alias HashType = ReturnType!hashFunc; 17 private LinkedList!(HashType, false, equal) backend; 18 /** 19 * Creates a LinkedHashSet from a compatible range. 20 */ 21 public this(R)(R range) @safe pure nothrow { 22 foreach(K key; range) put(key); 23 } 24 /** 25 * Puts an item into the hash set, then returns the generated hashcode. 26 */ 27 HashType put(K key) @safe pure nothrow { 28 return backend.put(hashFunc(key)); 29 } 30 /** 31 * Puts a hashcode into the hash set. 32 */ 33 private HashType put(HashType key) @safe pure nothrow { 34 return backend.put(key); 35 } 36 /** 37 * Returns true if the element exists within the set, false otherwise. 38 */ 39 bool has(K key) @safe pure nothrow { 40 return backend.has(hashFunc(key)); 41 } 42 auto opBinaryRight(string op)(const K key) @safe pure nothrow { 43 static if (op == "in") { 44 return has(key); 45 } else static assert(0, "Operator not supported!"); 46 } 47 /** 48 * Returns true if the element exists within the set, false otherwise. 49 */ 50 bool has(HashType key) @safe pure nothrow { 51 return backend.has(key); 52 } 53 auto opBinaryRight(string op)(const HashType key) @safe pure nothrow { 54 static if (op == "in") { 55 return has(key); 56 } else static assert(0, "Operator not supported!"); 57 } 58 /** 59 * Returns the amount of elements found in the set. 60 */ 61 size_t hasRange(R)(R range) @safe pure nothrow { 62 size_t result; 63 foreach (key; range) { 64 if(has(key)) result++; 65 } 66 return result; 67 } 68 /** 69 * Removes an element by match. 70 * Returns the hashcode if found, or uint.init if not. 71 */ 72 HashType removeByElem(K key) @safe pure nothrow { 73 return backend.removeByElem(hashFunc(key)); 74 } 75 /** 76 * Removes an element by hashcode. 77 */ 78 HashType removeByElem(HashType key) @safe pure nothrow { 79 return backend.removeByElem(key); 80 } 81 alias remove = removeByElem; 82 /** 83 * Set operators. 84 * Enables math operations on sets, like unions and intersections. 85 * Could work on ranges in general as long as they implement some basic functions, like iteration. 86 */ 87 LinkedHashSet!(K, hashFunc, equal) opBinary(string op, R)(R rhs) { 88 static if(op == "|" || op == "~") {//Union 89 LinkedHashSet!(K, hashFunc, equal) result; 90 foreach(e ; backend) 91 result.put(e); 92 foreach(e ; rhs) 93 result.put(e); 94 return result; 95 } else static if(op == "&" || op == "*") {//Intersection 96 LinkedHashSet!(K, hashFunc, equal) result; 97 foreach(e ; rhs){ 98 if(backend.has(e)) result.put(e); 99 } 100 return result; 101 } else static if(op == "-" || op == "/") {//Complement 102 LinkedHashSet!(K, hashFunc, equal) result; 103 foreach(e ; backend) 104 result.put(e); 105 foreach(e ; rhs){ 106 result.removeByElem(e); 107 } 108 return result; 109 } else static if(op == "^"){//Difference 110 LinkedHashSet!(K, hashFunc, equal) result = this | rhs; 111 LinkedHashSet!(K, hashFunc, equal) common = this & rhs; 112 foreach(e ; common){ 113 result.removeByElem(e); 114 } 115 return result; 116 } else static assert(0, "Operator " ~ op ~ "not supported"); 117 } 118 /** 119 * Set operators. 120 */ 121 LinkedHashSet!(K, hashFunc, equal) opOpAssign(string op)(E value) { 122 static if(op == "~=") {//Append 123 put(value); 124 } else static if(op == "-=" || op == "/=") { 125 removeByElem(value); 126 } else static assert(0, "Operator " ~ op ~ "not supported"); 127 return this; 128 } 129 /** 130 * Set operators. 131 */ 132 LinkedHashSet!(K, hashFunc, equal) opOpAssign(string op, R)(R range) { 133 static if(op == "~=" || op == "|=") {//Append 134 foreach(val; range) 135 put(val); 136 } else static if(op == "-=" || op == "/=") { 137 foreach(val; range) 138 removeByElem(val); 139 } else static assert(0, "Operator " ~ op ~ "not supported"); 140 return this; 141 } 142 /** 143 * Returns the element at the front. 144 */ 145 @property HashType front() @nogc @safe nothrow pure { 146 return backend.front; 147 } 148 /** 149 * Returns the element at begin and increments the position by one. 150 */ 151 HashType moveFront() @nogc @safe nothrow pure { 152 return backend.moveFront; 153 } 154 /** 155 * Increments the front iteration position by one 156 */ 157 void popFront() @nogc @safe nothrow pure { 158 backend.popFront(); 159 } 160 /** 161 * Returns true when the end of the list have been reached. 162 */ 163 @property bool empty() @nogc @safe nothrow pure { 164 return backend.empty; 165 } 166 /** 167 * Returns a copy of this struct. 168 */ 169 @property auto save() @nogc @safe nothrow pure { 170 return this; 171 } 172 } 173 174 unittest { 175 alias MatchFinder = LinkedHashSet!(string); 176 MatchFinder set = MatchFinder(["AAAAAA","BBBBBB","CCCCCC","DDDDDD"]); 177 assert(set.has("AAAAAA")); 178 assert("AAAAAA" in set); 179 assert(set.has("BBBBBB")); 180 assert(set.has("CCCCCC")); 181 assert(set.has("DDDDDD")); 182 assert(!set.has("000000")); 183 } 184 185 unittest { ///Test set operators 186 alias MatchFinder = LinkedHashSet!(string); 187 MatchFinder a = MatchFinder(["AAAA", "BBBB", "CCCC", "DDDD", "EEEE"]), b = MatchFinder(["DDDD", "EEEE", "FFFF", "GGGG"]); 188 MatchFinder c = MatchFinder(["BBBB", "CCCC", "EEEE", "GGGG"]), d = MatchFinder(["AAAA", "EEEE", "BBBB", "GGGG"]); 189 MatchFinder union_ab = a | b, union_ad = a | d; 190 assert(union_ab.hasRange(["AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG"]) == 7); 191 assert(union_ad.hasRange(["AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "GGGG"]) == 6); 192 MatchFinder intrsctn_ab = a & b, intrsctn_cd = c & d, intrsctn_ac = a & c; 193 MatchFinder diff_ab = a ^ b; 194 MatchFinder comp_ab = a - b; 195 assert(intrsctn_ab.hasRange(["DDDD", "EEEE"]) == 2); 196 assert(intrsctn_ac.hasRange(["BBBB", "CCCC", "EEEE"]) == 3); 197 assert(intrsctn_cd.hasRange(["BBBB", "GGGG"]) == 2); 198 assert(diff_ab.hasRange(["AAAA", "BBBB", "CCCC", "FFFF", "GGGG"]) == 5); 199 assert(comp_ab.hasRange(["AAAA", "BBBB", "CCCC"]) == 3); 200 }