1 module collections.hashset; 2 3 import collections.treemap; 4 import collections.linkedlist; 5 import std.traits; 6 //import std.digest.murmurhash; 7 public import collections.commons; 8 /** 9 * Implements a hashset, either using collections.treemap or collections.linkedlist as a set for backend. 10 * Cannot be accessed directly, instead it can check whether an element is within it or not. 11 * Backend's foreach capability is exposed to iterate over hashcodes. 12 */ 13 public struct HashSet(K, alias hashFunc = defaultHash!K, alias less = "a < b") { 14 alias HashType = ReturnType!hashFunc; 15 alias Backend = TreeMap!(HashType, void, true, less); 16 private Backend backend; 17 /** 18 * Creates a HashSet from a compatible range. 19 */ 20 public this(R)(R range) @safe pure nothrow { 21 foreach(K key; range) put(key); 22 } 23 /** 24 * Puts an item into the hash set, then returns the generated hashcode. 25 */ 26 HashType put(K key) @safe pure nothrow { 27 return backend.put(hashFunc(key)); 28 } 29 /** 30 * Puts a hashcode into the hash set. 31 */ 32 private HashType put(HashType key) @safe pure nothrow { 33 return backend.put(key); 34 } 35 /** 36 * Returns true if the element exists within the set, false otherwise. 37 */ 38 bool has(K key) @nogc @safe pure nothrow { 39 return backend.has(hashFunc(key)); 40 } 41 auto opBinaryRight(string op)(const K key) @nogc @safe pure nothrow { 42 static if (op == "in") { 43 return has(key); 44 } else static assert(0, "Operator not supported!"); 45 } 46 /** 47 * Returns true if the element exists within the set, false otherwise. 48 */ 49 bool has(HashType key) @nogc @safe pure nothrow { 50 return backend.has(key); 51 } 52 auto opBinaryRight(string op)(const K key) @nogc @safe pure nothrow { 53 static if (op == "in") { 54 return has(key); 55 } else static assert(0, "Operator not supported!"); 56 } 57 /** 58 * Returns the amount of elements found in the set. 59 */ 60 size_t hasRange(R)(R range) @nogc @safe pure nothrow { 61 size_t result; 62 foreach (key; range) { 63 if(has(key)) result++; 64 } 65 return result; 66 } 67 /** 68 * Removes an element by match. 69 * Returns the hashcode if found, or uint.init if not. 70 */ 71 HashType removeByElem(K key) @safe pure nothrow { 72 return backend.removeByElem(hashFunc(key)); 73 } 74 /** 75 * Removes an element by hashcode. 76 */ 77 HashType removeByElem(HashType key) @safe pure nothrow { 78 return backend.removeByElem(key); 79 } 80 alias remove = removeByElem; 81 /** 82 * Set operators. 83 * Enables math operations on sets, like unions and intersections. 84 * Could work on ranges in general as long as they implement some basic functions, like iteration. 85 */ 86 HashSet!(K, hashFunc, less) opBinary(string op, R)(R rhs) { 87 static if(op == "|" || op == "~") {//Union 88 HashSet!(K, hashFunc, less) result; 89 foreach(e ; backend) 90 result.put(e); 91 foreach(e ; rhs) 92 result.put(e); 93 return result; 94 } else static if(op == "&" || op == "*") {//Intersection 95 HashSet!(K, hashFunc, less) result; 96 foreach(e ; rhs){ 97 if(backend.has(e)) result.put(e); 98 } 99 return result; 100 } else static if(op == "-" || op == "/") {//Complement 101 HashSet!(K, hashFunc, less) result; 102 foreach(e ; backend) 103 result.put(e); 104 foreach(e ; rhs){ 105 result.removeByElem(e); 106 } 107 return result; 108 } else static if(op == "^"){//Difference 109 HashSet!(K, hashFunc, less) result = this | rhs; 110 HashSet!(K, hashFunc, less) common = this & rhs; 111 foreach(e ; common){ 112 result.removeByElem(e); 113 } 114 return result; 115 } else static assert(0, "Operator " ~ op ~ "not supported"); 116 } 117 /** 118 * Set operators. 119 */ 120 HashSet!(K, hashFunc, less) opOpAssign(string op)(E value) { 121 static if(op == "~=") {//Append 122 put(value); 123 } else static if(op == "-=" || op == "/=") { 124 removeByElem(value); 125 } else static assert(0, "Operator " ~ op ~ "not supported"); 126 return this; 127 } 128 /** 129 * Set operators. 130 */ 131 HashSet!(K, hashFunc, less) opOpAssign(string op, R)(R range) { 132 static if(op == "~=" || op == "|=") {//Append 133 foreach(val; range) 134 put(val); 135 } else static if(op == "-=" || op == "/=") { 136 foreach(val; range) 137 removeByElem(val); 138 } else static assert(0, "Operator " ~ op ~ "not supported"); 139 return this; 140 } 141 private int opApply(scope int delegate(HashType) dg) { 142 return backend.opApply(dg); 143 } 144 } 145 146 unittest { 147 alias MatchFinder = HashSet!(string); 148 MatchFinder set = MatchFinder(["AAAAAA","BBBBBB","CCCCCC","DDDDDD"]); 149 assert(set.has("AAAAAA")); 150 assert(set.has("BBBBBB")); 151 assert(set.has("CCCCCC")); 152 assert(set.has("DDDDDD")); 153 assert(!set.has("000000")); 154 } 155 156 unittest { ///Test set operators 157 alias MatchFinder = HashSet!(string); 158 MatchFinder a = MatchFinder(["AAAA", "BBBB", "CCCC", "DDDD", "EEEE"]), b = MatchFinder(["DDDD", "EEEE", "FFFF", "GGGG"]); 159 MatchFinder c = MatchFinder(["BBBB", "CCCC", "EEEE", "GGGG"]), d = MatchFinder(["AAAA", "EEEE", "BBBB", "GGGG"]); 160 MatchFinder union_ab = a | b, union_ad = a | d; 161 assert(union_ab.hasRange(["AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG"]) == 7); 162 assert(union_ad.hasRange(["AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "GGGG"]) == 6); 163 MatchFinder intrsctn_ab = a & b, intrsctn_cd = c & d, intrsctn_ac = a & c; 164 MatchFinder diff_ab = a ^ b; 165 MatchFinder comp_ab = a - b; 166 assert(intrsctn_ab.hasRange(["DDDD", "EEEE"]) == 2); 167 assert(intrsctn_ac.hasRange(["BBBB", "CCCC", "EEEE"]) == 3); 168 assert(intrsctn_cd.hasRange(["BBBB", "GGGG"]) == 2); 169 assert(diff_ab.hasRange(["AAAA", "BBBB", "CCCC", "FFFF", "GGGG"]) == 5); 170 assert(comp_ab.hasRange(["AAAA", "BBBB", "CCCC"]) == 3); 171 }