Since you have 8 elements, and 8 holes you need to fill without repetition and omission, you're dealing with permutations. The number of permutations is given by the formula:
$$^n\mkern-3muP\mkern-1mu_k=\frac{n!}{(n-k)!}$$
Solving given $n = 8$ and $k = 8$ we get:
$$
\begin{equation}
\begin{aligned}
^8\mkern-3muP\mkern-1mu_8 &= \frac{8!}{(8-8)!} \\
&= \frac{40,320}{0!} \\
&= \frac{40,320}{1} \\
&= 40,320 \\
\end{aligned}
\end{equation}
$$
Generating 40,320 grids is a piece of cake, a computer can do that! So a brute-force solution is tractable.
Filtering these 40,320 permutations, we'll keep only those for which the 4 sums are equal (i.e. they constitute valid solutions). After doing so we can see that there are this many solutions:
48
However, they aren't unique solutions, because each true solution appears 4 times (in its four rotated forms).
To duplicate this, I define a normalized()
function. It takes a PuzzleGrid
, computes its 4 rotations, and returns the minimal one by comparison. In my solution, I define the minimal a PuzzleGrid
as that with the lowest first number. If the firsts numbers are equal, I break ties by the second number, and so on.
After de-duplication, there are this many unique solutions:
12
And here they are:
┌─┐ ┌─┐ ┌─┐ ┌─┐
│1│ │1│ │1│ │1│
┌─┬─┼─┤ ┌─┬─┼─┤ ┌─┬─┼─┤ ┌─┬─┼─┤
│2│4│8│ │2│7│6│ │4│3│8│ │6│3│5│
└─┼─┼─┼─┐ └─┼─┼─┼─┐ └─┼─┼─┼─┐ └─┼─┼─┼─┐
│3│5│6│ │3│8│4│ │7│6│2│ │4│8│2│
├─┼─┴─┘ ├─┼─┴─┘ ├─┼─┴─┘ ├─┼─┴─┘
│7│ │5│ │5│ │7│
└─┘ └─┘ └─┘ └─┘
┌─┐ ┌─┐ ┌─┐ ┌─┐
│2│ │2│ │2│ │2│
┌─┬─┼─┤ ┌─┬─┼─┤ ┌─┬─┼─┤ ┌─┬─┼─┤
│3│4│6│ │3│7│4│ │5│1│8│ │7│1│5│
└─┼─┼─┼─┐ └─┼─┼─┼─┐ └─┼─┼─┼─┐ └─┼─┼─┼─┐
│1│5│7│ │1│8│5│ │7│4│3│ │4│6│3│
├─┼─┴─┘ ├─┼─┴─┘ ├─┼─┴─┘ ├─┼─┴─┘
│8│ │6│ │6│ │8│
└─┘ └─┘ └─┘ └─┘
┌─┐ ┌─┐ ┌─┐ ┌─┐
│3│ │3│ │4│ │4│
┌─┬─┼─┤ ┌─┬─┼─┤ ┌─┬─┼─┤ ┌─┬─┼─┤
│4│1│8│ │6│5│2│ │5│1│6│ │7│3│2│
└─┼─┼─┼─┐ └─┼─┼─┼─┐ └─┼─┼─┼─┐ └─┼─┼─┼─┐
│5│2│6│ │1│8│4│ │3│2│7│ │1│6│5│
├─┼─┴─┘ ├─┼─┴─┘ ├─┼─┴─┘ ├─┼─┴─┘
│7│ │7│ │8│ │8│
└─┘ └─┘ └─┘ └─┘
Here's my Swift implementation of a brute force solution:
struct PuzzleGrid {
/* line 1 (down)
0,
line 2 -> 1, 2, 3
line 3 -> 4, 5, 6
7
line 2 ^
*/
let numbers: [Int]
var line1Sum: Int { numbers[0] + numbers[3] + numbers[5] }
var line2Sum: Int { numbers[1] + numbers[2] + numbers[3] }
var line3Sum: Int { numbers[4] + numbers[5] + numbers[6] }
var line4Sum: Int { numbers[2] + numbers[4] + numbers[7] }
var isValid: Bool {
let expectedSum = line1Sum
return expectedSum == line2Sum
&& expectedSum == line3Sum
&& expectedSum == line4Sum
}
/// Return a new PuzzleGrid that's self rotated clockwise by 90 degrees
func rotate() -> PuzzleGrid {
let indices = [
1,
7, 4, 2,
5, 3, 0,
6
]
return PuzzleGrid(numbers: indices.map { self.numbers[$0] })
}
/// Return the "minimal" of the 4 rotations of self
func normalized() -> PuzzleGrid {
let r0 = self
let r1 = r0.rotate()
let r2 = r1.rotate()
let r3 = r2.rotate()
assert(r3.rotate() == r0)
return [r0, r1, r2, r3].min()!
}
}
extension PuzzleGrid: Comparable {
static func < (lhs: PuzzleGrid, rhs: PuzzleGrid) -> Bool {
for (leftNumber, rightNumber) in zip(lhs.numbers, rhs.numbers) {
if leftNumber < rightNumber { return true }
else if leftNumber > rightNumber { return false }
else { continue }
}
assert(lhs.numbers == rhs.numbers)
return true
}
}
extension PuzzleGrid: Hashable {}
extension PuzzleGrid: CustomStringConvertible {
var description: String {
let (a, b, c, d, e, f, g, h) = (
numbers[0], numbers[1], numbers[2], numbers[3],
numbers[4], numbers[5], numbers[6], numbers[7]
)
return """
┌─┐
│\(a)│
┌─┬─┼─┤
│\(b)│\(c)│\(d)│
└─┼─┼─┼─┐
│\(e)│\(f)│\(g)│
├─┼─┴─┘
│\(h)│
└─┘
"""
}
}
/// Given remainingElements, and the prefix, generate all permutations of remainingElements,
/// prepending the prefix to each permutation.
func createPermutations(
from remainingElements: Set<Int>,
prefix: [Int] = []
) -> [[Int]] {
if remainingElements.count == 1 { return [prefix + [remainingElements.first!]] }
return remainingElements.flatMap { element -> [[Int]] in
var newRemainingElements = remainingElements
newRemainingElements.remove(element)
return createPermutations(
from: newRemainingElements,
prefix: prefix + [element]
)
}
}
let allPermutations = createPermutations(from: [1, 2, 3, 4, 5, 6, 7, 8])
print("Total number of permutations: \(allPermutations.count)")
let allPuzzleGrids = allPermutations.map(PuzzleGrid.init(numbers:))
let solutions = allPuzzleGrids.filter(\.isValid)
print("Total number of solutions: \(solutions.count)")
let uniqueSolutions = Set(solutions.map { $0.normalized() }).sorted()
print("Total number of unique solutions: \(uniqueSolutions.count)")
uniqueSolutions.forEach { print($0) }