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 }