Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Solving the 15-puzzle in Swift: A Look at Algorithms and Speed

Bas Broek
August 29, 2019

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.

Bas Broek

August 29, 2019
Tweet

More Decks by Bas Broek

Other Decks in Programming

Transcript

  1. SOLVING THE 15 PUZZLE IN
    SWIFT
    A Look at Algorithms and Speed
    1 — @basthomas

    View Slide

  2. SOLVING THE 15 PUZZLE IN
    SWIFT
    A Look at Algorithms and Speed
    A Look at Algorithms and Performance
    2 — @basthomas

    View Slide

  3. WHAT IS A 15
    PUZZLE?
    3 — @basthomas

    View Slide

  4. | 1| 2| 3| 4|
    | 5| 6| 7| 8|
    | 9|10|11|12|
    |13|14|15| |
    4 — @basthomas

    View Slide

  5. | 1| 6| 2| 3|
    | |10| 7| 4|
    | 5| 9|11| 8|
    |13|14|15|12|
    5 — @basthomas

    View Slide

  6. | 1| 6| 2| 3|
    | 5|10| 7| 4|
    | | 9|11| 8|
    |13|14|15|12|
    6 — @basthomas

    View Slide

  7. | 1| 6| 2| 3|
    | 5|10| 7| 4|
    | 9| |11| 8|
    |13|14|15|12|
    7 — @basthomas

    View Slide

  8. | 1| 6| 2| 3|
    | 5| | 7| 4|
    | 9|10|11| 8|
    |13|14|15|12|
    8 — @basthomas

    View Slide

  9. | 1| | 2| 3|
    | 5| 6| 7| 4|
    | 9|10|11| 8|
    |13|14|15|12|
    9 — @basthomas

    View Slide

  10. | 1| 2| | 3|
    | 5| 6| 7| 4|
    | 9|10|11| 8|
    |13|14|15|12|
    10 — @basthomas

    View Slide

  11. | 1| 2| 3| |
    | 5| 6| 7| 4|
    | 9|10|11| 8|
    |13|14|15|12|
    11 — @basthomas

    View Slide

  12. | 1| 2| 3| 4|
    | 5| 6| 7| |
    | 9|10|11| 8|
    |13|14|15|12|
    12 — @basthomas

    View Slide

  13. | 1| 2| 3| 4|
    | 5| 6| 7| 8|
    | 9|10|11| |
    |13|14|15|12|
    13 — @basthomas

    View Slide

  14. | 1| 2| 3| 4|
    | 5| 6| 7| 8|
    | 9|10|11|12|
    |13|14|15| |
    14 — @basthomas

    View Slide

  15. BEFORE WE START, SOME
    HISTORY
    15 — @basthomas

    View Slide

  16. TVOS... DOES ANYONE STILL
    REMEMBER THAT?
    (OUTSIDE OF THE US)
    16 — @basthomas

    View Slide

  17. THIS WAS 4(!)
    YEARS AGO
    17 — @basthomas

    View Slide

  18. 18 — @basthomas

    View Slide

  19. UICollectionView
    HOUSES SOME VERY, VERY
    COOL APIS
    19 — @basthomas

    View Slide

  20. FAST-FORWARD TO JANUARY
    THIS YEAR
    20 — @basthomas

    View Slide

  21. START...
    SOMEWHERE
    21 — @basthomas

    View Slide

  22. START... WITH
    THE SOLUTION
    22 — @basthomas

    View Slide

  23. struct Solution {
    struct Step {
    let step: T
    }
    let steps: [Step]
    var input: Step { return steps.first! }
    var output: Step { return steps.last! }
    init(steps: [Step]) {
    precondition(steps.count > 0, "Solution must contain at least one step.")
    self.steps = steps
    }
    }
    23 — @basthomas

    View Slide

  24. let solution = Solution(
    steps: Solution.Step(step: 1), Solution.Step(step: 2), Solution.Step(step: 3)
    )
    print(solution.input) // Step(step: 1)
    print(solution.output) // Step(step: 3)
    24 — @basthomas

    View Slide

  25. COMFORMING TO
    Collection
    25 — @basthomas

    View Slide

  26. struct Solution: Collection {
    var startIndex: Int {
    return steps.startIndex
    }
    var endIndex: Int {
    return steps.endIndex
    }
    subscript(i: Int) -> Step {
    return steps[i]
    }
    func index(after i: Int) -> Int {
    return steps.index(after: i)
    }
    }
    26 — @basthomas

    View Slide

  27. for step in solution {
    print(step)
    }
    27 — @basthomas

    View Slide

  28. BUT NOW WHAT?
    28 — @basthomas

    View Slide

  29. LAYING IT (ALL)
    OUT
    29 — @basthomas

    View Slide

  30. struct Board {
    struct Position {}
    enum Tile { case empty, number(Int) }
    func next()
    func position(for tile: Tile) -> Position
    func tile(at position: Position) -> Tile
    func swap(_ aTile: Tile, with bTile: Tile) -> Bool
    func move(tile: Tile) -> Bool
    func shuffle(moves: Int = 50)
    func adjacentPositions(to position: Position) -> [Position]
    func solve() -> Solution
    }
    30 — @basthomas

    View Slide

  31. struct Position: CustomStringConvertible, Equatable {
    let row: Int
    let column: Int
    func isAdjacent(to position: Position, in board: Board) -> Bool {
    return board.adjacentPositions(to: self)
    .filter { $0 == position }
    .isEmpty == false
    }
    var description: String {
    return "row: \(row), column: \(column)"
    }
    }
    31 — @basthomas

    View Slide

  32. func isAdjacent(to position: Position, in board: Board) -> Bool {
    return board.adjacentPositions(to: self)
    - .filter { $0 == position }
    - .isEmpty == false
    + .first { $0 == position } != nil
    }
    32 — @basthomas

    View Slide

  33. IT'S MAGICAL
    33 — @basthomas

    View Slide

  34. IT'S MAGICAL
    THAT IT IS NOT.
    34 — @basthomas

    View Slide

  35. LET'S LOOK AT A
    Tile
    35 — @basthomas

    View Slide

  36. enum Tile: Equatable, ExpressibleByIntegerLiteral {
    case empty, number(Int)
    private static let emptyValue = -1
    init(integerLiteral value: Int) {
    if value == Tile.emptyValue {
    self = .empty
    } else {
    self = .number(value)
    }
    }
    var intValue: Int {
    switch self {
    case .empty: return Tile.emptyValue
    case .number(let number): return number
    }
    }
    }
    36 — @basthomas

    View Slide

  37. NOW IT'S ALMOST TIME FOR
    THE REAL DEAL...
    37 — @basthomas

    View Slide

  38. 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

    View Slide

  39. 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

    View Slide

  40. 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

    View Slide

  41. 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

    View Slide

  42. 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

    View Slide

  43. 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

    View Slide

  44. ALGORITHMS?
    39 — @basthomas

    View Slide

  45. SPEED?
    SCALE?
    PERFORMANCE..?
    40 — @basthomas

    View Slide

  46. WHAT IS
    PERFORMANCE?
    41 — @basthomas

    View Slide

  47. PERFORMANCE, NOUN
    per·for·mance
    / pərˈfɔr məns /
    per-fawr-muh ns
    a: the execution of an action
    b: something accomplished
    42 — @basthomas

    View Slide

  48. MAYBE IT'S NOT JUST ABOUT
    SPEED AND SCALE
    43 — @basthomas

    View Slide

  49. PERFORMANCE IS
    A MINDSET
    44 — @basthomas

    View Slide

  50. WHERE TO
    START?
    45 — @basthomas

    View Slide

  51. @discardableResult mutating func solve() -> Solution {
    var boards: [Board] = []
    repeat {
    next()
    boards.append(self)
    } while isSolved == false
    return Solution(steps: boards.map(Solution.Step.init))
    }
    46 — @basthomas

    View Slide

  52. private func adjacentPositions(to position: Position) -> [Position] {
    var adjacentPositions: [Position] = []
    var positions: [Position] {
    return initialBoard
    .flatMap { $0 }
    .map(position(for:))
    }
    /// ???
    precondition(
    adjacentPositions.count <= 4,
    "Can't have more than four adjacent positions, got \(adjacentPositions)"
    )
    precondition(
    adjacentPositions.count >= 2,
    "Must have at least two adjacent positions, got \(adjacentPositions)"
    )
    return adjacentPositions
    }
    47 — @basthomas

    View Slide

  53. private func adjacentPositions(to position: Position) -> [Position] {
    var adjacentPositions: [Position] = []
    var positions: [Position] {
    return initialBoard
    .flatMap { $0 }
    .map(position(for:))
    }
    /// ???
    precondition(
    adjacentPositions.count <= 4,
    "Can't have more than four adjacent positions, got \(adjacentPositions)"
    )
    precondition(
    adjacentPositions.count >= 2,
    "Must have at least two adjacent positions, got \(adjacentPositions)"
    )
    return adjacentPositions
    }
    47 — @basthomas

    View Slide

  54. private func adjacentPositions(to position: Position) -> [Position] {
    var adjacentPositions: [Position] = []
    var positions: [Position] {
    return initialBoard
    .flatMap { $0 }
    .map(position(for:))
    }
    /// ???
    precondition(
    adjacentPositions.count <= 4,
    "Can't have more than four adjacent positions, got \(adjacentPositions)"
    )
    precondition(
    adjacentPositions.count >= 2,
    "Must have at least two adjacent positions, got \(adjacentPositions)"
    )
    return adjacentPositions
    }
    47 — @basthomas

    View Slide

  55. private func adjacentPositions(to position: Position) -> [Position] {
    var adjacentPositions: [Position] = []
    var positions: [Position] {
    return initialBoard
    .flatMap { $0 }
    .map(position(for:))
    }
    /// ???
    precondition(
    adjacentPositions.count <= 4,
    "Can't have more than four adjacent positions, got \(adjacentPositions)"
    )
    precondition(
    adjacentPositions.count >= 2,
    "Must have at least two adjacent positions, got \(adjacentPositions)"
    )
    return adjacentPositions
    }
    47 — @basthomas

    View Slide

  56. private func adjacentPositions(to position: Position) -> [Position] {
    var adjacentPositions: [Position] = []
    var positions: [Position] {
    return initialBoard
    .flatMap { $0 }
    .map(position(for:))
    }
    /// ???
    precondition(
    adjacentPositions.count <= 4,
    "Can't have more than four adjacent positions, got \(adjacentPositions)"
    )
    precondition(
    adjacentPositions.count >= 2,
    "Must have at least two adjacent positions, got \(adjacentPositions)"
    )
    return adjacentPositions
    }
    47 — @basthomas

    View Slide

  57. for loopingPosition in positions where position != loopingPosition {
    switch loopingPosition {
    case Position(row: position.row, column: position.column - 1):
    fallthrough // above
    case Position(row: position.row - 1, column: position.column):
    fallthrough // left
    case Position(row: position.row + 1, column: position.column):
    fallthrough // below
    case Position(row: position.row, column: position.column + 1):
    precondition(adjacentPositions.contains(loopingPosition) == false)
    adjacentPositions.append(loopingPosition) // right
    default:
    continue // no match
    }
    }
    48 — @basthomas

    View Slide

  58. for loopingPosition in positions where position != loopingPosition {
    switch loopingPosition {
    case Position(row: position.row, column: position.column - 1):
    fallthrough // above
    case Position(row: position.row - 1, column: position.column):
    fallthrough // left
    case Position(row: position.row + 1, column: position.column):
    fallthrough // below
    case Position(row: position.row, column: position.column + 1):
    precondition(adjacentPositions.contains(loopingPosition) == false)
    adjacentPositions.append(loopingPosition) // right
    default:
    continue // no match
    }
    }
    48 — @basthomas

    View Slide

  59. for loopingPosition in positions where position != loopingPosition {
    switch loopingPosition {
    case Position(row: position.row, column: position.column - 1):
    fallthrough // above
    case Position(row: position.row - 1, column: position.column):
    fallthrough // left
    case Position(row: position.row + 1, column: position.column):
    fallthrough // below
    case Position(row: position.row, column: position.column + 1):
    precondition(adjacentPositions.contains(loopingPosition) == false)
    adjacentPositions.append(loopingPosition) // right
    default:
    continue // no match
    }
    }
    48 — @basthomas

    View Slide

  60. for loopingPosition in positions where position != loopingPosition {
    switch loopingPosition {
    case Position(row: position.row, column: position.column - 1):
    fallthrough // above
    case Position(row: position.row - 1, column: position.column):
    fallthrough // left
    case Position(row: position.row + 1, column: position.column):
    fallthrough // below
    case Position(row: position.row, column: position.column + 1):
    precondition(adjacentPositions.contains(loopingPosition) == false)
    adjacentPositions.append(loopingPosition) // right
    default:
    continue // no match
    }
    }
    48 — @basthomas

    View Slide

  61. for loopingPosition in positions where position != loopingPosition {
    switch loopingPosition {
    case Position(row: position.row, column: position.column - 1):
    fallthrough // above
    case Position(row: position.row - 1, column: position.column):
    fallthrough // left
    case Position(row: position.row + 1, column: position.column):
    fallthrough // below
    case Position(row: position.row, column: position.column + 1):
    precondition(adjacentPositions.contains(loopingPosition) == false)
    adjacentPositions.append(loopingPosition) // right
    default:
    continue // no match
    }
    }
    48 — @basthomas

    View Slide

  62. for loopingPosition in positions where position != loopingPosition {
    switch loopingPosition {
    case Position(row: position.row, column: position.column - 1):
    fallthrough // above
    case Position(row: position.row - 1, column: position.column):
    fallthrough // left
    case Position(row: position.row + 1, column: position.column):
    fallthrough // below
    case Position(row: position.row, column: position.column + 1):
    precondition(adjacentPositions.contains(loopingPosition) == false)
    adjacentPositions.append(loopingPosition) // right
    default:
    continue // no match
    }
    }
    48 — @basthomas

    View Slide

  63. 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

    View Slide

  64. 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

    View Slide

  65. 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

    View Slide

  66. 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

    View Slide

  67. 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

    View Slide

  68. REWIRING YOUR
    BRAIN
    50 — @basthomas

    View Slide

  69. BRANCH... AND BOUND
    51 — @basthomas

    View Slide

  70. BRANCH... AND BOUND
    ▸ What are the options?
    51 — @basthomas

    View Slide

  71. BRANCH... AND BOUND
    ▸ What are the options?
    ▸ Is there a best option?
    51 — @basthomas

    View Slide

  72. BRANCH... AND BOUND
    ▸ What are the options?
    ▸ Is there a best option?
    ▸ Do it again
    51 — @basthomas

    View Slide

  73. mutating func next() {
    let currentBoard = self.currentBoard
    let adjacentToEmpty = adjacentPositions(to: position(for: .empty))
    let boardOptions = adjacentToEmpty.map { position -> (board: [[Tile]], moved: Tile, validPositions: Int) in
    let tileToMove = tile(at: position)
    move(tile: tileToMove)
    defer { self.currentBoard = currentBoard }
    return (self.currentBoard, tileToMove, validPositions)
    }.filter { $0.moved != _previousNextTile }
    let amountOfValidPositionsList = boardOptions.map { $0.validPositions }
    let largestAmountOfValidPositions = amountOfValidPositionsList.max()
    precondition(largestAmountOfValidPositions != nil, "No maximum valid positions found in \(boardOptions)")
    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
    }
    52 — @basthomas

    View Slide

  74. let currentBoard = self.currentBoard
    let adjacentToEmpty = adjacentPositions(to: position(for: .empty))
    53 — @basthomas

    View Slide

  75. let boardOptions = adjacentToEmpty.map { position -> (board: [[Tile]], moved: Tile, validPositions: Int) in
    let tileToMove = tile(at: position)
    move(tile: tileToMove)
    defer { self.currentBoard = currentBoard }
    return (self.currentBoard, tileToMove, validPositions)
    }.filter { $0.moved != _previousNextTile }
    54 — @basthomas

    View Slide

  76. let amountOfValidPositionsList = boardOptions.map { $0.validPositions }
    let largestAmountOfValidPositions = amountOfValidPositionsList.max()
    precondition(
    largestAmountOfValidPositions != nil,
    "No maximum valid positions found in \(boardOptions)"
    )
    55 — @basthomas

    View Slide

  77. 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

    View Slide

  78. AN EXAMPLE
    57 — @basthomas

    View Slide

  79. | 1| 2| 3| 4|
    | 5|10| 6|11|
    | 9|14| 8| 7|
    |13| |15|12|
    58 — @basthomas

    View Slide

  80. ADJACENT POSITIONS
    59 — @basthomas

    View Slide

  81. ADJACENT POSITIONS
    ▸ 13
    59 — @basthomas

    View Slide

  82. ADJACENT POSITIONS
    ▸ 13
    ▸ 14
    59 — @basthomas

    View Slide

  83. ADJACENT POSITIONS
    ▸ 13
    ▸ 14
    ▸ 15
    59 — @basthomas

    View Slide

  84. VALID POSITIONS
    60 — @basthomas

    View Slide

  85. VALID POSITIONS
    ▸ 13 (right) > 7
    60 — @basthomas

    View Slide

  86. VALID POSITIONS
    ▸ 13 (right) > 7
    ▸ 14 (down) > 9
    60 — @basthomas

    View Slide

  87. VALID POSITIONS
    ▸ 13 (right) > 7
    ▸ 14 (down) > 9
    ▸ 15 (left) > 7
    60 — @basthomas

    View Slide

  88. VALID POSITIONS
    ▸ 13 (right) > 7
    ▸ 14 (down) > 9
    ▸ 15 (left) > 7
    ▸ ... and repeat
    60 — @basthomas

    View Slide

  89. BE YOUR OWN
    BEST FRIEND
    ... AND FAIL EARLY
    61 — @basthomas

    View Slide

  90. But this will never happen!
    62 — @basthomas

    View Slide

  91. BREAK UP THE PUZZLE... IN
    PIECES
    63 — @basthomas

    View Slide

  92. START
    SOMEWHERE
    64 — @basthomas

    View Slide

  93. MAKE YOUR "EDGE" CASES
    EXPLICIT
    LET THE COMPILER HELP
    65 — @basthomas

    View Slide

  94. SOLVING THE 15 PUZZLE IN
    SWIFT
    A Look at Algorithms and Speed
    66 — @basthomas

    View Slide