Swift 进阶 | 看得见的算法

5,696 阅读13分钟

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汇编, 了解了一些自认为还是比较深的知识点. 觉得自己前途还是有希望的.

第五个阶段: 也就是被刷掉之后的现在, 其实我现在也很迷茫, 也不知道现在学的东西到底有没有用, 也不知道大厂到底要什么样的人才(只知道牛逼就行...), 但我也通过把知识点进行分类进行进阶吧. 把最近的学习总结分享出来和大家一起讨论, 像我这种水平的玩家到底该怎么玩耍.

学习计划

我把最近学习的方面全部都整理在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…