二分查找 Binary Search

快速从一个数组中查找一个元素。 Linear Search 线性查找 func linearSearch<T: Equatable>(_ a: [T], _ key: T) -> Int? { for i in 0 ..< a.count { if a[i] == key { return i } } return nil } 线性查找在最坏情况:遍历了整个数组,但没有找到合适的元素。平均要遍历一半的元素性能为 O(n),而二分查找的效率为 O(log n),也就是说一个有 1,000,000 元素的数组只需要 20 步就可以找到想要的元素 log_2(1,000,000) = 19.9。 但是二分查找要求数组必须是排好序。 二分查找步骤: 将数组分为两半。 判断想要找的元素是在左边数组还是右边,这也是数组需要排好顺序的原因。 如果要的元素在左边,就将左边的数组分成更小的两部分,并判断要的元素在哪部分。 重复步骤直到找到想要的元素。如果数组不能进一步查分,就说明要找的元素不在数组中。 divide-and-conquer The code func binarySearch<T: Comparable>(_ a: [T], key: T, range: Range<Int>) -> Int? { if range.lowerBound >= range.upperBound { // If we get here, then the search key is not present in the array. return nil } else { // Calculate where to split the array. let midIndex = range.lowerBound + (range.upperBound - range.lowerBound) / 2 // Is the search key in the left half? if a[midIndex] > key { return binarySearch(a, key: key, range: range.lowerBound ..< midIndex) // Is the search key in the right half? // 这里 + 1 的原因是排除 midIndex 中间值 } else if a[midIndex] < key { return binarySearch(a, key: key, range: midIndex + 1 ..< range.upperBound) // If we get here, then we've found the search key! } else { return midIndex } } } // 19 numbers let numbers = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67] // 0 ..< numbers.count 覆盖所有范围 binarySearch(numbers, key: 43, range: 0 ..< numbers.count) // gives 13 二分查找是将数组分为两个,但是我们不需要正真的创建两个新数组。取而代之,我们使用 Swift Range 对象跟踪这些拆分。左闭右开。upperBound 总是比最后一个元素的索引多一。 ...

December 10, 2018 · 2 min · 329 words · Me

插入排序 Insertion Sort

将一个数组从高到低或者从低到高排序。 插入排序算法的工作原理: 将若干数字放在一个数组里,数组是乱序的。 从数组中挑选一个数字,它是哪个并不重要,但是为了方便我们挑选数组头部的这个。 将这个数字插入到一个新的数组里。 从乱序数组里挑选下一个数字也将它放到新数组里。这个数字要么在第一个数字前或者后,所以这个两个数字是被排序的。 再次重从乱序数组里挑选下一个数字也将它放到新数组里,并将数字放在正确的位置。 一直如此进行直到乱序数组中没有数字。这时也将等到一个排序好的新数组。 自己的一个实现: let array = [2, 1, 3, 8, 3, 5, 4] var newArray = [Int]() for (k, v) in array.enumerated() { for (nK, nV) in newArray.enumerated() { // 本次的数 小于 存在的数的第一个(nv) if v < nV { newArray.insert(v, at: nK) break } } // 没有插入成功 放在末尾 if newArray.count < k + 1 { newArray.append(v) } } In-place sort 上面的排序需要两个数组,一个原始的,一个排好顺序的。但是我们也可以 就地排序 无需创建一个额外的数组。我们只需要跟踪记录原始数组中哪里部分排好顺序了,哪一部分还没有排序。 ...

November 24, 2018 · 3 min · 574 words · Me

队列 Queue Data Structure

实现一个 队列,包括 enqueue、dequeue、peek。 Queue 队列 核心也是 array,A queue gives you a FIFO or first-in, first-out order. 队列是:先进先出的。 public struct Queue<T> { fileprivate var array = [T]() } enqueue 进队,在数组尾部追加元素。 public mutating func enqueue(_ element: T) { array.append(element) } dequeue 出队,将首位的元素移除。因为首位元素移除后,其他元素依次向前移动,所以是 O(n)。 public var isEmpty: Bool { // 使用数组自身的方法,而不是 array.count > 0 return array.isEmpty } public mutating func dequeue() -> T? { // 使用定义的变量 if isEmpty { return nil } else { return array.removeLast() } } peek 查看队首元素。 /// peek() 改为更有语义话的只读变量 public var front: T? { return array.first } 优化出队 在出队后不移动元素而是移动 起始索引,就像动的收银台而不是排队的人。 /// 优化 队列 的出队 public struct OptimizedQueue<T> { /// 这里改为了可选型,为了可以清理无效的元素 fileprivate var array = [T?]() /// 起始索引 fileprivate var head = 0 public var count: Int { // 减去 起始索引 前面的数量 return array.count - head } public var isEmpty: Bool { // 根据实际数量判断 return count == 0 } // 保持不变 public mutating func enqueue(_ element: T) { array.append(element) } public mutating func dequeue() -> T? { guard head < array.count, let element = array[head] else { return nil } // 置空当前位置元素 array[head] = nil // 前移起始索引 head += 1 // 空索引的占用比例 let percentage = Double(head)/Double(array.count) // 50 0.25 都是魔法数字,主要是为了控制数组修剪的频率,可以自行调整 if array.count > 50 && percentage > 0.25 { // 将起始空元素删除 array.removeFirst(head) // 重置 起始索引 head = 0 } return element } public var front: T? { if isEmpty { return nil } else { // 根据 起始索引进行 返回 return array[head] } } } 文章代码:GitHub - imzyf/data-structure-and-algorithm/002-Queue/。 ...

November 22, 2018 · 2 min · 239 words · Me

栈 Stack Data Structure

加入 Swift Algorithm Club /‘ælgə’rɪðəm/,回炉重新学习数据结构与算法。 自己创建的项目:GitHub - imzyf/data-structure-and-algorithm。 实现一个 栈 /stæk/,包含 push peek pop 与 Generics 泛型。 stack 栈 非常像一个数组,它包括少量的方法。 push 添加一个新元素到栈顶 pop 从栈顶移除一个元素 peek 查看栈顶的一个元素但是不 pop A stack gives you a LIFO or last-in first-out order. 栈是后进先出,队列是先进先出。 public struct Stack<Element> { fileprivate var array: [Element] = [] } push push 是在数组的尾部添加元素是以 O(1),如果是在数组最前添加是 O(n) 这是昂贵的。 public mutating func push(_ element: Element) { array.append(element) } 因为使用的 struct,修改属性值的方法要加 mutating。 pop 想从一个空栈中弹出最后一个元素将返回 nil。 ...

November 22, 2018 · 1 min · 126 words · Me

解决 Too many symbol files

在上传 App 到 App Store 后收到邮件,有 issues Too many symbol files。在之前看到 Your delivery was successful,此 issues 不影响发布,所以一直搁置了。 今天决定彻底处理下。 背景 先说 *.symbols 这文件是干嘛的,我现在(2018-10)对此的理解: symbols 为符号表文件 符号表是内存地址与函数名、文件名、行号的映射表 <起始地址> <结束地址> <函数> [<文件名:行号>] 为什么要配置符号表? 为了能快速并准确地定位用户 App 发生 Crash 的代码位置,使用符号表对 App 发生 Crash 的程序 堆栈 进行 解析 和 还原。 项目情况 再说下项目情况,因为数字都是用了的是 Int,为防止 32 位设备发生越界情况(理由好像有点扯),所以项目端设置了设备限制 arm64,也就是 5s 之前的设备不可以安装。 因为使用了三方库,但是三方库是支持 32 位设备的,所以生成了冗余的 symbols 文件。 查询 symbols 文件的生成情况:Xcode Window -> Organizer 选择有问题的 archive,右击选择 Show in finder,命令行进入 *.app 中的 dSYMs 文件夹,执行 dwarfdump --uuid * 可以查询到是否生成了多余的文件。 ...

October 30, 2018 · 1 min · 142 words · Me

在 MySQL 中选择合适的日期类型

如何在 MySQL 中选择合适的日期类型困扰了很久,varchar、int、timestamp、datetime 都有尝试过,近来有所感悟,做此总结。 注:此总结考虑了 PHP 和 Laravel 框架的特点。 使用 varchar varchar 存储日期时间的格式完全可以自己控制,月/日/年 还是 年-月-日 需求怎么说就怎么存,读取后展示是也不用在格式化。同时伏笔也就此埋下:日期时间格式没强制约束,总有一天字段里出现了与众不同的格式;要是日期时间会 变化 或作为 查询条件 或要进行 排序 时就又是一坑,还是要格式化标准格式再处理。可以说 varchar 应该是最差的选择了。 使用 int 与 timestamp PHP time() 可以直接获取当前时间戳秒数,数据库字段要也是 int 一存就完事了,不会有格式问题,谁用什么样转什么样。但是在数据库工具中查看此字段时显示不够直观,范围时会不方便,这些在使用 timestamp 是会得到解决。 timestamp 是我一直迷惑的一个类型。我写了几个例子做测试: 将 Laravel 项目设置为 CST 中国标准时间,MySQL 时区设置为 UTC,使用 now() 获取当前日期时间,比如:2018-5-25 11:00:00 存入 timestamp 类型的字段中,使用数据库工具查看字段结果为仍然为 2018-5-25 11:00:00。 继续上面的操作,项目中使用查询语句查询刚才的记录,结果显示为 2018-5-25 11:00:00,将项目时区从 CST 改为 UTC 后再次查询的结果仍然为 2018-5-25 11:00:00 没有变化。 继续上面的操作,将数据库的时区改为 +8:00,数据库工具、项目查询后的结果为 2018-5-25 19:00:00 发生了变化,修改项目为 CST 查询结果是 2018-5-25 19:00:00 和刚才一样也变化了。 这个测试说明了: ...

May 25, 2018 · 2 min · 254 words · Me

NGINX 禁止 IP 访问

禁止 IP 访问,其他域名跳转到 www.xxx.com: server { listen 80; server_name 55.66.77.88; deny all; } server { listen 80; server_name www.xxx.com xxx.com; return 301 https://www.xxx.com$request_uri; } server { listen 443 ssl http2; #... if ($host = 55.66.77.88) { return 403; } if ($host != 'www.xxx.com'){ rewrite ^/(.*)$ https://www.xxx.com/$1 permanent; } #... } – EOF –

May 20, 2018 · 1 min · 53 words · Me

L01 Web 开发实战入门

Laravel 教程 - Web 开发实战入门 基础信息 Laravel 与 PHP Ruby on Rails 有以下原则: 强调与注重敏捷开发; 约定高于配置(Convention over configuration); DRY(Don’t repeat yourself)不要重复自己,提倡代码重用; 重视「编码愉悦性」。 如何正确阅读本书 随后你会有很多机会来学习它们。现在最重要的是保持『训练』的连贯性。 编程是技能,不是知识,技能只有在不断刻意练习下才会有进步。 开发环境布置 第一个应用 composer create-project laravel/laravel Laravel --prefer-dist "5.5.*" Git 与 GitHub 设置 push 的默认模式为 simple git config --global push.default simple 部署上线 注册 Heroku 后: heroku login # 添加 SSH Key 到 Heroku 上 heroku keys:add # 创建配置文件来告诉 Heroku 应当使用什么命令来启动 Web 服务器 echo web: vendor/bin/heroku-php-apache2 public/ > Procfile git add -A git commit -m "Procfile for Heroku" # 创建一个新应用 heroku create # 对应用名称进行更改,保证未被其它人占用 heroku rename imzyf-laravel-essential # 声明应用是用 PHP 写的 heroku buildpacks:set heroku/php # 设置 APP key php artisan key:generate heroku config:set APP_KEY=base64:wuWj8Kicza6I9YxgWczviNVcueVN2RroqiUILreyNmA= # 部署上线 git push heroku master # 快速打开线上应用 heroku open # 输出生产环境上的日志 heroku logs 构建页面 静态页面 生成静态页面控制器: ...

May 9, 2018 · 2 min · 371 words · Me

【Modern PHP】笔记

又回到 PHP Web 开发,使用 Laravel 框架,重读《Modern PHP》。 PHP 正在重生。 特性 命名空间 声明命名空间: <?php namespace Oreilly\ModernPHP; 导入和别名: <?php use Symfony\Component\HttpFoundation\Response as Res; $r = new Res('Oops', 400); $r->send(); PHP 5.6 开始可以导入函数和常量: <?php use func Namespace\functionName; use constant Namespace\CONST_NAME; functionName(); echo CONST_NAME; 使用接口 接口是两个 PHP 对象之间的契约,其目的不是让一个对象依赖另一个对象的身份,而是依赖另一个对象的能力。 使用接口编写更加灵活,能委托别人实现细节。 性状 trait 性状是类的部分实现,可以混入一个或者多个现有的 PHP 类中。性状有两个作用:表明类可以做什么(像是接口);提供模块化实现(像是类)。 如果想让两个无关的 PHP 类具有类似的行为,应该怎么呢?性状就是为了解决这种问题而诞生的。性状能把模块化的实现方式注入多个无关的类中。而且性状还能促进代码的重用。 这与创建一个接口,两个无关的类实现这个接口的优势在于:不用写相同的实现代码,符合 DRY 原则。 PHP 解释器在编译时会把性状复制粘贴到类的定义体中,但是不会处理这个操作引入的不兼容问题。如果性状假定类中有特定的属性和方法(在性状中没有定义),要确保相应的类中有对应的属性和方法。 生成器 Generator 是 PHP 5.5.0 引入的功能。生成器是简单的迭代器,仅此而已。 PHP 生成器不要求类实现 Iterator 接口,从而减轻了类的负担。生成器会根据需求计算并产生要迭代的值。这对应该的性能有重大影响。假如标准的 PHP 迭代器经常在内存中执行迭代操作,这要预先计算出数据集,性能低;此时我们可以使用生成器,即时计算并产出后续值,不占用宝贵的内存资源。 PHP 生成器不能满足所有迭代操作的需求,因为如果不查询,生成器永远不知道下一个要迭代的值是什么,在生成器中无法后退和快进。生成器还是一次性,无法多次迭代同一个生成器。不过,如果需要,可以重建或克隆生成器。 PHP 生成器是 PHP 函数,只不过要在函数中一次或者多次使用 yield 关键字。生成器从不返回值,值产出值。 ...

May 8, 2018 · 3 min · 572 words · Me

PhpStorm 使用经验

Getting Started Two shortcuts to get started Shift+Shift(⇧+⇧) helps you find anything within your project. Alt+Enter(Option+Enter) provides instant access to contextual actions and quick fixes relevant to the selected code. Getting started with PHP in PhpStorm 还有个 One Dark theme 但是 Material Theme UI 已经包含这个主题。 配置: Preferences > Appearance & Behavior > Appearance 下,右侧配置:Theme: Darcula,勾选 User custom font: .AppleSystemUIFont Size: 18。 Preferences > Editor > Font 下,右侧配置:Font: Menlo Size: 18 Line spacing: 1.2。 ...

May 5, 2018 · 3 min · 433 words · Me