1 /**
2  * Data structure for optimized "sparse" N-dimensional matrices
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.utils.mapset;
15 
16 static if (__VERSION__ >= 2083):
17 
18 import std.algorithm.iteration;
19 import std.array;
20 import std.exception;
21 import std.typecons : tuple;
22 
23 import ae.utils.aa;
24 import ae.utils.array : amap;
25 
26 /**
27    Data structure for holding optimized "sparse" N-dimensional matrices.
28    The number of dimensions is arbitrary and can be varied at runtime.
29 
30    Unlike classical sparse arrays/matrices, this data structure is
31    optimized not just for arrays which are mostly zero (or any given
32    value), but for any matrices where data is likely to repeat across
33    dimensions.
34 
35    This is done by storing the data as a tree, with each depth
36    representing one dimension. A sub-tree is, thus, a sub-matrix
37    (slice of the matrix represented by the top-level tree) along the
38    top-level dimension. Each sub-tree is individually immutable, and
39    can therefore be shared within the same tree or even by several
40    instances of this type.
41 
42    Dimension order need not be the same for all sub-trees. Even the
43    number of dimensions in siblings' sub-trees need not be the same;
44    i.e, the data structure is itself sparse with regards to the
45    dimensions.
46 
47    As there is no explicit "value" type (associated with every set of
48    coordinates), this data structure can be seen as a representation
49    of a set of points, but it can be simulated by adding a "value"
50    dimension (in the same way how a gray-scale image can be
51    represented as a set of 3D points like a height-map).
52 
53    An alternative way to look at this data structure is as a set of
54    maps (i.e. associative arrays). Thus, each layer of the tree stores
55    one possible map key, and under it, all the maps that have this key
56    with a specific value. Note that for this use case, DimValue needs
57    to have a value (configured using nullValue) which indicates the
58    absence of a certain key (dimension) in a map.
59 
60    Params:
61      DimName = The type used to indicate the name (or index) of a dimension.
62      DimValue = The type for indicating a point on a dimension's axis.
63      nullValue = A value for DimValue indicating that a dimension is unset
64  */
65 struct MapSet(DimName, DimValue, DimValue nullValue = DimValue.init)
66 {
67 	struct Pair
68 	{
69 		DimValue value;
70 		MapSet set;
71 	}
72 
73 	struct Node
74 	{
75 		DimName dim;
76 		Pair[] children;
77 
78 		immutable this(DimName dim, immutable Pair[] children)
79 		{
80 			// Zero children doesn't make sense.
81 			// It would be the equivalent of an empty set,
82 			// but then we should just use MapSet.emptySet instead.
83 			assert(!children.empty, "Node with zero children");
84 
85 			this.dim = dim;
86 			this.children = children;
87 
88 			// Because each submatrix is immutable, we can
89 			// pre-calculate the hash during construction.
90 			hash = hashOf(dim) ^ hashOf(children);
91 
92 			size_t totalMembers = 0;
93 			foreach (ref pair; children)
94 			{
95 				pair.set.assertDeduplicated();
96 				// Same as "Node with zero children"
97 				assert(pair.set !is emptySet, "Empty set as submatrix");
98 
99 				totalMembers += pair.set.count;
100 			}
101 			this.totalMembers = totalMembers;
102 		}
103 
104 		immutable this(DimName dim, immutable MapSet[DimValue] children)
105 		{
106 			this(dim, children.byKeyValue.map!(kv => Pair(kv.key, kv.value)).array);
107 		}
108 
109 		void toString(scope void delegate(const(char)[]) sink) const
110 		{
111 			import std.format : formattedWrite;
112 			sink.formattedWrite("{ %(%s%), [ ", (&dim)[0..1]);
113 			bool first = true;
114 			foreach (ref pair; children)
115 			{
116 				if (first)
117 					first = false;
118 				else
119 					sink(", ");
120 				sink.formattedWrite("%s : %s", pair.value, pair.set);
121 			}
122 			sink(" ] }");
123 		}
124 
125 		private hash_t hash;
126 		size_t totalMembers;
127 
128 		hash_t toHash() const
129 		{
130 			return hash;
131 		}
132 
133 		bool opEquals(ref const typeof(this) s) const
134 		{
135 			return hash == s.hash && dim == s.dim && children == s.children;
136 		}
137 	}
138 
139 	/// Indicates the empty set.
140 	/// May only occur at the top level (never as a submatrix).
141 	private enum emptySetRoot = cast(immutable(Node)*)1;
142 
143 	/// If emptySetRoot, zero values.
144 	/// If null, one value with zero dimensions.
145 	/// Otherwise, pointer to node describing the next dimension.
146 	immutable(Node)* root = emptySetRoot;
147 
148 	/// A set containing a single nil-dimensional element.
149 	/// Holds exactly one value (a point with all dimensions at
150 	/// nullValue).
151 	static immutable unitSet = MapSet(null);
152 
153 	/// The empty set. Represents a set which holds zero values.
154 	static immutable emptySet = MapSet(emptySetRoot);
155 
156 	/// Return the total number of items in this set.
157 	size_t count() const
158 	{
159 		if (this is emptySet)
160 			return 0;
161 		if (this is unitSet)
162 			return 1;
163 		return root.totalMembers;
164 	}
165 
166 	private struct SetSetOp { MapSet a, b; }
167 	private struct SetDimOp { MapSet set; DimName dim; }
168 	private struct Cache
169 	{
170 		/// For deduplication - key is value
171 		MapSet[MapSet] instances;
172 		/// Operations - things that operate recursively on subtrees
173 		/// should be memoized here
174 		MapSet[SetSetOp] merge, subtract, cartesianProduct;
175 		MapSet[SetDimOp] remove, bringToFront;
176 		MapSet[MapSet] optimize;
177 		size_t[MapSet] uniqueNodes;
178 	}
179 	private static Cache cache;
180 
181 	/// Clear the global operations cache.
182 	/// Because subtrees can be reused within the tree, a way of
183 	/// memoizing operations across the entire tree (instead of just
184 	/// across children of a single node, or siblings) is crucial for
185 	/// performance.
186 	/// Call this function to clear this cache.
187 	static void clearCache()
188 	{
189 		cache = Cache.init;
190 	}
191 
192 	/// Because MapSet operations benefit greatly from memoization,
193 	/// maintaining a set of interned sets benefits performance. After
194 	/// calling `clearCache`, call this method to re-register extant
195 	/// live instances to the instance cache.
196 	void addToCache() const
197 	{
198 		cache.instances.updateVoid(
199 			this,
200 			{
201 				if (this !is emptySet && this !is unitSet)
202 					foreach (ref pair; root.children)
203 						pair.set.addToCache();
204 				return this;
205 			},
206 			(ref MapSet set)
207 			{
208 				assert(set is this);
209 			}
210 		);
211 	}
212 
213 	/// Intern and deduplicate this MapSet.
214 	/// Needs to be called only after constructing a MapSet manually
215 	/// (by allocating, populating, and setting `root`).
216 	MapSet deduplicate() const
217 	{
218 		MapSet deduplicated;
219 		cache.instances.updateVoid(
220 			this,
221 			{
222 				debug if (this !is emptySet && this !is unitSet)
223 					foreach (ref pair; root.children)
224 						pair.set.assertDeduplicated();
225 				deduplicated = this;
226 				return this;
227 			},
228 			(ref MapSet set)
229 			{
230 				deduplicated = set;
231 			}
232 		);
233 		return deduplicated;
234 	}
235 
236 	private void assertDeduplicated() const { debug assert(this is emptySet || this is unitSet || cache.instances[this] is this); }
237 
238 	/// Count and return the total number of unique nodes in this MapSet.
239 	size_t uniqueNodes() const
240 	{
241 		return cache.uniqueNodes.require(this, {
242 			HashSet!MapSet seen;
243 			void visit(MapSet set)
244 			{
245 				if (set is emptySet || set is unitSet || set in seen)
246 					return;
247 				seen.add(set);
248 				foreach (ref pair; set.root.children)
249 					visit(pair.set);
250 			}
251 			visit(this);
252 			return seen.length;
253 		}());
254 	}
255 
256 	/// Collect the names of all dimensions occurring in this tree.
257 	DimName[] getDims() const
258 	{
259 		HashSet!DimName dims;
260 		HashSet!MapSet seen;
261 		void visit(MapSet set)
262 		{
263 			if (set is emptySet || set is unitSet || set in seen)
264 				return;
265 			seen.add(set);
266 			dims.add(set.root.dim);
267 			foreach (ref pair; set.root.children)
268 				visit(pair.set);
269 		}
270 		visit(this);
271 		return dims.keys;
272 	}
273 
274 	/// Return all values for all dimensions occurring in this set.
275 	/// The Cartesian product of these values would thus be a superset
276 	/// of this set.
277 	/// This is equivalent to, but faster than, calling `getDims` and
278 	/// then `all` for each dim.
279 	HashSet!DimValue[DimName] getDimsAndValues() const
280 	{
281 		if (this is emptySet) return null;
282 		// Be careful to count the implicit nullValues on branches
283 		// where a dim doesn't occur.
284 		auto dims = getDims();
285 		auto dimIndices = OrderedSet!DimName(dims);
286 		auto dimSeen = new bool[dims.length];
287 		HashSet!DimValue[DimName] result;
288 		HashSet!MapSet seen;
289 		void visit(MapSet set)
290 		{
291 			if (set is unitSet)
292 			{
293 				foreach (i, seen; dimSeen)
294 					if (!seen)
295 						result.require(dims[i], HashSet!DimValue.init).add(nullValue);
296 				return;
297 			}
298 
299 			if (set in seen) return;
300 			seen.add(set);
301 			auto pvalues = &result.require(set.root.dim, HashSet!DimValue.init);
302 			foreach (ref pair; set.root.children)
303 				pvalues.add(pair.value);
304 
305 			auto i = dimIndices.indexOf(set.root.dim);
306 			dimSeen[i] = true;
307 			foreach (ref pair; set.root.children)
308 				visit(pair.set);
309 			dimSeen[i] = false;
310 		}
311 		visit(this);
312 		return result;
313 	}
314 
315 	/// Combine two matrices together, returning their union.
316 	/// If `other` is a subset of `this`, return `this` unchanged.
317 	MapSet merge(MapSet other) const
318 	{
319 		if (this is emptySet) return other;
320 		if (other is emptySet) return this;
321 		if (this is unitSet && other is unitSet) return unitSet;
322 		if (!root) return bringToFront(other.root.dim).merge(other);
323 
324 		this.assertDeduplicated();
325 		other.assertDeduplicated();
326 		return cache.merge.require(SetSetOp(this, other), {
327 			other = other.bringToFront(root.dim);
328 
329 			if (root.children.length == 1 && other.root.children.length == 1 &&
330 				root.children[0].value == other.root.children[0].value)
331 			{
332 				// Single-child optimization
333 				auto mergeResult = root.children[0].set.merge(other.root.children[0].set);
334 				if (mergeResult !is root.children[0].set)
335 					return MapSet(new immutable Node(root.dim, [Pair(root.children[0].value, mergeResult)])).deduplicate;
336 				else
337 					return this;
338 			}
339 
340 			MapSet[DimValue] newChildren;
341 			foreach (ref pair; root.children)
342 				newChildren[pair.value] = pair.set;
343 
344 			bool modified;
345 			foreach (ref pair; other.root.children)
346 				newChildren.updateVoid(pair.value,
347 					{
348 						modified = true;
349 						return pair.set;
350 					},
351 					(ref MapSet oldSubmatrix)
352 					{
353 						auto mergeResult = oldSubmatrix.merge(pair.set);
354 						if (oldSubmatrix !is mergeResult)
355 						{
356 							oldSubmatrix = mergeResult;
357 							modified = true;
358 						}
359 					}
360 				);
361 
362 			if (!modified)
363 				return this;
364 
365 			return MapSet(new immutable Node(root.dim, cast(immutable) newChildren)).deduplicate;
366 		}());
367 	}
368 
369 	/// Return the difference between `this` and the given set.
370 	/// If `other` does not intersect with `this`, return `this` unchanged.
371 	MapSet subtract(MapSet other) const
372 	{
373 		if (this is emptySet) return this;
374 		if (other is emptySet) return this;
375 		if (this is unitSet && other is unitSet) return emptySet;
376 		if (!root) return bringToFront(other.root.dim).subtract(other);
377 
378 		this.assertDeduplicated();
379 		other.assertDeduplicated();
380 		return cache.subtract.require(SetSetOp(this, other), {
381 			other = other.bringToFront(root.dim);
382 
383 			if (root.children.length == 1 && other.root.children.length == 1 &&
384 				root.children[0].value == other.root.children[0].value)
385 			{
386 				// Single-child optimization
387 				auto subtractResult = root.children[0].set.subtract(other.root.children[0].set);
388 				if (subtractResult is emptySet)
389 					return emptySet;
390 				else
391 				if (subtractResult !is root.children[0].set)
392 					return MapSet(new immutable Node(root.dim, [Pair(root.children[0].value, subtractResult)])).deduplicate;
393 				else
394 					return this;
395 			}
396 
397 			MapSet[DimValue] newChildren;
398 			foreach (ref pair; root.children)
399 				newChildren[pair.value] = pair.set;
400 
401 			bool modified;
402 			foreach (ref pair; other.root.children)
403 				if (auto poldSubmatrix = pair.value in newChildren)
404 				{
405 					auto subtractResult = poldSubmatrix.subtract(pair.set);
406 					if (*poldSubmatrix !is subtractResult)
407 					{
408 						*poldSubmatrix = subtractResult;
409 						if (subtractResult is emptySet)
410 							newChildren.remove(pair.value);
411 						modified = true;
412 					}
413 				}
414 
415 			if (!modified)
416 				return this;
417 			if (!newChildren.length)
418 				return emptySet;
419 
420 			return MapSet(new immutable Node(root.dim, cast(immutable) newChildren)).deduplicate;
421 		}());
422 	}
423 
424 	private MapSet lazyMap(scope MapSet delegate(MapSet) fn) const
425 	{
426 		// Defer allocation until the need to mutate
427 		foreach (i, ref pair; root.children) // Read-only scan
428 		{
429 			auto newSet = fn(pair.set);
430 			if (newSet !is pair.set)
431 			{
432 				auto newChildren = new Pair[root.children.length];
433 				// Known to not need mutation
434 				newChildren[0 .. i] = root.children[0 .. i];
435 				// Reuse already calculated result
436 				newChildren[i] = Pair(pair.value, newSet);
437 				// Continue scan with mutation
438 				foreach (j, ref pair2; root.children[i + 1 .. $])
439 					newChildren[i + 1 + j] = Pair(pair2.value, fn(pair2.set));
440 				return MapSet(new immutable Node(root.dim, cast(immutable) newChildren)).deduplicate;
441 			}
442 		}
443 		return this; // No mutation necessary
444 	}
445 
446 	/// "Unset" a given dimension, removing it from the matrix.
447 	/// The result is the union of all sub-matrices for all values of `dim`.
448 	MapSet remove(DimName dim) const
449 	{
450 		if (this is emptySet || this is unitSet) return this;
451 		this.assertDeduplicated();
452 		return cache.remove.require(SetDimOp(this, dim), {
453 			if (root.dim == dim)
454 			{
455 				MapSet result;
456 				foreach (ref pair; root.children)
457 					result = result.merge(pair.set);
458 				return result;
459 			}
460 			return lazyMap(set => set.remove(dim));
461 		}());
462 	}
463 
464 	/// Set the given dimension to always have the given value,
465 	/// collapsing (returning the union of) all sub-matrices
466 	/// for previously different values of `dim`.
467 	MapSet set(DimName dim, DimValue value) const
468 	{
469 		if (this is emptySet) return emptySet;
470 		this.assertDeduplicated();
471 		auto removed = this.remove(dim);
472 		if (value == nullValue)
473 			return removed;
474 		return MapSet(new immutable Node(dim, cast(immutable) [value : removed])).deduplicate;
475 	}
476 
477 	/// Return a sub-matrix for all points where the given dimension has this value.
478 	/// The dimension itself is not included in the result.
479 	/// Note: if `dim` doesn't occur (e.g. because `this` is the unit set) and `value` is `nullValue`,
480 	/// this returns the unit set (not the empty set).
481 	MapSet slice(DimName dim, DimValue value) const
482 	{
483 		if (this is emptySet) return emptySet;
484 		this.assertDeduplicated();
485 		foreach (ref pair; bringToFront(dim).root.children)
486 			if (pair.value == value)
487 				return pair.set;
488 		return emptySet;
489 	}
490 
491 	/// Return a subset of this set for all points where the given dimension has this value.
492 	/// Unlike `slice`, the dimension itself is included in the result (with the given value).
493 	MapSet get(DimName dim, DimValue value) const
494 	{
495 		if (this is emptySet) return emptySet;
496 		this.assertDeduplicated();
497 		foreach (ref pair; bringToFront(dim).root.children)
498 			if (pair.value == value)
499 				return MapSet(new immutable Node(dim, [Pair(value, pair.set)])).deduplicate;
500 		return emptySet;
501 	}
502 
503 	/// Return all unique values occurring for a given dimension.
504 	/// Unless this is the empty set, the return value is always non-empty.
505 	/// If `dim` doesn't occur, it will be `[nullValue]`.
506 	const(DimValue)[] all(DimName dim) const
507 	{
508 		// return bringToFront(dim).root.children.keys;
509 		if (this is emptySet) return null;
510 		if (this is unitSet) return [nullValue];
511 		if (root.dim == dim) return root.children.amap!(child => child.value);
512 		this.assertDeduplicated();
513 
514 		HashSet!DimValue allValues;
515 		HashSet!MapSet seen;
516 		void visit(MapSet set)
517 		{
518 			if (set is unitSet)
519 			{
520 				allValues.add(nullValue);
521 				return;
522 			}
523 			if (set in seen)
524 				return;
525 			seen.add(set);
526 
527 			if (set.root.dim == dim)
528 				foreach (ref pair; set.root.children)
529 					allValues.add(pair.value);
530 			else
531 				foreach (ref pair; set.root.children)
532 					visit(pair.set);
533 		}
534 		visit(this);
535 		return allValues.keys;
536 	}
537 
538 	/// Return a set which represents the Cartesian product between
539 	/// this set and the given `values` across the specified
540 	/// dimension.
541 	MapSet cartesianProduct(DimName dim, DimValue[] values) const
542 	{
543 		if (this is emptySet) return emptySet;
544 		if (values.length == 0) return emptySet;
545 		this.assertDeduplicated();
546 		auto unset = this.remove(dim);
547 		return MapSet(new immutable Node(dim, cast(immutable) values.map!(value => tuple(value, unset)).assocArray)).deduplicate;
548 	}
549 
550 	/// Return a set which represents the Cartesian product between
551 	/// this and the given set.
552 	/// Duplicate dimensions are first removed from `this` set.
553 	/// For best performance, call `big.cartesianProduct(small)`
554 	MapSet cartesianProduct(MapSet other) const
555 	{
556 		if (this is emptySet || other is emptySet) return emptySet;
557 		if (this is unitSet) return other;
558 		if (other is unitSet) return this;
559 
560 		MapSet unset = this;
561 		foreach (dim; other.getDims())
562 			unset = unset.remove(dim);
563 
564 		return other.uncheckedCartesianProduct(this);
565 	}
566 
567 	// Assumes that the dimensions in `this` and `other` are disjoint.
568 	private MapSet uncheckedCartesianProduct(MapSet other) const
569 	{
570 		if (this is unitSet) return other;
571 		if (other is unitSet) return this;
572 
573 		this.assertDeduplicated();
574 		other.assertDeduplicated();
575 
576 		return cache.cartesianProduct.require(SetSetOp(this, other), {
577 			return lazyMap(set => set.uncheckedCartesianProduct(other));
578 		}());
579 	}
580 
581 	/// Refactor this matrix into one with the same data,
582 	/// but putting the given dimension in front.
583 	/// This will speed up access to values with the given dimension.
584 	/// If the dimension does not yet occur in the set (or any subset),
585 	/// it is instantiated with a single `nullValue` value.
586 	/// The set must be non-empty.
587 	MapSet bringToFront(DimName dim) const
588 	{
589 		assert(this !is emptySet, "Empty sets may not have dimensions");
590 
591 		if (this is unitSet)
592 		{
593 			// We reached the bottom, and did not find `dim` along the way.
594 			// Create it now.
595 			return MapSet(new immutable Node(dim, [Pair(nullValue, unitSet)])).deduplicate;
596 		}
597 
598 		if (dim == root.dim)
599 		{
600 			// Already at the front.
601 			return this;
602 		}
603 
604 		// 1. Recurse.
605 		// 2. After recursion, all children should have `dim` at the front.
606 		// So, just swap this layer with the next one.
607 
608 		this.assertDeduplicated();
609 		return cache.bringToFront.require(SetDimOp(this, dim), {
610 			if (root.children.length == 1)
611 			{
612 				auto newSubmatrix = root.children[0].set.bringToFront(dim);
613 				auto newChildren = newSubmatrix.root.children.dup;
614 				foreach (ref pair; newChildren)
615 					pair.set = MapSet(new immutable Node(root.dim, [Pair(root.children[0].value, pair.set)])).deduplicate;
616 				return MapSet(new immutable Node(dim, cast(immutable) newChildren)).deduplicate;
617 			}
618 
619 			Pair[][DimValue] submatrices;
620 			foreach (ref pair; root.children)
621 			{
622 				auto newSubmatrix = pair.set.bringToFront(dim);
623 				assert(newSubmatrix.root.dim == dim);
624 				foreach (ref pair2; newSubmatrix.root.children)
625 					submatrices[pair2.value] ~= Pair(pair.value, pair2.set);
626 			}
627 			Pair[] newChildren;
628 			foreach (value, children; submatrices)
629 				newChildren ~= Pair(value, MapSet(new immutable Node(root.dim, cast(immutable) children)).deduplicate);
630 			return MapSet(new immutable Node(dim, cast(immutable) newChildren)).deduplicate;
631 		}());
632 	}
633 
634 	/// Refactor this matrix into one with the same data,
635 	/// but attempting to lower the total number of nodes.
636 	MapSet optimize() const
637 	{
638 		if (this is emptySet || this is unitSet) return this;
639 		this.assertDeduplicated();
640 
641 		return cache.optimize.require(this, {
642 			bool modified;
643 			MapSet[DimValue] newChildren;
644 			foreach (ref pair; root.children)
645 			{
646 				auto newMatrix = pair.set.optimize;
647 				if (newMatrix !is pair.set)
648 				{
649 					modified = true;
650 					assert(newMatrix.count == pair.set.count);
651 				}
652 				newChildren[pair.value] = newMatrix;
653 			}
654 
655 			MapSet result = modified ? MapSet(new immutable Node(root.dim, cast(immutable) newChildren)).deduplicate : this;
656 
657 			foreach (ref pair; result.root.children)
658 				if (pair.set.root)
659 				{
660 					auto optimized = result.bringToFront(pair.set.root.dim);
661 					// {
662 					// 	import std.stdio;
663 					// 	writefln("Trying to optimize:\n- Old: %d : %s\n- New: %d : %s",
664 					// 		result.root.totalNodes, result,
665 					// 		optimized.root.totalNodes, optimized,
666 					// 	);
667 					// }
668 					return optimized.uniqueNodes < result.uniqueNodes ? optimized : result;
669 				}
670 
671 			return result.deduplicate;
672 		}());
673 	}
674 
675 	void toString(scope void delegate(const(char)[]) sink) const
676 	{
677 		import std.format : formattedWrite;
678 		if (this is emptySet)
679 			sink("{}");
680 		else
681 		if (this is unitSet)
682 			sink("{[]}");
683 		else
684 			sink.formattedWrite!"%s"(*root);
685 	}
686 
687 	hash_t toHash() const
688 	{
689 		return
690 			this is emptySet ? 0 :
691 			this is unitSet ? 1 :
692 			root.toHash();
693 	}
694 
695 	bool opEquals(const typeof(this) s) const
696 	{
697 		if (root is s.root)
698 			return true;
699 		if (this is emptySet || this is unitSet || s is emptySet || s is unitSet)
700 			return this is s;
701 		return *root == *s.root;
702 	}
703 }
704 
705 unittest
706 {
707 	import std.algorithm.sorting : sort;
708 
709 	alias M = MapSet!(string, int);
710 	M m, n;
711 	m = m.merge(M.unitSet.set("x", 1).set("y", 5));
712 	m = m.merge(M.unitSet.set("x", 1).set("y", 6));
713 	assert(m.all("x") == [1]);
714 	assert(m.all("y").dup.sort.release == [5, 6]);
715 
716 	m = m.merge(M.unitSet.set("x", 2).set("y", 6));
717 	assert(m.get("x", 1).all("y").dup.sort.release == [5, 6]);
718 	assert(m.get("y", 6).all("x").dup.sort.release == [1, 2]);
719 
720 	m = m.subtract(M.unitSet.set("x", 1).set("y", 6));
721 	assert(m.all("x").dup.sort.release == [1, 2]);
722 	assert(m.all("y").dup.sort.release == [5, 6]);
723 	assert(m.get("x", 1).all("y") == [5]);
724 	assert(m.get("y", 6).all("x") == [2]);
725 
726 	m = M.emptySet;
727 	m = m.merge(M.unitSet.set("x", 10).set("y", 20));
728 	m = m.merge(M.unitSet.set("x", 10).set("y", 21));
729 	m = m.merge(M.unitSet.set("x", 11).set("y", 21));
730 	m = m.merge(M.unitSet.set("x", 11).set("y", 22));
731 	auto o = m.optimize();
732 	assert(o.uniqueNodes < m.uniqueNodes);
733 
734 	m = M.emptySet;
735 	assert(m.all("x") == []);
736 	m = M.unitSet;
737 	assert(m.all("x") == [0]);
738 	m = m.merge(M.unitSet.set("x", 1));
739 	assert(m.all("x").dup.sort.release == [0, 1]);
740 
741 	m = M.unitSet;
742 	assert(m.set("x", 1).set("x", 1).all("x") == [1]);
743 
744 	m = M.unitSet;
745 	m = m.cartesianProduct("x", [1, 2, 3]);
746 	m = m.cartesianProduct("y", [1, 2, 3]);
747 	m = m.cartesianProduct("z", [1, 2, 3]);
748 	assert(m.count == 3 * 3 * 3);
749 	assert(m            .all("x").dup.sort.release == [1, 2, 3]);
750 	assert(m.set("z", 1).all("x").dup.sort.release == [1, 2, 3]);
751 	assert(m.set("x", 1).all("z").dup.sort.release == [1, 2, 3]);
752 
753 	m = M.unitSet;
754 	m = m.cartesianProduct("a", [1, 2, 3]);
755 	m = m.cartesianProduct("b", [1, 2, 3]);
756 	n = M.unitSet;
757 	n = n.cartesianProduct("c", [1, 2, 3]);
758 	n = n.cartesianProduct("d", [1, 2, 3]);
759 	m = m.cartesianProduct(n);
760 	assert(m.count == 3 * 3 * 3 * 3);
761 
762 	assert(M.unitSet != M.unitSet.set("x", 1));
763 }
764 
765 
766 /// Allows executing a deterministic algorithm over all states in a given MapSet.
767 /// If a variable is not queried by the algorithm, states for all
768 /// variations of that variable are processed in one iteration.
769 struct MapSetVisitor(A, V)
770 {
771 	alias Set = MapSet!(A, V);
772 	Set set;
773 
774 	struct Var
775 	{
776 		A name;
777 		const(V)[] values;
778 		size_t pos;
779 	}
780 	Var[] stack;
781 	V[A] singularValues, resolvedValues; // Faster than currentSubset.all(name)[0]
782 	Set currentSubset;
783 
784 	this(Set set)
785 	{
786 		this.set = set;
787 		foreach (dim, values; set.getDimsAndValues())
788 			if (values.length == 1)
789 				singularValues[dim] = values.byKey.front;
790 	}
791 
792 	/// Resets iteration to the beginning.
793 	/// Equivalent to but faster than constructing a new MapSetVisitor
794 	/// instance (`visitor = MapSetVisitor(visitor.set)`).
795 	void reset()
796 	{
797 		currentSubset = Set.emptySet;
798 		stack = null;
799 	}
800 
801 	/// Returns true if there are more states to iterate over,
802 	/// otherwise returns false
803 	bool next()
804 	{
805 		if (set is Set.emptySet)
806 			return false;
807 		if (currentSubset is Set.emptySet)
808 		{
809 			// first iteration
810 		}
811 		else
812 			while (true)
813 			{
814 				if (!stack.length)
815 					return false; // All possibilities exhausted
816 				auto last = &stack[$-1];
817 				last.pos++;
818 				if (last.pos == last.values.length)
819 				{
820 					stack = stack[0 .. $ - 1];
821 					continue;
822 				}
823 				break;
824 			}
825 
826 		currentSubset = set;
827 		resolvedValues = null;
828 		foreach (ref var; stack)
829 		{
830 			auto value = var.values[var.pos];
831 			currentSubset = currentSubset.get(var.name, value);
832 			resolvedValues[var.name] = value;
833 		}
834 		return true;
835 	}
836 
837 	/// Algorithm interface - get a value by name
838 	V get(A name)
839 	{
840 		if (auto pvalue = name in resolvedValues)
841 			return *pvalue;
842 		if (auto pvalue = name in singularValues)
843 			return *pvalue;
844 
845 		auto values = currentSubset.all(name);
846 		auto value = values[0];
847 		resolvedValues[name] = value;
848 		stack ~= Var(name, values, 0);
849 		if (values.length > 1)
850 			currentSubset = currentSubset.get(name, value);
851 		return value;
852 	}
853 
854 	/// Algorithm interface - set a value by name
855 	void put(A name, V value)
856 	{
857 		currentSubset = currentSubset.set(name, value);
858 		resolvedValues[name] = value;
859 	}
860 
861 	/// Apply a function over every possible value of the given
862 	/// variable, without resolving it (unless it's already resolved).
863 	void transform(A name, scope void delegate(ref V value) fun)
864 	{
865 		if (auto pvalue = name in resolvedValues)
866 			return fun(*pvalue);
867 
868 		currentSubset = currentSubset.bringToFront(name);
869 		auto newChildren = currentSubset.root.children.dup;
870 		foreach (ref child; newChildren)
871 			fun(child.value);
872 		currentSubset = Set(new immutable Set.Node(name, cast(immutable) newChildren));
873 	}
874 }
875 
876 /// An algorithm which divides two numbers.
877 /// When the divisor is zero, we don't even query the dividend,
878 /// therefore processing all dividends in one iteration.
879 unittest
880 {
881 	alias M = MapSet!(string, int);
882 	M m = M.unitSet
883 		.cartesianProduct("divisor" , [0, 1, 2])
884 		.cartesianProduct("dividend", [0, 1, 2]);
885 	assert(m.count == 9);
886 
887 	auto v = MapSetVisitor!(string, int)(m);
888 	M results;
889 	int iterations;
890 	while (v.next())
891 	{
892 		iterations++;
893 		auto divisor = v.get("divisor");
894 		if (divisor == 0)
895 			continue;
896 		auto dividend = v.get("dividend");
897 		v.put("quotient", dividend / divisor);
898 		results = results.merge(v.currentSubset);
899 	}
900 
901 	assert(iterations == 7); // 1 for division by zero + 3 for division by one + 3 for division by two
902 	assert(results.get("divisor", 2).get("dividend", 2).all("quotient") == [1]);
903 	assert(results.get("divisor", 0).count == 0);
904 }
905 
906 unittest
907 {
908 	import std.algorithm.sorting : sort;
909 
910 	alias M = MapSet!(string, int);
911 	M m = M.unitSet.cartesianProduct("x", [1, 2, 3]);
912 	auto v = MapSetVisitor!(string, int)(m);
913 	v.next();
914 	v.transform("x", (ref int v) { v *= 2; });
915 	assert(v.currentSubset.all("x").dup.sort.release == [2, 4, 6]);
916 }