Swift 進階 | 看得見的算法

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

平常扯淡

前段日子寫了篇面經, 獲得了掘金徵文活動三等獎仍是很是開心, 可是被寒神說面試不過就泄題, 影響很差, 我想一想也是, 立刻就把大廠的名字給抹掉了, 但被轉載的就無能爲力了, 看到下面好多噴的, 真是背後一涼, 一首涼涼送給本身, 形成的傷害沒法挽回, 再此鄭重道歉, 不再寫面經了. 但願之後還能得到大廠面試的機會!python

大廠刷掉後, 我心情難以平復, 由於其實我是作了充足的準備來的, 可是仍是實力上有差距, 誒... 仍是想一想改如何提高本身的水平吧!webpack

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

提及轉行作iOS到如今, 從Objective-C基礎語法學起, 認爲OC是最好的語言, 學會作幾個簡單的UITableView頁面, 能播放音頻視頻, 以爲本身真是轉了個高大上的職業, 如今想一想, 真是膚淺的讓人忍不住發笑.git

我把作iOS這段學習經歷分爲五個階段:程序員

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

第二個階段: 就是瞎學些有的沒的, 如Swift, JavaScript, Java什麼的, 以爲本身全棧了, 什麼都會了, 很牛逼, 你看還本身可以寫一個前端簡歷, 還作過公司的前端項目, 服務器開發也會了, 自信心爆棚啊, 以爲本身真是無所不能.web

第三個階段: 寫了一個架構生成器, 以爲本身所向披靡了, 公司項目都在用我寫的架構, 用着我制定的規則, 尼瑪不就是一個簡單的字符串替換, 嘚瑟個啥... 並且對於架構的理解膚淺至極...面試

第四個階段: 察覺到了本身的薄弱, 開始學習iOS的底層原理, 學習了C\C++的語法, 學習了Linux基礎, 學習了8086, ARM64彙編, 瞭解了一些自認爲仍是比較深的知識點. 以爲本身前途仍是有但願的.算法

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

學習計劃

我把最近學習的方面所有都整理在StudyNote這個裏面了, 能夠看得出, 這個階段, 我明顯的就是像要學習增強算法與數據結構方面的, 多是由於被大廠刷的有陰影了吧.

除了數據結構與算法, 最近剛看完的CS193p的教程, 白鬍子老頭的這個視頻的質量很高, 發現了不少我平時忽略的東西, 並且以爲不少我覺得的寫法從本質上都是有問題的.

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

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

算法學習

果真, 算法是擋在(複製黏貼)程序員和(正常)程序員以前的一條很難跨越的鴻溝, 我這裏不是說, 你會寫快速排序,二分查找, 深度廣度優先這類你們都可以背出來的東西就叫作掌握算法了, 我在Coursera上看了加州大學的算法課, 才知道該如何設計算法, 但因爲是英語的, 並且沒有代碼, 純數學的看不太懂, 因此轉向了北京大學的算法課, 如下就是我最新學到的算法和你分享.

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

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

#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到底怎麼寫, 以致於我之前理解的感受徹底就是錯的, 正好趁這個機會來練練手.

咱們經過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的正確寫法啊.

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

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

算法測試

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("========")
    }
複製代碼

咱們將初始狀態從隨機數改爲只暗一個燈, 位置是[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的按鈕就能夠將全部燈都打開了, 咱們來試一下.

更新算法

爲了避免用每次都跑到另外一個程序去運行算法, 我新增了Hint提示功能, 每盤遊戲均可以點擊提示, 看到提示顯示的深色按鈕點擊對應位置的燈, 便可點亮全部的燈.

添加這個功能其實也很簡單, 只須要新建一個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中加入核心算法便可

這個Demo多是全網惟一的熄燈問題UI版本吧, 給本身一個贊~

通過好幾回測試, 能夠看見, 算法是正確的, 我也學到了這個算法背後的思惟, 更經過了寫了一個Demo來證實了算法的正確性. 這個Demo的難點在於向量矩陣之間的互相轉換, 這裏爲何不說一維數組二維數組呢? , 緣由在於吳恩達的機器學習課程中也教會了我一些比較厲害的算法, 好比梯度降低之類的.

好了, 如今寫文章沒有以前頻繁了, 緣由在於以前那些文章都太水, 太膚淺, 寫了對本身也沒有太大的意義, 被人看到也只會以爲是垃圾而已... 因此在我第五階段的學習後, 但願可以有機會進入一家大廠繼續深造吧!

最後 本文中全部的源碼均可以在github上找到:

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

相關文章
相關標籤/搜索