Solve puzzle with less inventory churn#80
Conversation
Although a Gray code walk can get lucky (I have access to one input file that it solved in just 3 takes, 7 drops, and 7 move attempts; my laptop benchmarked this in 900us), others might not attempt to drop the final item until after traversing through 128 other Gray codes (another input file I tried took 69 takes, 73 drops, and 74 move attempts, and benchmark reports 3.7ms). However, Intcode emulation is expensive (~1500 instructions for a drop, ~2000 for a take, and ~8500 for a move attempt; where there is some variability depending on the item name length and which in-memory array index the items live at), so anything we can do locally to avoid another Intcode command will likely speed up the overall puzzle. In this case, we can make two improvements: first, instead of comparing to see if we are a superset or subset of just the previous state (50% of the time, but only checks one adjacent state in the overall hypercube traversal), we can compare against all other settled states (even if the state we are a superset of was walked 32 or 64 steps ago in the Gray code path); minimizing the size of too_heavy and too_light helps keep this check fast, and the result of better pruning is fewer calls to attempt moves. Second, instead of always changing our current inventory to match the latest Gray code directions, we can defer inventory changes until we lack enough information from superset/subset checks, for fewer calls to take and drop between move attempts. For the two input files I tested, the faster one remains unchanged at 900us and 3/7/7 take/drop/move, but the slower one improved to 2.8ms and a smaller 38/42/43 actions.
|
Of course, if we're willing to peek inside the black box, we can do even better: it's relatively easy to determine the in-memory location of each item's power-of-two weight, sort the items in descending weight order, and then solve the puzzle with at most 8 move attempts. And if we decide that peeking in memory is kosher, it's not much harder to also poke memory to make inventory changes instant rather than going through the take/drop commands. Slightly harder, but even faster, would be to reverse-engineer the scoring system that converts the sum of weights into the output number to be printed on success. |
|
For reference (as much my own as anyone else's), here's a C benchmark program I wrote to compare various strategies. The pre-patch strategy most closely aligns to local_learning_gray (average estimated cost of 875k intcode instructions across all 8! permutations of initial inventory ordering and all 70 8-choose-4 targets, at the point starting where all 8 items are in inventory before the first move onto the pressure plate), and the post-patch strategy is global_learning_virtual_gray (estimated 152k intcode instructions). I also checked askalski's solution which I benchmarked as deduction; it focuses on fewer moves, but ends up with more take/drop inventory churn and a higher average estimated cost (325k intcode instructions, but smaller standard deviation). AI suggests it might also be possible to write a directed search, rather than using Gray codes to follow an entire Hamiltonian path, while still skipping VM changes for the portion of the walk where the global superset/subset filtering is applicable. A directed search would start by immediately dropping 4 items, and then assign a probability to all 8 items (it suggested using an integer value 0-1024, initially 512 for 50%). Each time a set is too heavy, items in the set are penalized (reduce their probabilities by a percentage, say 25%); each time a set is too light, items on the floor are boosted (increase their probabilities by a percentage). After a set is tried and probabilities are updated, the code then computes all 8 possible Gray alterations to the set, tosses any such set whose outcome is already known, and then proceeds to try the remaining set with highest cumulative probability attached to each member of the set, while also trying to favor moves that reach another set of 4 (since it appears that all valid input files have a 4-out-of-8 target). This potential search algorithm can probably converge more quickly than a Gray code walk, but is more extensive to code, so it may add more overhead than what it can save in avoided Intcode commands. I do hope to give it a try, though. |
I've since tried a probabalistic search, and it added to much complexity and potential for chasing dead ends to be with it. The deterministic Gray code walk is good enough, especially when coupled with smarter subset learning and minimized inventory changes. |
Description
Although a Gray code walk can get lucky (I have access to one input file that it solved in just 3 takes, 7 drops, and 7 move attempts; my laptop benchmarked this in 900us), others might not attempt to drop the final item until after traversing through 128 other Gray codes (another input file I tried took 69 takes, 73 drops, and 74 move attempts, and benchmark reports 3.7ms).
However, Intcode emulation is expensive (~1500 instructions for a drop, ~2000 for a take, and ~8500 for a move attempt; where there is some variability depending on the item name length and which in-memory array index the items live at), so anything we can do locally to avoid another Intcode command will likely speed up the overall puzzle. In this case, we can make two improvements: first, instead of comparing to see if we are a superset or subset of just the previous state (50% of the time, but only checks one adjacent state in the overall hypercube traversal), we can compare against all other settled states (even if the state we are a superset of was walked 32 or 64 steps ago in the Gray code path); minimizing the size of too_heavy and too_light helps keep this check fast, and the result of better pruning is fewer calls to attempt moves. Second, instead of always changing our current inventory to match the latest Gray code directions, we can defer inventory changes until we lack enough information from superset/subset checks, for fewer calls to take and drop between move attempts.
For the two input files I tested, the faster one remains unchanged at 900us and 3/7/7 take/drop/move, but the slower one improved to 2.8ms and a smaller 38/42/43 actions.
Type of change
Checklist
using the same naming conventions. Code should be portable, avoiding any
architecture-specific intrinsics.
cargo testcargo fmt -- `find . -name "*.rs"`cargo clippy --all-targets --all-featuresFormatting and linting also can be executed by running
just(if installed) on the command line at the project root.