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 }