CS3217 Documents

CS3217 Documents

  • About CS3217
  • Problem Sets
  • Tutorials
  • Final Project Guidelines

›Tutorial Solutions

Tutorials

  • Tutorial 1 (week 2)
  • Tutorial 2 (week 3)
  • Tutorial 3 (week 4)
  • Tutorial 4 (week 5)
  • Tutorial 5 (week 6)

Tutorial Solutions

  • Tutorial 1 (week 2) Solutions
  • Tutorial 2 (week 3) Solutions

Tutorial 2 (week 3) Solutions

  1. You would want to start with something small like testing that the board is empty, testing that adding marks work, etc. We can then start going into more Tic-tac-toe specific logic, such as: marking with the same marks consecutively, marking the same cell, marking outside the 3 by 3 grid, and so on. Finally, we can focus on the winning logic: check for horizontal, vertical, and diagonal. We should also check that we cannot place any marks after the game ends.

  2. Basically, this is how you would want to do it:

    func sendEmailToUsers(message: String, database: PDatabase, emailService: PEmailService) {
        let userEmails = database.queryAll(table: "users").map { $0.email }
        emailService.sendEmail(to: userEmails, message: message)
    }
    

    Here, we suppose that PDatabase and PEmailService are both protocols.

    In the code, you want to write something like:

    struct Database: PDatabase {
        ...
    }
    
    struct EmailService: PEmailService {
        ...
    }
    
    // At app initialisation
    let database = Database(...)
    let emailService = EmailService(...)
    
    // When calling the method
    sendEmailToUsers(message: "hello", database: database, emailService: emailService)
    

    In the tests, you can mock this out in this sense:

    // At test utils
    struct DatabaseMock: PDatabase {
        func queryAll(table: String) -> [User] {
            return User(email: "a@example.com")
        }
    }
    
    struct EmailServiceMock: PEmailService {
        private(set) var sentEmails: [String]
        private(set) var sentMessage: String
        func sendEmail(to emails: [String], message: String) {
            sentEmails = emails
            sentMessage = message
        }
    }
    
    // At the actual tests
    func testSendEmailToUsers() {
        let database = DatabaseMock()
        let emailService = EmailServiceMock()
    
        sendEmailToUsers(message: "hello", database: database, emailService: emailService)
        XCTAssertEqual(emailService.sentEmails, ["a@example.com"])
        XCTAssertEqual(emailService.sentMessage, "hello")
    }
    
  3. Something like this is acceptable:

    import Foundation
    
    func intToHexString(_ number: UInt32) -> String {
        return String(format: "%02x", number)
    }
    
    struct SHA1StateMachine {
        private(set) var h0: UInt32
        private(set) var h1: UInt32
        private(set) var h2: UInt32
        private(set) var h3: UInt32
        private(set) var h4: UInt32
    
        mutating func append(a: UInt32, b: UInt32, c: UInt32, d: UInt32, e: UInt32) {
            h0 &+= a
            h1 &+= b
            h2 &+= c
            h3 &+= d
            h4 &+= e
        }
    
        var output: String {
            return [h0, h1, h2, h3, h4].map(intToHexString).joined()
        }
    }
    
    func leftRotate(_ data: UInt32, by amount: Int) -> UInt32 {
        return data << amount | data >> (32 - amount)
    }
    
    func initialiseData(message: String) -> Data {
        var data = Data(message.utf8)
        let ml = data.count
    
        data.append(0x80)
        data.append(contentsOf: Array(repeating: 0,
                                      count: (64 + (55 - ml) % 64) % 64))
        data.append(withUnsafeBytes(of: (ml * 8).bigEndian) { Data($0) })
        assert(data.count.isMultiple(of: 64))
    
        return data
    }
    
    func unpackUInt32(data: Data) -> [UInt32] {
        return stride(from: 0, to: data.count, by: 4).map { i -> UInt32 in
            let word = data[i..<i + 4]
            return word.withUnsafeBytes { bytes in
                bytes.load(as: UInt32.self).bigEndian
            }
        }
    }
    
    func prepareWords(data: Data) -> [UInt32] {
        var words = unpackUInt32(data: data)
    
        for i in 16..<80 {
            words.append(leftRotate(
                words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16],
                by: 1
            ))
        }
    
        return words
    }
    
    func getFAndK(i: Int, b: UInt32, c: UInt32, d: UInt32) -> (UInt32, UInt32) {
        switch i {
        case 0..<20:
            return ((b & c) ^ ((~b) & d), 0x5A827999)
        case 20..<40:
            return (b ^ c ^ d, 0x6ED9EBA1)
        case 40..<60:
            return ((b & c) ^ (b & d) ^ (c & d), 0x8F1BBCDC)
        default:
            return (b ^ c ^ d, 0xCA62C1D6)
        }
    }
    
    func getNewState(word: UInt32, i: Int, a: UInt32, b: UInt32, c: UInt32, d: UInt32, e: UInt32) -> (UInt32, UInt32, UInt32, UInt32, UInt32) {
        let (f, k) = getFAndK(i: i, b: b, c: c, d: d)
        return (leftRotate(a, by: 5) &+ f &+ e &+ k &+ word, a, leftRotate(b, by: 30), c, d)
    }
    
    func processChunk(data: Data, stateMachine: SHA1StateMachine) -> (UInt32, UInt32, UInt32, UInt32, UInt32) {
        let words = prepareWords(data: data)
    
        var (a, b, c, d, e) = (stateMachine.h0, stateMachine.h1, stateMachine.h2, stateMachine.h3, stateMachine.h4)
        for (i, word) in words.enumerated() {
            (a, b, c, d, e) = getNewState(word: word, i: i, a: a, b: b, c: c, d: d, e: e)
        }
        return (a, b, c, d, e)
    }
    
    // Taken from https://en.wikipedia.org/wiki/SHA-1#SHA-1_pseudocode
    func sha1(_ message: String) -> String {
        let data = initialiseData(message: message)
    
        var stateMachine = SHA1StateMachine(h0: 0x67452301, h1: 0xEFCDAB89, h2: 0x98BADCFE, h3: 0x10325476, h4: 0xC3D2E1F0)
        for chunkIndex in stride(from: 0, to: data.count, by: 64) {
            let (a, b, c, d, e) = processChunk(data: data[chunkIndex..<chunkIndex + 64], stateMachine: stateMachine)
            stateMachine.append(a: a, b: b, c: c, d: d, e: e)
        }
    
        return stateMachine.output
    }
    
  4. Here is a sample protocol:

    protocol SudokuGridViewDelegate {
        func sudokuGridView(_: SudokuGridView, shouldPut: Int, at: Cell) -> Bool
    
        func sudokuGridView(_: SudokuGridView, didPut: Int, at: Cell)
    
        func sudokuGridViewShouldUndo(_: SudokuGridView) -> Bool
    
        func sudokuGridViewDidUndo(_: SudokuGridView) -> Bool
    }
    

    Depending on what abstraction layer you wish to operate, you can also have something like:

    protocol SudokuGridViewDelegate {
        func sudokuGridView(_: SudokuGridView, colorOfCell: Cell) -> UIColor
    
        func sudokuGridView(_: SudokuGridView, heightOfRow: Int) -> CGFloat
    }
    

    Note that there are other names for this pattern, if you are a GoF (Gang of Four) follower. The refactoring.guru website has good descriptions on the patterns that are being used. Nevertheless, I would encourage less on remembering the exact names and more on recognising where they can be useful, why there are useful, and actually using them.

← Tutorial 1 (week 2) SolutionsNext →