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 }