Solving a rotating combination lock puzzle
I'd implement backtracking like this:
cylinders = {{4, 1, 1, 1, 3, 1}, {3, 1, 1, 1, 2, 1}, {1, 2, 2, 4, 1, 3}, {3, 2, 1, 2, 3, 1}};
sums = FromRomanNumeral[{"XI", "V", "X", "IV", "IX", "VI"}];
bt[rotations_] := If[
Length[rotations] == 4,
If[evaluate[rotations] == sums, Throw[rotations]],
If[
! impossible[rotations],
bt[Append[rotations, #]] & /@ Range[0, 5]
]
]
evaluate[rotations_, l_ : All] := Total@MapThread[
RotateRight,
{Take[cylinders, l], rotations}
]
impossible[rotations_] := AnyTrue[
sums - evaluate[rotations, Length[rotations]],
# < 0 &
]
bt[{0}] // Catch
{0, 2, 3, 4}
The more ways you can come up with to exclude series of rotations that you know cannot occur, the better it will perform. In this case I used sums - evaluate[rotations, Length[rotations]]
to rule out solutions. However, it is possible to restrict the solutions even more by using sums - evaluate[rotations, Length[rotations]] - (4 - Length[rotations])
because the smallest value a cylinder can have is 1.
Here is another approach. Use Solve to find the 129 solutions without considerations for the order of elements in a cylinder. Then, check these solutions and keep the one consistent with rotation of the given order.
Clear[a1, a2, a3, a4, a5, a6, b1, b2, b3, b4, b5, b6, c1, c2, c3, c4,
c5, c6, d1, d2, d3, d4, d5, d6];
cylinders = {{4, 1, 1, 1, 3, 1}, {3, 1, 1, 1, 2, 1}, {1, 2, 2, 4, 1,
3}, {3, 2, 1, 2, 3, 1}};
sums = FromRomanNumeral[{"XI", "V", "X", "IV", "IX", "VI"}];
(* function takes two lists and returns True if equivalent by \
rotation *)
isRotation[cyl_List, sol_List] := (
double = Flatten[Append[cyl, cyl]];
seq = SequenceCases[double, sol];
If[seq == {}, False, True]
)
(* To name variables to solve for,the four cylinders are assigned a \
letter a to d. Each of the six values for a cylinder is assigned a \
number from 1 to 6. This gives 129 solutions. *)
mySolution =
Solve[a1 + b1 + c1 + d1 == sums[[1]] &&
a2 + b2 + c2 + d2 == sums[[2]] &&
a3 + b3 + c3 + d3 == sums[[3]] &&
a4 + b4 + c4 + d4 == sums[[4]] &&
a5 + b5 + c5 + d5 == sums[[5]] &&
a6 + b6 + c6 + d6 == sums[[6]] &&
a1 + a2 + a3 + a4 + a5 + a6 == Total[cylinders[[1]]] &&
b1 + b2 + b3 + b4 + b5 + b6 == Total[cylinders[[2]]] &&
c1 + c2 + c3 + c4 + c5 + c6 == Total[cylinders[[3]]] &&
d1 + d2 + d3 + d4 + d5 + d6 == Total[cylinders[[4]]] &&
5 > a1 > 0 && 5 > a2 > 0 && 5 > a3 > 0 && 5 > a4 > 0 &&
5 > a5 > 0 && 5 > a6 > 0 &&
5 > b1 > 0 && 5 > b2 > 0 &&
5 > b3 > 0 && 5 > b4 > 0 && 5 > b5 > 0 && 5 > b6 > 0 &&
5 > c1 > 0 && 5 > c2 > 0 && 5 > c3 > 0 && 5 > c4 > 0 &&
5 > c5 > 0 && 5 > c6 > 0 &&
5 > d1 > 0 && 5 > d2 > 0 &&
5 > d3 > 0 && 5 > d4 > 0 && 5 > d5 > 0 && 5 > d6 > 0 &&
a1 a2 a3 a4 a5 a6 == Times @@ cylinders[[1]] &&
b1 b2 b3 b4 b5 b6 == Times @@ cylinders[[2]] &&
c1 c2 c3 c4 c5 c6 == Times @@ cylinders[[3]] &&
d1 d2 d3 d4 d5 d6 == Times @@ cylinders[[4]],
{a1, a2, a3, a4, a5, a6, b1, b2, b3, b4, b5, b6, c1, c2, c3, c4,
c5, c6, d1, d2, d3, d4, d5, d6}, Integers
];
(* Go through the solutions and select the one consistent with \
cylinder rotation *)
n = 1;
While[n <= Length[mySolution],
truthValues = {};
v = Values[mySolution[[n]]];
parts = Partition[v, 6];
i = 1;
While[i <= Length[parts],
truthValues =
Append[truthValues, isRotation[cylinders[[i]], parts[[i]]]];
i++;
];
If[truthValues == {True, True, True, True},
Print[Column[Partition[Values[mySolution[[n]]], 6]]]
];
n++;
]
(* ==== SOLUTION ==== *)
{4,1,1,1,3,1}
{2,1,3,1,1,1}
{4,1,3,1,2,2}
{1,2,3,1,3,2}```