Swift 進階 | 看得見的演算法

Castie1發表於2018-05-18

GitHub Repo:coderZsq.target.swift
Follow: coderZsq · GitHub
Resume: coderzsq.github.io/coderZsq.we…

日常扯淡

前段日子寫了篇面經, 得到了掘金徵文活動三等獎還是非常開心, 但是被寒神說面試不過就洩題, 影響不好, 我想想也是, 馬上就把大廠的名字給抹掉了, 但被轉載的就無能為力了, 看到下面好多噴的, 真是背後一涼, 一首涼涼送給自己, 造成的傷害無法挽回, 再此鄭重道歉, 再也不寫面經了. 希望以後還能獲得大廠面試的機會!

大廠刷掉後, 我心情難以平復, 因為其實我是做了充足的準備來的, 但是還是實力上有差距, 誒... 還是想想改如何提升自己的水平吧!

其實對於自己, 我其實並不知道iOS該如何進行學習, 也不知道技術這條路我半道轉的是不是正確, 更不知道在當今環境下我這種水平的iOS開發者是否還有存在的必要.

說起轉行做iOS到現在, 從Objective-C基礎語法學起, 認為OC是最好的語言, 學會做幾個簡單的UITableView頁面, 能播放音訊視訊, 覺得自己真是轉了個高大上的職業, 現在想想, 真是膚淺的讓人忍不住發笑.

我把做iOS這段學習經歷分為五個階段:

第一個階段: 是剛剛轉行進入網際網路企業, 那時候覺得學好CALayer掌握了一些酷炫的動畫(貝塞爾曲線), 感覺就已經比大多數人都強了, 那時候還把常用的工具類封裝起來, 就覺得, 嗯, 自己還不錯.

第二個階段: 就是瞎學些有的沒的, 如Swift, JavaScript, Java什麼的, 覺得自己全棧了, 什麼都會了, 很牛逼, 你看還自己能夠寫一個前端簡歷, 還做過公司的前端專案, 伺服器開發也會了, 自信心爆棚啊, 覺得自己真是無所不能.

第三個階段: 寫了一個架構生成器, 覺得自己所向披靡了, 公司專案都在用我寫的架構, 用著我制定的規則, 尼瑪不就是一個簡單的字串替換, 嘚瑟個啥... 而且對於架構的理解膚淺至極...

第四個階段: 察覺到了自己的薄弱, 開始學習iOS的底層原理, 學習了C\C++的語法, 學習了Linux基礎, 學習了8086, ARM64彙編, 瞭解了一些自認為還是比較深的知識點. 覺得自己前途還是有希望的.

第五個階段: 也就是被刷掉之後的現在, 其實我現在也很迷茫, 也不知道現在學的東西到底有沒有用, 也不知道大廠到底要什麼樣的人才(只知道牛逼就行...), 但我也通過把知識點進行分類進行進階吧. 把最近的學習總結分享出來和大家一起討論, 像我這種水平的玩家到底該怎麼玩耍.

學習計劃

Swift 進階 | 看得見的演算法

我把最近學習的方面全部都整理在StudyNote這個裡面了, 可以看得出, 這個階段, 我明顯的就是像要學習加強演算法與資料結構方面的, 可能是因為被大廠刷的有陰影了吧.

Swift 進階 | 看得見的演算法

除了資料結構與演算法, 最近剛看完的CS193p的教程, 白鬍子老頭的這個視訊的質量很高, 發現了很多我平時忽略的東西, 而且覺得很多我以為的寫法從本質上都是有問題的.

Swift 進階 | 看得見的演算法

還有就是objc.io的這幾本書了, 質量挺高的, 函式式Swift的學習對我很有幫助. 感覺就像是開啟了新世界.

Swift 進階 | 看得見的演算法

還有就是排在後面的學習計劃: python, 資料分析, 機器學習, 深度學習.可以看得出, 其實這些都學完, 其實也不知道能夠幹什麼, 無所事事誒... 但至少這些都是我能夠找到的比較高質量的資料了, 如果有其他高質量的資料, 歡迎進行資料共享hhhh~

演算法學習

果然, 演算法是擋在(複製黏貼)程式設計師和(正常)程式設計師之前的一條很難跨越的鴻溝, 我這裡不是說, 你會寫快速排序,二分查詢, 深度廣度優先這類大家都能夠背出來的東西就叫做掌握演算法了, 我在Coursera上看了加州大學的演算法課, 才知道該如何設計演算法, 但由於是英語的, 而且沒有程式碼, 純數學的看不太懂, 所以轉向了北京大學的演算法課, 以下就是我最新學到的演算法和你分享.

Swift 進階 | 看得見的演算法

這個只是演算法基礎的第二節課, 你能想象這是演算法基礎麼? 光是這個題目, 我就看了老半天, 大意是點選一個燈, 上下左右的燈會自動點亮(熄滅), 當隨機給出點亮熄滅數的時候, 需要算出點選哪幾個燈可以將所有的燈點亮或熄滅.

這種演算法題, 真是聞所未聞見所未見吧, 而且這是演算法基礎的開頭的課... 我在想難道那些大廠的人做這種題跟玩的一樣麼, 想起了面試官的微笑, 那可真是有力量的微笑呢.

#include <stdio.h>

int puzzle[6][8], press[6][8];
/*
推測驗證過程:
根據第一行猜測
*/
bool guess() {
    int c, r;
    //根據press第1行和puzzle陣列,計算press其他行的值
    for(r=1; r<5; r++) {
        for(c=1; c<7; c++) {
            press[r+1][c]=(puzzle[r][c]+press[r][c]+press[r-1][c]+press[r][c-1]+press[r][c+1])%2;
        }
    }
    //判斷所計算的press陣列能否熄滅第5行的所有燈
    for(c=1; c<7; c++) {
        if ((press[5][c-1]+press[5][c]+press[5][c+1]+press[4][c])%2 != puzzle[5][c]) {
            return false;
        }
    }
    return true;
}

/*
列舉過程:
對press第1行的元素press[1][1]~press[1][6]的各種取值進行列舉
*/
void enumerate() {
    int c;
    bool success; //這個變數時當時定義了沒排上用場吧,NodYoung注
    for(c=1; c<7; c++) {
        press[1][c]=0;
    }
    while(guess()==false) {
        press[1][1]++;
        c=1;
        while(press[1][c]>1) {  //累加進位
            press[1][c]=0;
            c++;
            press[1][c]++;
        }
    }
    return ;
}

int main() {
    int cases, i, r, c;
    scanf("%d", &cases);
    for(r=0; r<6; r++) {
        press[r][0]=press[r][7]=0;
    }
    for(c=0; c<7; c++) {
        press[0][c]=0;
    }
    for(i=0; i<cases; i++) {
        for(r=1; r<6; r++) {
            for(c=1; c<7; c++) {
                scanf("%d", &puzzle[r][c]); //讀入輸入資料
            }
        }
        enumerate();
        printf("PUZZLE#%d\n", i+1);
        for (r=1; r<6; r++) {
            for (c=1; c<7; c++) {
                printf("%d ", press[r][c]);
            }
            printf("\n");
        }
    }
    return 0;
}
複製程式碼

這是北大老師視訊裡給出的演算法的答案, 講的很好, 但是說真的聽的是一知半解, 原因在於不知道為什麼, 這些網課都不是線上編譯的, 而是直接對著程式碼分析, 上面的程式碼摘抄自-> 可以搜尋熄燈問題.

var puzzle = [[Int]](repeating: [Int](repeating: 0, count: 8), count: 6)
var press = [[Int]](repeating: [Int](repeating: 0, count: 8), count: 6)
複製程式碼
func guess() -> Bool {
    for r in 1..<5 {
        for c in 1..<7 {
            press[r + 1][c] = (puzzle[r][c] + press[r][c] + press[r - 1][c] + press[r][c - 1] + press[r][c + 1]) % 2
        }
    }
    for c in 1..<7 {
        if (press[5][c - 1] + press[5][c] + press[5][c + 1] + press[4][c]) % 2 != puzzle[5][c] {
            return false
        }
    }
    return true
}
複製程式碼
func enumerate() {
    var c = 1
    for _ in 1..<7 {
        press[1][c] = 0
        while (guess() == false) {
            press[1][1] += 1
            c = 1
            while press[1][c] > 1 {
                press[1][c] = 0
                c += 1
                press[1][c] += 1
            }
        }
        c += 1
    }
}
複製程式碼
class Enumerate {
    
    static func main() {
        let cases = 1
        for r in 0..<6 {
            press[r][0] = 0
            press[r][7] = 0
        }
        for c in 1..<7 {
            press[0][c] = 0
        }
        for i in 0..<cases {
            for r in 1..<6 {
                for c in 1..<7 {
                    puzzle[r][c] = 2.arc4random
                }
            }
            enumerate()
            print("PUZZLE #\(i + 1)")
            for r in 1..<6 {
                for c in 1..<7 {
                    print(puzzle[r][c], terminator: "")
                }
                print()
            }
            print("== press ==")
            for r in 1..<6 {
                for c in 1..<7 {
                    print(press[r][c], terminator: "")
                }
                print()
            }
            print()
        }
    }
}
複製程式碼

以上是我學習的時候轉換成swift表達的, 不為什麼, 只是用來熟悉Swift語法罷了, 畢竟OC也不知道還能活個幾年了.

PUZZLE #1
011010
001110
010011
000101
100000
== press ==
001001
000101
001010
001101
011110
複製程式碼

但是光看程式碼, 很難看懂這個結果到底是正確還是不正確的... 因為跑出來是這樣的一個東西, 但是有些地方還是可以講一下, 比如是外面包了一圈0來避免冗餘邏輯判斷, 用2進位制進位的方法進行運算, 還是有學到一些皮毛的.

看得見的演算法

當然, 這種文字上的描述, 很難有深刻的印象的, 所以, 我就在想是否可以把這個熄燈遊戲給做出來, 再自己測試一下呢? 想到就幹吧!!

通過CS193p的學習, 對於畫UI方面有了全新的認識, 該如何新增約束, MVC到底怎麼寫, 以至於我以前理解的感覺完全就是錯的, 正好趁這個機會來練練手.

Swift 進階 | 看得見的演算法

我們通過StoryBoard先把View畫好, 不得不說UIStackView真是好用到爆!!

import Foundation

struct Matrix {
    var rows: Int
    var columns: Int
}

struct LightSwitch {
    
    private var puzzle: [[Int]]
    private var matrix: Matrix
    var lights = [Int]()
    
    mutating func lightUp(index: Array<Any>.Index?) {
        guard let index = index else { return }
        var m = Matrix(rows: 0, columns: 0)
        if index <= matrix.rows {
            m.columns = index + 1
        } else {
            m.columns += index % matrix.columns + 1
        }
        for i in 0...index {
            if i % matrix.columns == 0 {
                m.rows += 1
            }
        }
        puzzle[m.rows][m.columns] = puzzle[m.rows][m.columns] == 0 ? 1 : 0
        puzzle[m.rows + 1][m.columns] = puzzle[m.rows + 1][m.columns] == 0 ? 1 : 0
        puzzle[m.rows][m.columns + 1] = puzzle[m.rows][m.columns + 1] == 0 ? 1 : 0
        puzzle[m.rows - 1][m.columns] = puzzle[m.rows - 1][m.columns] == 0 ? 1 : 0
        puzzle[m.rows][m.columns - 1] = puzzle[m.rows][m.columns - 1] == 0 ? 1 : 0
        lights.removeAll()
        for r in 1..<matrix.rows + 1 {
            for c in 1..<matrix.columns + 1 {
                lights.append(puzzle[r][c])
            }
        }
    }
    
    init(matrix: Matrix) {
        self.matrix = matrix
        puzzle = [[Int]](repeating: [Int](repeating: 0, count: matrix.columns + 2), count: matrix.rows + 2)
        for r in 1..<matrix.rows + 1 {
            for c in 1..<matrix.columns + 1 {
                puzzle[r][c] = 2.arc4random
                lights.append(puzzle[r][c])
            }
        }
        print("========")
        for r in 0..<matrix.rows + 2 {
            for c in 0..<matrix.columns + 2 {
                print(puzzle[r][c], terminator: "")
            }
            print()
        }
        print("========")
    }
}
複製程式碼

Model程式碼, 原來MVCM需要這樣寫的, 以前都只是認為是簡單的資料結構來的真是膚淺, 這種直接業務邏輯寫在M裡面的的寫法真是好用到爆啊!

import UIKit

extension UIColor {
    var toImage: UIImage {
        let bounds = CGRect(origin: .zero, size: CGSize(width: 1.0, height: 1.0))
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { context in
            self.setFill()
            context.fill(CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0))
        }
    }
}

extension Int {
    var arc4random: Int {
        if self > 0 {
            return Int(arc4random_uniform(UInt32(self)))
        } else if self < 0 {
            return -Int(arc4random_uniform(UInt32(self)))
        } else {
            return 0
        }
    }
}

class ViewController: UIViewController {
    
    @IBOutlet var lights: [UIButton]! {
        didSet {
            for (index, light) in lights.enumerated() {
                light.setBackgroundImage(UIColor.yellow.toImage, for: .normal)
                light.setBackgroundImage(UIColor.darkGray.toImage, for: .selected)
                light.isSelected = switchs.lights[index] == 1 ? true : false
            }
        }
    }
    
    @IBAction func lightUp(_ sender: UIButton) {
        switchs.lightUp(index: lights.index(of: sender))
        for (index, light) in lights.enumerated() {
            light.isSelected = switchs.lights[index] == 1 ? true : false
        }
        if Set(switchs.lights).count == 1 {
            let alert = UIAlertController(title: "Congratulation", message: "You made all light up successfully", preferredStyle: .alert)
            alert.addAction(UIAlertAction(
                title: "again",
                style: .default,
                handler: { [weak self] _ in
                    self?.restart()
                }
            ))
            present(alert, animated: true)
        }
    }
    
    @IBAction func restart(_ sender: UIButton? = nil) {
        switchs = LightSwitch(matrix: Matrix(rows: 5, columns: 6))
        for (index, light) in (self.lights.enumerated()) {
            light.isSelected = self.switchs.lights[index] == 1 ? true : false
        }
    }
    
    var switchs: LightSwitch = LightSwitch(matrix: Matrix(rows: 5, columns: 6))
}
複製程式碼

Controller的程式碼, 這才能夠真正理解什麼叫做控制器用來協調ViewModel的互動, ModelView毫無關聯, 這才是iOS的正確寫法啊.

Swift 進階 | 看得見的演算法

執行了一下, 果然白鬍子大叔沒有騙我, 跑的6到飛起~

Swift 進階 | 看得見的演算法

可以看到的是, 終端列印的矩陣和介面上顯示的是一一對應的, 從北大老師學到的外面包一圈的方法也是特別好用的.

演算法測試

    init(matrix: Matrix) {
        self.matrix = matrix
        puzzle = [[Int]](repeating: [Int](repeating: 0, count: matrix.columns + 2), count: matrix.rows + 2)
        for r in 1..<matrix.rows + 1 {
            for c in 1..<matrix.columns + 1 {
//                puzzle[r][c] = 2.arc4random
                puzzle[1][1] = 1
                lights.append(puzzle[r][c])
            }
        }
        print("========")
        for r in 0..<matrix.rows + 2 {
            for c in 0..<matrix.columns + 2 {
                print(puzzle[r][c], terminator: "")
            }
            print()
        }
        print("========")
    }
複製程式碼
Swift 進階 | 看得見的演算法

我們將初始狀態從隨機數改成只暗一個燈, 位置是[1][1]

class Enumerate {
    
    static func main() {
        let cases = 1
        for r in 0..<6 {
            press[r][0] = 0
            press[r][7] = 0
        }
        for c in 1..<7 {
            press[0][c] = 0
        }
        for i in 0..<cases {
            for r in 1..<6 {
                for c in 1..<7 {
//                    puzzle[r][c] = 2.arc4random
                    puzzle[1][1] = 1
                }
            }
            enumerate()
            print("PUZZLE #\(i + 1)")
            for r in 1..<6 {
                for c in 1..<7 {
                    print(puzzle[r][c], terminator: "")
                }
                print()
            }
            print("== press ==")
            for r in 1..<6 {
                for c in 1..<7 {
                    print(press[r][c], terminator: "")
                }
                print()
            }
            print()
        }
    }
}

複製程式碼

我們把北大演算法也改成對應的[1][1]

PUZZLE #1
100000
000000
000000
000000
000000
== press ==
000111
101010
101100
001000
110000
複製程式碼

可以看懂只要按下全部位置對應為1的按鈕就可以將所有燈都開啟了, 我們來試一下.

Swift 進階 | 看得見的演算法

更新演算法

為了不用每次都跑到另一個程式去執行演算法, 我新增了Hint提示功能, 每盤遊戲都可以點選提示, 看到提示顯示的深色按鈕點選對應位置的燈, 即可點亮所有的燈.

Swift 進階 | 看得見的演算法

新增這個功能其實也很簡單, 只需要新建一個Popver控制器即可, 使用對應演算法, 對映到向量即可.

import UIKit

class HintViewController: UIViewController {

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        if let fittedSize = topLevelView?.sizeThatFits(UILayoutFittingCompressedSize) {
            preferredContentSize = CGSize(width: fittedSize.width + 30, height: fittedSize.height + 30)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        if presentationController is UIPopoverPresentationController {
            view.backgroundColor = .clear
        }        
    }
    @IBOutlet var hints: [UIButton]! {
        didSet {
            for (index, hint) in hints.enumerated() {
                hint.setBackgroundImage(UIColor.yellow.toImage, for: .normal)
                hint.setBackgroundImage(UIColor.darkGray.toImage, for: .selected)
                hint.isSelected = switchs?.hints[index] == 1 ? true : false
            }
        }
    }
    @IBOutlet weak var topLevelView: UIStackView!
    var switchs: LightSwitch?
}

複製程式碼

新增控制器, 就是顯示提示的控制器

    @IBOutlet weak var hintButton: UIButton!
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "Show Hint", let destination = segue.destination.contents as? HintViewController,
            let ppc = destination.popoverPresentationController {
            ppc.delegate = self
            ppc.sourceRect = hintButton.bounds
            destination.switchs = switchs
        }
    }
    
    func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
        return .none
    }

複製程式碼

原先控制器的Segue設定, 讓iphone預設不適配

    mutating func guess() -> Bool {
        for r in 1..<matrix.rows {
            for c in 1..<matrix.columns + 1 {
                press[r + 1][c] = (puzzle[r][c] + press[r][c] + press[r - 1][c] + press[r][c - 1] + press[r][c + 1]) % 2
            }
        }
        for c in 1..<matrix.columns + 1 {
            if (press[matrix.rows][c - 1] + press[matrix.rows][c] + press[matrix.rows][c + 1] + press[matrix.rows - 1][c]) % 2 != puzzle[matrix.rows][c] {
                return false
            }
        }
        return true
    }
    
    mutating func enumerate() {
        var c = 1
        for _ in 1..<matrix.columns + 1 {
            press[1][c] = 0
            while (guess() == false) {
                press[1][1] += 1
                c = 1
                while press[1][c] > 1 {
                    press[1][c] = 0
                    c += 1
                    press[1][c] += 1
                }
            }
            c += 1
        }
    }
複製程式碼

Model中加入核心演算法即可

Swift 進階 | 看得見的演算法

這個Demo可能是全網唯一的熄燈問題UI版本吧, 給自己一個贊~

經過好幾次測試, 可以看見, 演算法是正確的, 我也學到了這個演算法背後的思維, 更通過了寫了一個Demo來證明了演算法的正確性. 這個Demo的難點在於向量矩陣之間的互相轉換, 這裡為什麼不說一維陣列二維陣列呢? , 原因在於吳恩達的機器學習課程中也教會了我一些比較厲害的演算法, 比如梯度下降之類的.

好了, 現在寫文章沒有之前頻繁了, 原因在於之前那些文章都太水, 太膚淺, 寫了對自己也沒有太大的意義, 被人看到也只會覺得是垃圾而已... 所以在我第五階段的學習後, 希望能夠有機會進入一家大廠繼續深造吧!

最後 本文中所有的原始碼都可以在github上找到:

GitHub Repo:coderZsq.target.swift
Follow: coderZsq · GitHub
Resume: coderzsq.github.io/coderZsq.we…

相關文章