Solving the 15-puzzle in Swift: A Look at Algorithms and Speed
Algorithms and optimization can sound daunting, but are a really interesting programming problem. In this talk, we'll be looking at writing a solver for a puzzle, improving it along the way by making it more performant.
init(rows: Int) { precondition(rows > 1, "A puzzle should at least be 2x2") var _board: [[Tile]] = [] for row in 0.._board.append([]) for column in 0..let number = (row * rows) + column + 1 if row == rows - 1 && column == rows - 1 { _board[row].append(.empty) } else { _board[row].append(.number(number)) } } } } 38 — @basthomas
init(rows: Int) { precondition(rows > 1, "A puzzle should at least be 2x2") var _board: [[Tile]] = [] for row in 0.._board.append([]) for column in 0..let number = (row * rows) + column + 1 if row == rows - 1 && column == rows - 1 { _board[row].append(.empty) } else { _board[row].append(.number(number)) } } } } 38 — @basthomas
init(rows: Int) { precondition(rows > 1, "A puzzle should at least be 2x2") var _board: [[Tile]] = [] for row in 0.._board.append([]) for column in 0..let number = (row * rows) + column + 1 if row == rows - 1 && column == rows - 1 { _board[row].append(.empty) } else { _board[row].append(.number(number)) } } } } 38 — @basthomas
init(rows: Int) { precondition(rows > 1, "A puzzle should at least be 2x2") var _board: [[Tile]] = [] for row in 0.._board.append([]) for column in 0..let number = (row * rows) + column + 1 if row == rows - 1 && column == rows - 1 { _board[row].append(.empty) } else { _board[row].append(.number(number)) } } } } 38 — @basthomas
init(rows: Int) { precondition(rows > 1, "A puzzle should at least be 2x2") var _board: [[Tile]] = [] for row in 0.._board.append([]) for column in 0..let number = (row * rows) + column + 1 if row == rows - 1 && column == rows - 1 { _board[row].append(.empty) } else { _board[row].append(.number(number)) } } } } 38 — @basthomas
init(rows: Int) { precondition(rows > 1, "A puzzle should at least be 2x2") var _board: [[Tile]] = [] for row in 0.._board.append([]) for column in 0..let number = (row * rows) + column + 1 if row == rows - 1 && column == rows - 1 { _board[row].append(.empty) } else { _board[row].append(.number(number)) } } } } 38 — @basthomas
mutating func shuffle(moves: Int = 50) { for _ in 1...moves { let adjacentToEmpty = adjacentPositions(to: position(for: .empty)) precondition( adjacentToEmpty.count >= 2, "Should always have at least two positions adjacent to empty" ) // Remove the previously moved tile, so we do not move a tile // back-and-forth. That would be rather pointless. let adjacentWithoutPrevious = adjacentToEmpty .filter { $0 != position(for: _previouslyShuffledTile) } let randomAdjacent = adjacentWithoutPrevious.randomElement()! let randomTile = tile(at: randomAdjacent) move(tile: randomTile) _previouslyShuffledTile = randomTile } } 49 — @basthomas
mutating func shuffle(moves: Int = 50) { for _ in 1...moves { let adjacentToEmpty = adjacentPositions(to: position(for: .empty)) precondition( adjacentToEmpty.count >= 2, "Should always have at least two positions adjacent to empty" ) // Remove the previously moved tile, so we do not move a tile // back-and-forth. That would be rather pointless. let adjacentWithoutPrevious = adjacentToEmpty .filter { $0 != position(for: _previouslyShuffledTile) } let randomAdjacent = adjacentWithoutPrevious.randomElement()! let randomTile = tile(at: randomAdjacent) move(tile: randomTile) _previouslyShuffledTile = randomTile } } 49 — @basthomas
mutating func shuffle(moves: Int = 50) { for _ in 1...moves { let adjacentToEmpty = adjacentPositions(to: position(for: .empty)) precondition( adjacentToEmpty.count >= 2, "Should always have at least two positions adjacent to empty" ) // Remove the previously moved tile, so we do not move a tile // back-and-forth. That would be rather pointless. let adjacentWithoutPrevious = adjacentToEmpty .filter { $0 != position(for: _previouslyShuffledTile) } let randomAdjacent = adjacentWithoutPrevious.randomElement()! let randomTile = tile(at: randomAdjacent) move(tile: randomTile) _previouslyShuffledTile = randomTile } } 49 — @basthomas
mutating func shuffle(moves: Int = 50) { for _ in 1...moves { let adjacentToEmpty = adjacentPositions(to: position(for: .empty)) precondition( adjacentToEmpty.count >= 2, "Should always have at least two positions adjacent to empty" ) // Remove the previously moved tile, so we do not move a tile // back-and-forth. That would be rather pointless. let adjacentWithoutPrevious = adjacentToEmpty .filter { $0 != position(for: _previouslyShuffledTile) } let randomAdjacent = adjacentWithoutPrevious.randomElement()! let randomTile = tile(at: randomAdjacent) move(tile: randomTile) _previouslyShuffledTile = randomTile } } 49 — @basthomas
mutating func shuffle(moves: Int = 50) { for _ in 1...moves { let adjacentToEmpty = adjacentPositions(to: position(for: .empty)) precondition( adjacentToEmpty.count >= 2, "Should always have at least two positions adjacent to empty" ) // Remove the previously moved tile, so we do not move a tile // back-and-forth. That would be rather pointless. let adjacentWithoutPrevious = adjacentToEmpty .filter { $0 != position(for: _previouslyShuffledTile) } let randomAdjacent = adjacentWithoutPrevious.randomElement()! let randomTile = tile(at: randomAdjacent) move(tile: randomTile) _previouslyShuffledTile = randomTile } } 49 — @basthomas
let amountOfValidPositionsList = boardOptions.map { $0.validPositions } let largestAmountOfValidPositions = amountOfValidPositionsList.max() precondition( largestAmountOfValidPositions != nil, "No maximum valid positions found in \(boardOptions)" ) 55 — @basthomas
let nextBestSteps = amountOfValidPositionsList.filter { $0 == largestAmountOfValidPositions } let bestStepIndex = boardOptions.firstIndex { $0.validPositions == nextBestSteps.first } precondition(bestStepIndex != nil, "Should always have an index for the next best step") let bestOption = boardOptions[bestStepIndex!] self.currentBoard = bestOption.board _previousNextTile = bestOption.moved 56 — @basthomas