I created this disgusting mess of a recursive search that happened to work. This problem was really hard to think about due to the levels of indirection. It was also hard because of a bug I introduced into my code that would have been easy to debug with more print statements, but hubris.
P2
Recursive solution from P1 was too slow, once I was at 7 robots it was taking minutes to run the code. It didn't take long to realise that you don't really care about where the robots other than the keypad robot and the one controlling the keypad robot are since the boundary of each state needs all the previous robots to be on the A button. So with memoisation, you can calculate all the shortest paths for a given robot to each of the directional inputs in constant time, so O(kn) all up where n is the number of robots (25) and k is the complexity of searching for a path over 5 or 11 nodes.
What helped was looking at the penultimate robot's button choices when moving the keypad robot. After the first one or two levels, the transitions settle into the table in the appendix. I will not explain the code.
Got lucky on the max clique in part 2, my solution only works if there are at least 2 nodes in the clique, that only have the clique members as common neighbours.
Ended up reading wikipedia to lift one the Bron-Kerbosch methods:
#!/usr/bin/env jq -n -rR -f
reduce (
inputs / "-" # Build connections dictionary #
) as [$a,$b] ({}; .[$a] += [$b] | .[$b] += [$a]) | . as $conn |
# Allow Loose max clique check #
if $ARGS.named.loose == true then
# Only works if there is at least one pair in the max clique #
# That only have the clique members in common. #
[
# For pairs of connected nodes #
( $conn | keys[] ) as $a | $conn[$a][] as $b | select($a < $b) |
# Get the list of nodes in common #
[$a,$b] + ($conn[$a] - ($conn[$a]-$conn[$b])) | unique
]
# From largest size find the first where all the nodes in common #
# are interconnected -> all(connections ⋂ shared == shared) #
| sort_by(-length)
| first (
.[] | select( . as $cb |
[
$cb[] as $c
| ( [$c] + $conn[$c] | sort )
| ( . - ( . - $cb) ) | length
] | unique | length == 1
)
)
else # Do strict max clique check #
# Example of loose failure:
# 0-1 0-2 0-3 0-4 0-5 1-2 1-3 1-4 1-5
# 2-3 2-4 2-5 3-4 3-5 4-5 a-0 a-1 a-2
# a-3 b-2 b-3 b-4 b-5 c-0 c-1 c-4 c-5
def bron_kerbosch1($R; $P; $X; $cliques):
if ($P|length) == 0 and ($X|length) == 0 then
if ($R|length) > 2 then
{cliques: ($cliques + [$R|sort])}
end
else
reduce $P[] as $v ({$R,$P,$X,$cliques};
.cliques = bron_kerbosch1(
.R - [$v] + [$v] ; # R ∪ {v}
.P - (.P - $conn[$v]); # P ∩ neighbours(v)
.X - (.X - $conn[$v]); # X ∩ neighbours(v)
.cliques
) .cliques |
.P = (.P - [$v]) | # P ∖ {v}
.X = (.X - [$v] + [$v]) # X ∪ {v}
)
end
;
bron_kerbosch1([];$conn|keys;[];[]).cliques | max_by(length)
end
| join(",") # Output password