1 /**
2  * MapSet visitor type.
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 <ae@cy.md>
12  */
13 
14 module ae.utils.mapset.visitor;
15 
16 static if (__VERSION__ >= 2083):
17 
18 import ae.utils.mapset.mapset;
19 
20 import std.algorithm.iteration;
21 import std.algorithm.searching;
22 import std.algorithm.sorting;
23 import std.array;
24 import std.exception;
25 import std.typecons : tuple;
26 
27 import ae.utils.aa;
28 import ae.utils.array : amap;
29 
30 /// Allows executing a deterministic algorithm over all states in a given MapSet.
31 /// If a variable is not queried by the algorithm, states for all
32 /// variations of that variable are processed in one iteration.
33 struct MapSetVisitor(A, V, V nullValue = V.init)
34 {
35 	/// Underlying `MapSet`.
36 	alias Set = MapSet!(A, V, nullValue);
37 	Set set; /// ditto
38 
39 	/// Internal state.
40 	/*private*/ public
41 	{
42 		// Iteration state for resolved values
43 		struct Var
44 		{
45 			A name; ///
46 			const(V)[] values; ///
47 			size_t pos; ///
48 		}
49 		Var[] stack;
50 		size_t stackPos;
51 
52 		private enum Maybe { no, maybe, yes }
53 
54 		struct VarState
55 		{
56 			// Variable holds this one value if `haveValue` is true.
57 			V value = nullValue;
58 
59 			// Optimization.
60 			// For variables which have been resolved, or have been
61 			// set to some specific value, remember that value here
62 			// (in the `value` field).
63 			// Faster than checking workingSet.all(name)[0].
64 			// If this is set, it has a concrete value because
65 			// - we are iterating over it (it's in the stack)
66 			// - due to a `put` call
67 			// - it was never in the set (so it's implicitly at nullValue).
68 			bool haveValue = true;
69 
70 			// Optimization.
71 			// Accumulate MapSet.set calls, and flush then in bulk.
72 			// The value to flush is stored in the `value` field.
73 			// If `dirty` is true, `haveValue` must be true.
74 			bool dirty;
75 
76 			// Optimization.
77 			// Remember whether this variable is in the set or not.
78 			Maybe inSet = Maybe.no;
79 
80 			void setInSet(bool inSet)
81 			{
82 				if (inSet)
83 					this.inSet = Maybe.yes;
84 				else
85 				{
86 					this.inSet = Maybe.no;
87 					assert(!this.dirty, "TODO");
88 					if (this.haveValue)
89 						assert(this.value == nullValue);
90 					else
91 					{
92 						this.haveValue = true;
93 						this.value = nullValue;
94 					}
95 				}
96 			}
97 		}
98 		VarState[A] varState, initialVarState;
99 
100 		// The version of the set for the current iteration.
101 		private Set workingSet;
102 	}
103 
104 	this(Set set)
105 	{
106 		this.set = set;
107 		foreach (dim, values; set.getDimsAndValues())
108 		{
109 			auto pstate = &initialVarState.require(dim);
110 			pstate.inSet = Maybe.yes;
111 			if (values.length == 1)
112 				pstate.value = values.byKey.front;
113 			else
114 				pstate.haveValue = false;
115 		}
116 	} ///
117 
118 	@disable this(this);
119 
120 	MapSetVisitor dup()
121 	{
122 		MapSetVisitor r;
123 		r.set = set;
124 		r.stack = stack.dup;
125 		r.stackPos = stackPos;
126 		r.varState = varState.dup;
127 		r.workingSet = workingSet;
128 		return r;
129 	}
130 
131 	/// Resets iteration to the beginning.
132 	/// Equivalent to but faster than constructing a new MapSetVisitor
133 	/// instance (`visitor = MapSetVisitor(visitor.set)`).
134 	void reset()
135 	{
136 		workingSet = Set.emptySet;
137 		stack = null;
138 	}
139 
140 	/// Returns true if there are more states to iterate over,
141 	/// otherwise returns false
142 	bool next()
143 	{
144 		if (set is Set.emptySet)
145 			return false;
146 		if (workingSet is Set.emptySet)
147 		{
148 			// first iteration
149 		}
150 		else
151 			while (true)
152 			{
153 				if (!stack.length)
154 					return false; // All possibilities exhausted
155 				auto last = &stack[$-1];
156 				last.pos++;
157 				if (last.pos == last.values.length)
158 				{
159 					stack = stack[0 .. $ - 1];
160 					continue;
161 				}
162 				break;
163 			}
164 
165 		workingSet = set;
166 		varState = initialVarState.dup;
167 		stackPos = 0;
168 		return true;
169 	}
170 
171 	private void flush()
172 	{
173 		auto toRemove = varState.byKeyValue
174 			.filter!(pair => pair.value.dirty && pair.value.inSet >= Maybe.maybe)
175 			.map!(pair => pair.key)
176 			.toSet;
177 		workingSet = workingSet.remove((A name) => name in toRemove);
178 		foreach (name, ref state; varState)
179 			if (state.dirty)
180 			{
181 				workingSet = workingSet.addDim(name, state.value);
182 				state.dirty = false;
183 				state.setInSet(state.value != nullValue); // addDim is a no-op with nullValue
184 			}
185 	}
186 
187 	private void flush(A name)
188 	{
189 		if (auto pstate = name in varState)
190 			if (pstate.dirty)
191 			{
192 				pstate.dirty = false;
193 				if (pstate.inSet >= Maybe.maybe)
194 				{
195 					if (pstate.inSet == Maybe.yes)
196 					{
197 						auto oldSet = workingSet;
198 						auto newSet = workingSet.remove(name);
199 						assert(oldSet != newSet, "Actually wasn't in the set");
200 						workingSet = newSet;
201 					}
202 					else
203 						workingSet = workingSet.remove(name);
204 				}
205 				workingSet = workingSet.addDim(name, pstate.value);
206 				pstate.setInSet(pstate.value != nullValue); // addDim is a no-op with nullValue
207 			}
208 	}
209 
210 	/// Peek at the subset the algorithm is currently working with.
211 	@property Set currentSubset()
212 	{
213 		assert(workingSet !is Set.emptySet, "Not iterating");
214 		flush();
215 		return workingSet;
216 	}
217 
218 	/// Get all possible values for this variable at this point.
219 	/// Should be used mainly for debugging.
220 	/*private*/ const(V)[] getAll(A name)
221 	{
222 		assert(workingSet !is Set.emptySet, "Not iterating");
223 		if (auto pstate = name in varState)
224 			if (pstate.haveValue)
225 				return (&pstate.value)[0 .. 1];
226 
227 		return workingSet.all(name);
228 	}
229 
230 	/// Algorithm interface - get a value by name
231 	V get(A name)
232 	{
233 		assert(workingSet !is Set.emptySet, "Not iterating");
234 		auto pstate = &varState.require(name);
235 		if (pstate.haveValue)
236 			return pstate.value;
237 
238 		// We are going to narrow the workingSet - update inSet appropriately
239 		foreach (varName, ref state; varState)
240 			if (varName == name)
241 				state.inSet = Maybe.maybe;
242 			else
243 			if (state.inSet == Maybe.yes)
244 				state.inSet = Maybe.maybe;
245 
246 		if (stackPos == stack.length)
247 		{
248 			// Expand new variable
249 			auto values = workingSet.all(name);
250 			auto value = values[0];
251 			// auto pstate = varState
252 			pstate.value = value;
253 			pstate.haveValue = true;
254 			stack ~= Var(name, values, 0);
255 			stackPos++;
256 			if (values.length > 1)
257 				workingSet = workingSet.get(name, value);
258 			return value;
259 		}
260 
261 		// Iterate over known variable
262 		auto var = &stack[stackPos];
263 		assert(var.name == name, "Mismatching get order");
264 		auto value = var.values[var.pos];
265 		workingSet = workingSet.get(var.name, value);
266 		assert(workingSet !is Set.emptySet, "Empty set after restoring");
267 		pstate.value = value;
268 		pstate.haveValue = true;
269 		stackPos++;
270 		return value;
271 	}
272 
273 	/// Algorithm interface - set a value by name
274 	void put(A name, V value)
275 	{
276 		assert(workingSet !is Set.emptySet, "Not iterating");
277 
278 		auto pstate = &varState.require(name);
279 
280 		if (pstate.haveValue && pstate.value == value)
281 			return; // All OK
282 
283 		pstate.value = value;
284 		pstate.haveValue = pstate.dirty = true;
285 
286 		// Flush null values ASAP, to avoid non-null values
287 		// accumulating in the set and increasing the set size.
288 		if (value == nullValue)
289 			flush(name);
290 	}
291 
292 	/// Prepare an unresolved variable for overwriting (with more than
293 	/// one value).
294 	private void destroy(A name)
295 	{
296 		auto pstate = &varState.require(name);
297 		pstate.haveValue = pstate.dirty = false;
298 		if (pstate.inSet >= Maybe.maybe)
299 		{
300 			workingSet = workingSet.remove(name);
301 			pstate.inSet = Maybe.no;
302 		}
303 	}
304 
305 	/// A smarter `workingSet = workingSet.bringToFront(name)`, which
306 	/// checks if `name` is in the set first.
307 	private void bringToFront(A name)
308 	{
309 		auto pState = &varState.require(name);
310 		if (pState.inSet == Maybe.no)
311 			workingSet = Set(new immutable Set.Node(name, [Set.Pair(nullValue, workingSet)])).deduplicate;
312 		else
313 			workingSet = workingSet.bringToFront(name);
314 		pState.inSet = Maybe.yes;
315 	}
316 
317 	/// Algorithm interface - copy a value target another name,
318 	/// without resolving it (unless it's already resolved).
319 	void copy(bool reorder = false)(A source, A target)
320 	{
321 		if (source == target)
322 			return;
323 
324 		auto pSourceState = &varState.require(source);
325 		auto pTargetState = &varState.require(target);
326 		if (pSourceState.haveValue)
327 		{
328 			put(target, pSourceState.value);
329 			return;
330 		}
331 
332 		assert(workingSet !is Set.emptySet, "Not iterating");
333 
334 		static if (reorder)
335 		{
336 			destroy(target);
337 
338 			bringToFront(source);
339 			auto newChildren = workingSet.root.children.dup;
340 			foreach (ref pair; newChildren)
341 			{
342 				auto set = Set(new immutable Set.Node(source, [Set.Pair(pair.value, pair.set)])).deduplicate;
343 				pair = Set.Pair(pair.value, set);
344 			}
345 			workingSet = Set(new immutable Set.Node(target, cast(immutable) newChildren)).deduplicate;
346 			pSourceState.inSet = Maybe.yes;
347 			pTargetState.inSet = Maybe.yes;
348 		}
349 		else
350 		{
351 			targetTransform!true(source, target,
352 				(ref const V inputValue, out V outputValue)
353 				{
354 					outputValue = inputValue;
355 				}
356 			);
357 		}
358 	}
359 
360 	/// Apply a function over every possible value of the given
361 	/// variable, without resolving it (unless it's already resolved).
362 	void transform(A name, scope void delegate(ref V value) fun)
363 	{
364 		assert(workingSet !is Set.emptySet, "Not iterating");
365 		auto pState = &varState.require(name);
366 		if (pState.haveValue)
367 		{
368 			pState.dirty = true;
369 			fun(pState.value);
370 			return;
371 		}
372 
373 		bringToFront(name);
374 		Set[V] newChildren;
375 		foreach (ref child; workingSet.root.children)
376 		{
377 			V value = child.value;
378 			fun(value);
379 			newChildren.updateVoid(value,
380 				() => child.set,
381 				(ref Set set)
382 				{
383 					set = set.merge(child.set);
384 				});
385 		}
386 		workingSet = Set(new immutable Set.Node(name, cast(immutable) newChildren)).deduplicate;
387 		pState.inSet = Maybe.yes;
388 	}
389 
390 	/// Apply a function over every possible value of the given
391 	/// variable, without resolving it (unless it's already resolved).
392 	/// The function is assumed to be injective (does not produce
393 	/// duplicate outputs for distinct inputs).
394 	void injectiveTransform(A name, scope void delegate(ref V value) fun)
395 	{
396 		assert(workingSet !is Set.emptySet, "Not iterating");
397 		auto pState = &varState.require(name);
398 		if (pState.haveValue)
399 		{
400 			pState.dirty = true;
401 			fun(pState.value);
402 			return;
403 		}
404 
405 		bringToFront(name);
406 		auto newChildren = workingSet.root.children.dup;
407 		foreach (ref child; newChildren)
408 			fun(child.value);
409 		newChildren.sort();
410 		workingSet = Set(new immutable Set.Node(name, cast(immutable) newChildren)).deduplicate;
411 		pState.inSet = Maybe.yes;
412 	}
413 
414 	/// Perform a transformation with one input and one output.
415 	/// Does not reorder the MapSet.
416 	void targetTransform(bool injective = false)(A input, A output, scope void delegate(ref const V inputValue, out V outputValue) fun)
417 	{
418 		assert(input != output, "Input is the same as output - use transform instead");
419 
420 		flush(input);
421 		destroy(output);
422 
423 		auto pInputState = &varState.require(input);
424 		auto pOutputState = &varState.require(output);
425 
426 		if (pInputState.haveValue)
427 		{
428 			V outputValue;
429 			fun(pInputState.value, outputValue);
430 			put(output, outputValue);
431 			return;
432 		}
433 
434 		bool sawInput, addedOutput;
435 		Set[Set] cache;
436 		Set visit(Set set)
437 		{
438 			return cache.require(set, {
439 				if (set == Set.unitSet)
440 				{
441 					V inputValue = nullValue;
442 					V outputValue;
443 					fun(inputValue, outputValue);
444 					if (outputValue != nullValue)
445 						addedOutput = true;
446 					return set.addDim(output, outputValue);
447 				}
448 				if (set.root.dim == input)
449 				{
450 					sawInput = true;
451 					static if (injective)
452 					{
453 						auto outputChildren = new Set.Pair[set.root.children.length];
454 						foreach (i, ref outputPair; outputChildren)
455 						{
456 							auto inputPair = &set.root.children[i];
457 							fun(inputPair.value, outputPair.value);
458 							outputPair.set = Set(new immutable Set.Node(input, inputPair[0..1])).deduplicate;
459 						}
460 						outputChildren.sort();
461 						addedOutput = true;
462 						return Set(new immutable Set.Node(output, cast(immutable) outputChildren)).deduplicate;
463 					}
464 					else
465 					{
466 						auto inputChildren = set.root.children;
467 						set = Set.emptySet;
468 						foreach (i, ref inputPair; inputChildren)
469 						{
470 							V outputValue;
471 							fun(inputPair.value, outputValue);
472 							auto inputSet = Set(new immutable Set.Node(input, (&inputPair)[0..1])).deduplicate;
473 							auto outputSet = inputSet.addDim(output, outputValue);
474 							set = set.merge(outputSet);
475 							if (outputValue != nullValue)
476 								addedOutput = true;
477 						}
478 						return set;
479 					}
480 				}
481 				else
482 					return set.lazyMap(&visit);
483 			}());
484 		}
485 		workingSet = visit(workingSet);
486 		pInputState.setInSet(sawInput);
487 		pOutputState.setInSet(addedOutput);
488 	}
489 
490 	/// Perform a transformation with multiple inputs and outputs.
491 	/// Inputs and outputs must not overlap.
492 	/// Can be used to perform binary operations, copy-transforms, and more.
493 	void multiTransform(scope A[] inputs, scope A[] outputs, scope void delegate(scope V[] inputValues, scope V[] outputValues) fun)
494 	{
495 		assert(inputs.length > 0 && outputs.length > 0, "");
496 		foreach (output; outputs)
497 			assert(!inputs.canFind(output), "Inputs and outputs overlap");
498 
499 		foreach (input; inputs)
500 			flush(input);
501 		foreach (output; outputs)
502 			destroy(output);
503 		foreach_reverse (input; inputs)
504 			bringToFront(input);
505 
506 		Set resultSet = Set.emptySet;
507 		auto inputValues = new V[inputs.length];
508 		auto outputValues = new V[outputs.length];
509 		auto addedInput = new bool[inputs.length];
510 		auto addedOutput = new bool[outputs.length];
511 
512 		void visit(Set set, size_t depth)
513 		{
514 			if (depth == inputs.length)
515 			{
516 				fun(inputValues, outputValues);
517 				foreach_reverse (i, input; inputs)
518 				{
519 					set = set.addDim(input, inputValues[i]);
520 					if (inputValues[i] != nullValue)
521 						addedInput[i] = true;
522 				}
523 				foreach_reverse (i, output; outputs)
524 				{
525 					set = set.addDim(output, outputValues[i]);
526 					if (outputValues[i] != nullValue)
527 						addedOutput[i] = true;
528 				}
529 				resultSet = resultSet.merge(set);
530 			}
531 			else
532 			{
533 				assert(set.root.dim == inputs[depth]);
534 				foreach (ref pair; set.root.children)
535 				{
536 					inputValues[depth] = pair.value;
537 					visit(pair.set, depth + 1);
538 				}
539 			}
540 		}
541 		visit(workingSet, 0);
542 		workingSet = resultSet;
543 		foreach (i, input; inputs)
544 			varState.require(input).setInSet(addedInput[i]);
545 		foreach (i, output; outputs)
546 			varState.require(output).setInSet(addedOutput[i]);
547 	}
548 
549 	/// Inject a variable and values to iterate over.
550 	/// The variable must not have been resolved yet.
551 	void inject(A name, V[] values)
552 	{
553 		assert(values.length > 0, "Injecting zero values would result in an empty set");
554 		destroy(name);
555 		workingSet = workingSet.uncheckedCartesianProduct(name, values);
556 		varState.require(name).inSet = Maybe.yes;
557 	}
558 }
559 
560 /// An algorithm which divides two numbers.
561 /// When the divisor is zero, we don't even query the dividend,
562 /// therefore processing all dividends in one iteration.
563 debug(ae_unittest) unittest
564 {
565 	alias M = MapSet!(string, int);
566 	M m = M.unitSet
567 		.cartesianProduct("divisor" , [0, 1, 2])
568 		.cartesianProduct("dividend", [0, 1, 2]);
569 	assert(m.count == 9);
570 
571 	auto v = MapSetVisitor!(string, int)(m);
572 	M results;
573 	int iterations;
574 	while (v.next())
575 	{
576 		iterations++;
577 		auto divisor = v.get("divisor");
578 		if (divisor == 0)
579 			continue;
580 		auto dividend = v.get("dividend");
581 		v.put("quotient", dividend / divisor);
582 		results = results.merge(v.currentSubset);
583 	}
584 
585 	assert(iterations == 7); // 1 for division by zero + 3 for division by one + 3 for division by two
586 	assert(results.get("divisor", 2).get("dividend", 2).all("quotient") == [1]);
587 	assert(results.get("divisor", 0).count == 0);
588 }
589 
590 debug(ae_unittest) unittest
591 {
592 	import std.algorithm.sorting : sort;
593 
594 	alias M = MapSet!(string, int);
595 	M m = M.unitSet.cartesianProduct("x", [1, 2, 3]);
596 	auto v = MapSetVisitor!(string, int)(m);
597 	v.next();
598 	v.transform("x", (ref int v) { v *= 2; });
599 	assert(v.currentSubset.all("x").dup.sort.release == [2, 4, 6]);
600 }
601 
602 debug(ae_unittest) unittest
603 {
604 	alias M = MapSet!(string, int);
605 	M m = M.unitSet.cartesianProduct("x", [1, 2, 3]);
606 	auto v = MapSetVisitor!(string, int)(m);
607 	while (v.next())
608 	{
609 		v.transform("x", (ref int v) { v *= 2; });
610 		v.put("y", v.get("x"));
611 	}
612 }
613 
614 // Test that initialVarState does not interfere with flushing
615 debug(ae_unittest) unittest
616 {
617 	alias M = MapSet!(string, int);
618 	M m = M.unitSet.cartesianProduct("x", [1]);
619 	auto v = MapSetVisitor!(string, int)(m);
620 	assert(v.initialVarState["x"].haveValue);
621 	v.next();
622 	v.put("x", 2);
623 	v.currentSubset;
624 	assert(v.get("x") == 2);
625 }
626 
627 // Test resolving the same variable several times
628 debug(ae_unittest) unittest
629 {
630 	alias M = MapSet!(string, int);
631 	M m = M.unitSet.cartesianProduct("x", [10, 20, 30]);
632 	auto v = MapSetVisitor!(string, int)(m);
633 	int[] result;
634 	while (v.next())
635 	{
636 		auto a = v.get("x"); // First resolve
637 		v.inject("x", [1, 2, 3]);
638 		auto b = v.get("x"); // Second resolve
639 		result ~= a + b;
640 	}
641 	assert(result == [11, 12, 13, 21, 22, 23, 31, 32, 33]);
642 }
643 
644 // Same, with `copy`.
645 debug(ae_unittest) unittest
646 {
647 	alias M = MapSet!(string, int);
648 	M m = M.unitSet;
649 	m = m.cartesianProduct("x", [10, 20, 30]);
650 	m = m.cartesianProduct("y", [ 1,  2,  3]);
651 	auto v = MapSetVisitor!(string, int)(m);
652 	int[] result;
653 	while (v.next())
654 	{
655 		v.copy("x", "tmp");
656 		auto x = v.get("tmp"); // First resolve
657 		v.copy("y", "tmp");
658 		auto y = v.get("tmp"); // Second resolve
659 		result ~= x + y;
660 	}
661 	result.sort();
662 	assert(result == [11, 12, 13, 21, 22, 23, 31, 32, 33]);
663 }
664 
665 // targetTransform
666 debug(ae_unittest) unittest
667 {
668 	import std.algorithm.sorting : sort;
669 
670 	alias M = MapSet!(string, int);
671 	M m = M.unitSet.cartesianProduct("x", [1, 2, 3, 4, 5]);
672 	auto v = MapSetVisitor!(string, int)(m);
673 	v.next();
674 	v.targetTransform("x", "y", (ref const int input, out int output) { output = input + 1; });
675 	assert(v.currentSubset.all("y").dup.sort.release == [2, 3, 4, 5, 6]);
676 	assert(!v.next());
677 }
678 
679 // multiTransform
680 debug(ae_unittest) unittest
681 {
682 	alias M = MapSet!(string, int);
683 	M m = M.unitSet.cartesianProduct("x", [1, 2, 3, 4, 5]);
684 	auto v = MapSetVisitor!(string, int)(m);
685 	v.next();
686 	v.copy("x", "y");
687 	v.transform("y", (ref int v) { v = 6 - v; });
688 	v.multiTransform(["x", "y"], ["z"], (int[] inputs, int[] outputs) { outputs[0] = inputs[0] + inputs[1]; });
689 	assert(v.currentSubset.all("z") == [6]);
690 	assert(!v.next());
691 }