超长阶乘的计算

打印 n! 的结果(1 <= n <= 100)。注意:当 n > 20 时 64 位的 Int 将无法直接存储结果。 思路 将大数字用 数组 形式表示。比如 987 使用 [9,8,7] 代替。 每一位乘以 n,再进行进位操作,得到新数组。 let nums = [9, 8, 7] let tmpNums = nums.map { $0 * 2 } // [18, 16, 14] // 遍历 tmpNums 每一个数字,进行进制操作 [18, 16, 14] -> [18, 17, 4] -> [19, 7, 4] -> [1, 9, 7, 4] print(tmpNums.map(String.init).joined()) // 1974 解答项目 func extraLongFactorials(n: Int) -> Void { guard n > 0 else { return } // 结果数组 var result: [Int] = [1] for index in 1...n { // 数组翻转 从低位开始每一位乘以本次的数字 let tmpNums = result.reversed().map { $0 * index } // 进位数 var carryNum = 0 // 重置结果 result = [] tmpNums.forEach { // 每一位加上上一位的进的数 let tmpNum = $0 + carryNum // 向下一位进制的数 carryNum = tmpNum / 10 // 本位实际剩下的数 插入结果 result.append(tmpNum % 10) } // 处理剩余进位数 进位数是可能大于 100 while carryNum > 0 { // 逐渐插入进制 result.append(carryNum % 10) carryNum /= 10 } // 翻转回数组 result = result.reversed() } // 连接字符串 print(result.map(String.init).joined()) } References Extra Long Factorials | HackerRank Swift 3 calculate factorial number. Result becomes too high? – EOF –

March 1, 2019 · 1 min · 187 words · Me

回顾 2018

重新翻阅的自己工作邮件的发件箱,回顾一年工作。新年伊始自己还是在开发 P 项目的 iOS App,开始写 Q&A 功能。一些不算太难的 tableView 布局的需求,对我来说,都是头大的问题。 这段时期招聘时的面试,竟成为我学习 App 开发的一扇小窗。 年前收到了奖金还是挺开心的。leader 新年寄语: 要发声,要当主力 当有好的想法时,要学会说服别人 要有耐心,Yifan 需要时间的沉淀 前两点意思差不多,这段时间思想上困扰我的是:自己对自己的定位是一个初级工程师,认为会的东西、经验不多,见识少,我尊重比我年长的工程师的想法与观点,也相信他们是经过长远思考过的。这个思维设定,我觉得没有什么问题。但是也许有人忽略了 责任,对方案负责,对项目负责。开发方案一再重建性修改,接口结构没有规范。 2017 的总结说胜利属于伏地魔,本想苟着发育,这时发现:苟是苟不住的,这个世界 需要英雄 carry。 后来我感觉应该将公司看做一个舞台,舞台上有灯光、音效就要利用,展示自己、锻炼自己,即使是出糗,那就整理整理再来一次,who care? 成长是最重要的。 Course 模块是前工程师用 Objective-C 写的,离职后一直没有再维护,过年期间自己重构了所有 Objective-C 的代码,项目完全转为了一个纯 Swift 项目。使用 Realm 作为数据本地化方案,选择的原因也很朴素,GitHub 哪个星多我就优先选用什么。 后来参考 Jack Feng - 6ag 的几个 Swift 开源项目,新创建了 P 项目的工具 App,也对主项目结构做了重新的整理,在这里再次特别感谢。 西安运营部的成立,加多了 C 项目的后台需求,难以都顾及项目两头。使用 laravel-admin 搭建新后台,也开始使用 Docker 部署项目,感觉从此离不开 Docker 了,像极了遇到 Git 时的感觉。 C 项目主站的前端是在服务端渲染,非常传统的模式,多个工程师转手也是十分混乱。参考了 白俊遥 工程的博客、laravel 项目,修改了项目结构,添加了 gulp 工具制定了工作流,虽然没有实现前后端的完全分离,但终究是向现代化前端走出了一步。 转眼就到了年中调薪,公司不含糊,薪水涨到了我满意的值。这对我很关键,调整的不单单是我的薪水,也调整了我的心态。因为当时我认为没有强的工程师、甚至归我负责,却拿着比我还高的薪水。现在总算是有了一个平衡。 不断向 C 项目投入更多的资源,项目 指标 的要求越来越多。比如:优化了项目、优化了查询,到底优化了多少,怎么量化?这些在之前一直不被重视,改好了就都算叫优化了。项目中出现的问题错误,都要查找真实具体的原因,而不是说一个可能的什么原因,就当解答的了。我一开始也很难适应这些,但心里是认同的。 ...

December 31, 2018 · 1 min · 134 words · Me

二分查找 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

MySQL 日期/时间类型选型与时区处理

适用版本:MySQL 8.0+ / Laravel 10+。MySQL 5.7 已 EOL,文中不再覆盖。 一句话结论 「物理时刻」(事件发生于何时)→ TIMESTAMP,自动跟随会话时区转换 「字面值」(合同上写的、用户输入的日期)→ DATETIME,原样存取不转换 仅日期 → DATE;仅时长 → TIME;JS/跨语言毫秒戳 → BIGINT 永远不要用 VARCHAR 存日期——无强制约束、无法范围查询、排序按字符串 选型对照表 类型 范围 字节 时区行为 小数秒 典型场景 DATETIME 1000-01-01 00:00:00 ~ 9999-12-31 23:59:59 5 + fsp 字符串原样存取,不转换 DATETIME(0~6) 合同生效时点、用户填写的本地时间、历史事件 TIMESTAMP 1970-01-01 00:00:01 UTC ~ 2038-01-19 03:14:07 UTC 4 + fsp 写入:会话时区 → UTC;读取:UTC → 会话时区 TIMESTAMP(0~6) created_at / updated_at、日志、跨时区事件戳 DATE 1000-01-01 ~ 9999-12-31 3 — — 生日、纪念日、报表日期 TIME -838:59:59 ~ 838:59:59 3 + fsp — TIME(0~6) 营业时长、计时器 YEAR 1901 ~ 2155 1 — — 罕用 BIGINT INT64 8 应用层控制 — JS 毫秒戳、Unix 微秒戳、跨语言互操作 小数秒(fsp,fractional seconds precision) 0~6 位,每 2 位多占 1 字节。Laravel 11+ 默认就用 DATETIME(6) / TIMESTAMP(6)。 ...

May 25, 2018 · 3 min · 468 words · Me, LLM

【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 关键字。生成器从不返回值,值产出值。 function myGenerator() { yield 'value1'; yield 'value2'; yield 'value3'; } foreach (myGenerator() as $yieldedValue) { echo $yieldedValue, PHP_EOL; } value1 value2 value3 使用生成器处理 CSV: ...

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

用 Certbot 自动化 Let's Encrypt TLS 证书

Certbot 和 Let’s Encrypt 的关系 Let’s Encrypt 一个免费、自动化、开放的公共证书颁发机构(CA)。 通过 ACME(Automatic Certificate Management Environment,RFC 8555)协议向域名所有者颁发 DV(Domain Validation)TLS / SSL 证书。 Certbot 由 Electronic Frontier Foundation(EFF)维护的开源 ACME 客户端。 主要目标是简化与 Let’s Encrypt 之间的交互: 自动化域名验证(HTTP-01、DNS-01、TLS-ALPN-01) 安装并续期证书 更新 Web 服务器配置(Apache、Nginx、Lighttpd 等) Certbot 也能与任何兼容 ACME 的 CA 通信,不限于 Let’s Encrypt。 ACME 挑战怎么选 挑战 适用场景 限制 HTTP-01 80 端口可达、单域名 / 多域名(最常用) 不能签 wildcard DNS-01 任意域名 —— wildcard 唯一选项 需要 DNS provider API 或手动加 TXT TLS-ALPN-01 443 可达但 80 被封,常用于反代后端 较少用,需要 ACME-aware 反代支持 典型工作流程 解析参数并检测服务器类型。 选择并执行挑战(HTTP-01 例:在 /.well-known/acme-challenge/ 下写入 token)。 Let’s Encrypt 回访验证域名归属。 验证通过后签发证书;Certbot 下载并安装到本地。 创建 systemd timer(snap 安装会自动配置)周期性 certbot renew 续期。 开始实验 实验环境:Amazon Linux 2023(AL2023)。AL2023 没有官方 certbot 包,社区维护的 snap 仓库是当前最省事的安装路径。 ...

April 26, 2018 · 4 min · 815 words · Me, LLM

【Git 权威指南】读书笔记 - 协同

【Git 权威指南】“和声”+“协同模型"两章的精炼合并版:远程协议、推送与冲突解决、里程碑共享、分支协同、工作流模型、共用代码(submodule / subtree)。 本文沿用书中 master 作为默认分支名;Git 2.28+ 默认改为 main,行为完全一致。 远程协议与版本库 协议选择 协议 用途 现状 SSH 推 + 拉,需公钥认证 协作首选 HTTPS 推 + 拉,凭证由 helper 缓存 网络受限环境首选;个人项目常用 本地 file:// 或裸路径 备份 / 镜像 git:// 只读匿名协议 已弃用,GitHub 2022 年关停 FTP / RSYNC — 已弃用,忘了它们 ssh://[user@]example.com[:port]/path/to/repo.git [user@]example.com:path/to/repo.git # SSH 简写 https://example.com/path/to/repo.git 远程版本库管理 git remote -v # 列出 fetch/push URL git remote add <name> <url> # 注册 git remote set-url <name> <url> # 改 URL git remote set-url --push <name> <url> # 单独改 push URL(读写分离) git remote rename <old> <new> git remote rm <name> git remote update # 拉取所有 remote 的更新 git config remote.<name>.skipDefaultUpdate true # 排除某个 remote fetch / pull 的关系 git pull = git fetch + git merge # 默认 git pull --rebase = git fetch + git rebase 习惯用 rebase 整理历史的人,全局开启: ...

January 17, 2018 · 5 min · 952 words · Me, LLM