1 /// Ordered Associative Array
2 module nwnlibd.orderedaa;
3 
4 import std.typecons;
5 debug import std.stdio: writeln;
6 
7 template isOpApplyDg(DG, KEY, VALUE) {
8 	import std.traits;
9 	static if (is(DG == delegate) && is(ReturnType!DG : int)) {
10 		private alias PTT = ParameterTypeTuple!(DG);
11 		private alias PSCT = ParameterStorageClassTuple!(DG);
12 		private alias STC = ParameterStorageClass;
13 		// Just a value
14 		static if (PTT.length == 1) {
15 			enum isOpApplyDg = (is(PTT[0] == VALUE));
16 		} else static if (PTT.length == 2) {
17 			enum isOpApplyDg = (is(PTT[0] == KEY))
18 				&& (is(PTT[1] == VALUE));
19 		} else
20 			enum isOpApplyDg = false;
21 	} else {
22 		enum isOpApplyDg = false;
23 	}
24 }
25 
26 
27 /// Ordered Associative Array
28 struct OrderedAA(KEY, VALUE){
29 	private alias THIS = OrderedAA!(KEY, VALUE);
30 	private alias DataContainer = Tuple!(KEY, "key", VALUE, "value");
31 
32 	///
33 	@property THIS dup() inout{
34 		THIS ret;
35 		ret.data.length = data.length;
36 		foreach(i, ref d ; ret.data){
37 			import std.meta: AliasSeq;
38 			foreach(TUPLEI ; AliasSeq!(0,1)){
39 				static if(is(typeof(d[TUPLEI] = data[i][TUPLEI].dup)))
40 					d[TUPLEI] = data[i][TUPLEI].dup;
41 				else static if(is(typeof(d[TUPLEI] = data[i][TUPLEI])))
42 					d[TUPLEI] = data[i][TUPLEI];
43 				else
44 					static assert(0, "Cannot duplicate type "~typeof(ret.data[0][TUPLEI]).stringof);
45 			}
46 		}
47 
48 		foreach(ref k, ref v ; map){
49 			static if(is(typeof(ret.map[k] = v.dup)))
50 				ret.map[k] = v.dup;
51 			else static if(is(typeof(ret.map[k] = v)))
52 				ret.map[k] = v;
53 			else
54 				static assert(0, "Cannot duplicate type "~typeof(ret.data[0][TUPLEI]).stringof);
55 		}
56 
57 		return ret;
58 	}
59 
60 	///
61 	bool opEquals(const THIS rhs) const{
62 		if(length != rhs.length)
63 			return false;
64 
65 		foreach(idx, ref kv ; data){
66 			if(kv.key!=rhs.data[idx].key || kv.value!=rhs.data[idx].value)
67 				return false;
68 		}
69 		return true;
70 	}
71 
72 	///
73 	@property bool empty() const{
74 		return data.length==0;
75 	}
76 	///
77 	@property size_t length() const{
78 		return data.length;
79 	}
80 
81 	///
82 	void rehash(){
83 		map.rehash;
84 	}
85 
86 	///
87 	void remove(KEY key)
88 	{
89 		immutable idx = map[key];
90 		if(idx+1<length)
91 			data = data[0..idx]~data[idx+1..$];
92 		else
93 			data = data[0..idx];
94 		map.remove(key);
95 		foreach(ref k, ref v ; map){
96 			if(v>idx) v--;
97 		}
98 	}
99 
100 	///
101 	void clear(){
102 		data.length = 0;
103 		map.clear;
104 	}
105 
106 	///
107 	void opIndexAssign(VALUE value, KEY key){
108 		if(auto idx = key in map){
109 			data[*idx].value = value;
110 		}
111 		else{
112 			map[key] = data.length;
113 			data ~= DataContainer(key, value);
114 		}
115 	}
116 	///
117 	ref inout(VALUE) opIndex(KEY key) inout{
118 		immutable idx = map[key];
119 		return data[idx].value;
120 	}
121 
122 	///
123 	inout(VALUE)* opBinaryRight(string op)(KEY key) inout if(op == "in"){
124 		auto idx = key in map;
125 		if(idx is null)
126 			return null;
127 
128 		return &(data[*idx].value);
129 	}
130 
131 	///
132 	int opApply(DG)(scope DG dg) if(isOpApplyDg!(DG, KEY, VALUE)){
133 		import std.traits : arity;
134 
135 		foreach(ref kv ; data){
136 			static if(arity!dg == 1){
137 				if(auto res = dg(kv.value))
138 					return res;
139 			}
140 			else static if(arity!dg == 2){
141 				if(auto res = dg(kv.key, kv.value))
142 					return res;
143 			}
144 			else
145 				static assert(0);
146 		}
147 		return 0;
148 	}
149 
150 	///
151 	auto ref byKeyValue() inout{
152 		return data;
153 	}
154 
155 
156 	/// This function should not be used by sane people.
157 	///
158 	/// Workaround for storing multiple values for a same key like in half-broken nwn2 gff files.
159 	/// Only the newest value will be accessible using its key.
160 	/// The older value will only be accessible using byKeyValue or iterating with foreach.
161 	void dirtyAppendKeyValue(KEY key, VALUE value){
162 		map[key] = data.length;
163 		data ~= DataContainer(key, value);
164 	}
165 
166 
167 
168 private:
169 	DataContainer[] data;
170 	size_t[KEY] map;
171 
172 	invariant{
173 		foreach(ref k, ref v ; map){
174 			assert(v<data.length && data[v].key == k, "Corrupted OrderedAA");
175 		}
176 	}
177 }
178 
179 unittest{
180 	OrderedAA!(string, string) orderedAA;
181 	orderedAA["Hello"] = "World";
182 	orderedAA["Foo"] = "Bar";
183 	orderedAA["Yolo"] = "Yay";
184 
185 	size_t i = 0;
186 	foreach(string k, string v ; orderedAA){
187 		switch(i++){
188 			case 0: assert(k=="Hello"); assert(v=="World"); break;
189 			case 1: assert(k=="Foo");   assert(v=="Bar");   break;
190 			case 2: assert(k=="Yolo");  assert(v=="Yay");   break;
191 			default: assert(0);
192 		}
193 	}
194 
195 	assert(*("Hello" in orderedAA)=="World");
196 	assert(("nothing" in orderedAA) is null);
197 
198 	auto orderedAA2 = orderedAA.dup;
199 	assert(orderedAA2 == orderedAA);
200 
201 	orderedAA2["Foo"] = "42";
202 	assert(orderedAA["Foo"] == "Bar");
203 	assert(orderedAA2 != orderedAA);
204 
205 	orderedAA2.remove("Foo");
206 	orderedAA2["Heya"] = "It's me Imoen";
207 	foreach(idx, kv ; orderedAA2.byKeyValue){
208 		switch(idx){
209 			case 0: assert(kv.key=="Hello"); assert(kv.value=="World"); break;
210 			case 1: assert(kv.key=="Yolo");  assert(kv.value=="Yay");   break;
211 			case 2: assert(kv.key=="Heya");  assert(kv.value=="It's me Imoen");break;
212 			default: assert(0);
213 		}
214 	}
215 	orderedAA2.remove("Heya");
216 	foreach(idx, kv ; orderedAA2.byKeyValue){
217 		switch(idx){
218 			case 0: assert(kv.key=="Hello"); assert(kv.value=="World"); break;
219 			case 1: assert(kv.key=="Yolo");  assert(kv.value=="Yay");   break;
220 			default: assert(0);
221 		}
222 	}
223 
224 
225 	OrderedAA!(string, int) aa;
226 	foreach(ref int v ; aa){}
227 	foreach(ref int v ; aa){}
228 	foreach(string k, int v ; aa){}
229 	foreach(ref string k, ref int v ; aa){}
230 }