Becomin' Charles

算法 | LNMP | Flutter | Mac

Becomin' Charles

在 Mac 上安装了 Adobe Photoshop 2021 后,我发现一个代表 Adobe Creative Cloud 服务的图标,出现在 Mac 系统右上角的菜单栏(Menu Bar)上。如果点击这个图标,就会弹出一个窗口,展示 Adobe 的登录表单。这个图标很容易误点,在图标上点击右键,菜单里也没有“退出”选项。打开“系统偏好设置”,“用户与群组”,在“登录项”选项卡上,也没有看到 Adobe 注册什么登录项。到底怎么才能退出这个恼人的程序呢?唯有了解了 launchd 这个系统服务管理机制,才能做到。

Image result for adobe creative cloud icon

Adobe Creative Cloud 的图标

阅读全文 »

从我起心动念开始想要学习 iOS 软件开发到今天为止,已经有差不多四周了。

想要学习手机软件开发的一个原因是我们公司有一个 oa 系统,但是呢,我们现在已经不想使用企业微信,作为扫码登录的一个工具也不想使用企业微信,作为这个系统的消息通道,但是企业微信其实在这方面是非常好用的以至于,那我们并没有合适的替代品,于是我就想到为什么不干脆自己开发一个最简单的 oa 系统的客户端软件呢?

但是其实现在我们公司已经没有养着 iOS 4 团队了,只有一个安卓的开发团队还不是我所在的部门,于是我这个想法并没有相应的开发资源来进行支撑,我就想干脆我自己做算了,我用我的业余时间来开发一个软件给公司的同事们用难道不好吗?

因为我已经连续多年使用苹果手机了,所以呢,我只想学习 iOS 开发对安卓开发没有一点兴趣,另外最近跨平台的开发技术非常的热门,但是拿我也提不起什么性质来,我只是想学习一下苹果手机的开发。

正好呢,最近苹果手机的开发体系发布了一个叫做 SwiftUI 的开发技术框架,于是我就想到嗯嗯,了解一下,没想到一看 SwiftUI 的嗯嗯介绍视频发现嗯这个开发框架非常的友好非常的简洁,而且也十分具有表现力。

于是,我下定决心想要真的学会使用 SwiftUI 去开发一个苹果手机的软件嗯,我的不少朋友都告诉我,SwiftUI 并不是一个特别好的选择应该是在开发到比较高深的功能的时候会遇到很大的阻力,主要是在很多细节的设定上应该是不够灵活的一个框架,但是我想我使用的功能,应该是非常简单的几个功能,我的目标是非常明确的,我只想做这样几个功能:第一,可以进行登录;第二,可以进行扫码登录;第三,可以进行消息,推送;第四,甚至可以实现二次验证登录的这个验证码。至于其他更加复杂的功能,比如说聊天啊,这些都不想实现那么如何在一个 App 里实现 O A 的其他全部功能的当然最简单使用一个 web view 由来嵌入网页的形式。

不过对于我这样一个从来没有学习过苹果手机软件开发的人来说做这样的工作,实在还是太困难了嗯,虽然我懂得开发的基本原理,但是我发现我如果想要完成,我给自己设定的任务,还要学习非常非常多的东西,然后嗯我作为一个资深的程序员,仍然是不能把握住很多重要的东西,因为我以前是一个 web 开发。现在我需要学习的东西,相当于是一个额客户端,软件的开发在性质上差距是非常大的。

今天晚上,我熬到了凌晨 3:00 仍然还无法产生睡意,根本原因就在于我用了一晚上的时间在搜索关于 SwiftUI 相关的开发技术资料,我搜索这些技术资料的根本原因是在于我想要解答,我对 SwiftUI 开发过程中嗯几个疑难的问题。

比如第一个问题就是说一个苹果手机软件,一旦启动了,以后第一个界面会进入到哪里,这个是怎么确定的呢,其实网上嗯,关于这样基本的内容是非常难以搜索到的,因为你如果说关键词是属于 UI 的话,都是在介绍界面怎么布局动画,怎么怎么呈现各种空间如何去设置它?

但是我现在想学的是关于如何构建一个软件基本的流程,而这可能是很古老的 iOS 软件开发的教程里面会提及的内容,但是他涉及到的知识又不是使用 SwiftUI 很有可能是使用最早的 objective c 或者说是 uikit 之类的技术框架。

另外,我想实现一个功能就是嗯用户的登录用户登录以后呢,应该跳转到一个软件的正式界面,但是在用户登录之前是不能直接进入到用户的正式见面了,于是我就想到这其实就相当于是两个完全不同的怎么说呢,就是场景吧,登录是一个场景,正式使用一个软件又是一个场景。如果我们没有登录的话,会应该会被登录界面给挡在外面无法进入到软件的正式操作空间,就这样一个简单的功能,到底应该如何去实现了所有介绍 SwiftUI 的教程视频教程也好文字教程也好都没有提及这个问题。

还有一个我想知道问题,比如说我应该用怎样的布局模式去安排我自己想要的功能,这个也是非常困难的,其实他属于苹果的这种基本交互规范的一个范畴的问题嗯,我按照现在自己的理解参考了企业微信的界面设计,我觉得就应该使用 tabbed view 来实现这个这个界面的布局。但是这个在各种各样的教程里也是没有说的教程,一般只会说,什么样的空间怎样使用,但没有说在什么样的场景下,应该使用什么样的控件。

再有一个问题,就是说我如何,使我的苹果手机软件与互联网上面的各种操作进行有机的结合,比如说我检查一个客户有没有登录到这个手机软件上,那么显然是要通过联网去向服务器确认的,然后我如果手机软件开着,从服务器不断地收到嗯推送过来的消息这个是怎样去实现了市场连接到服务器嗯,然后是服务器推送过来,还是说嗯,是不断地去轮询服务器那样的话大量的客户端软件会不会使服务器造成拥塞,甚至拒绝服务。

随着我对 SwiftUI 表面现象的理解的深入,然后发现更多困扰,我的问题在于如何非常普通的去构建一个苹果手机软件,而不是仅仅使用一个界面开发的框架,我缺乏的是手机客户端软件构建的基本原理的知识。

然而,现在对我来说非常困难,是我根本就不知道从什么样的资料,或者从哪里可以更容易补充到这些客户的软件构建软原理层面基本的知识,互联网上提供的大量的知识都是一种浮于表面性的知识,或者是尝鲜性质的知识,并没有深入到带另一个学习者,逐步深入到一个真正的客观世界的软件的开发过程中。

于是,我就萌生了记录,整个这个过程的一个想法,因为对我来说我是从一个饿一个资深的 web 开发程序员嗯转岗到这个客户端开发程序员,然后从零开始想要构建一个在客观世界中真实有用的,有业务逻辑的苹果手机软件的这样一个场景,那么,如果我能够精细的记录下,我整个过程中的想法,以及我碰的壁以及我找到的学习的路径和方向,那么别人从阅读我的经历的过程中,应该能够得到很多的启发,甚至嗯,从此走上一个正确的道路。

iOS 开发学习,对我来说,最难的并不是语言语法的学习,也不是类库的学习。对我来说,最难的地方,在于资料是零星琐碎而且不成系统的。整个 iOS 开发发展速度太快了。我记得我三周前,第一次打开 Xcode 生成的 Hello World 程序和现在打开再生成的就已经不同了,一些模板文件消失,可见短短三周时间,很多东西已经更新并更换了。

这就导致了一个问题,你能找到的所有的电子书都是过时的。网上找到的关于 SwiftUI 的教程,你照着做一个例子的话,跑不通是大概率的。哪怕是三个月前刚刚出品的视频也是不能避免此点。甚至视频教程里用到的一些类,在你实践的时候,并不存在。

这就导致了,你几乎没有什么东西可以用来直接跟着学习和实践。而真正能找到的学习材料里面,用到了一些基本原理,可能是上一代开发模式里就已经确认的,也可能是上上代就已经确认的,在最新的材料里面,就默认读者已经知道前几代就已经确立了没变的那些原理了,并不会专门去讲解。这就给理解制造了极大的困难。

目前,我找到了一个比较好的能够跟着操作的,就是苹果官方开发网站的 SwiftUI 教程。我已经照着学习了几天了,每天往前做一两个例子,总好过一点也没有前进的好,至少在所有的官方例子实践一遍之前,我还没有卡住过。这是唯一不改一字就可以照着操作的学习资料了。

在学习 SwiftUI Tutorials 的时候,我发现,鼓吹的 Preview 功能,经常有不正常的时候,比如我明明按照例子,什么都写对了,但是就是无法预览,预览的内容始终停留在代码更改之前的样子。我重新 Build 后,仍然解决不了,我不能确认是否代码编写正确。这时候,我如果关掉 Xcode,重新启动后,预览竟然又正常了。重启大法好,对 IDE 都有效。

跟一个五六年前就开始做 iOS 朋友请教一些 Xcode 的用法,他说,SwiftUI 性价比不高,学这个不如学习 Flutter 或者 React,理由是前向兼容性差。估计,他觉得,我作为一个业余选手,如果用不多的时间来学习的话,选择 SwiftUI,可能不太合算吧。至少他们这样职业的开发者,是不会考虑使用 SwiftUI 的,因为可能不兼容老版本的各种设备或者各种系统。

– 至此大概放弃了 –

公司要求每个部门制定事故定级和处罚规定。听那意思是,还是以处罚为主,希望制定出来不同等级的事故的不同处罚。

听说,这个想法来由是,财务团队曾经数次出现疏漏,给公司带来了几个不小的麻烦,所以,想要逼着所有部门都能制定事故分级管理和处罚规定,估计心里想得是希望以此能威慑员工,做事小心不要犯错误。

阅读全文 »

公司大了,内网系统多了,关键性信息和商业秘密也多了以后,内网信息安全就成了很重要的命题,使用各种框架默认带有的类库,直接弄一个简单的用户名密码校验就已经不满足需求了。

设计一个身份验证解决方案就势在必行,当然了,设计一个解决方案,不一定非要自己撸代码,可以对接、购买,关键是看你的需求是什么。当然,我们还是选择了自己实现一个,这可能不是一个很好的选择,要在此声明这一点。

阅读全文 »

今天这道题目真是简单,把一个整数反转了,要考虑负数的情况,如果反转过来溢出了,返回 0,假设的整数是 32位 有符号整数。我写了这样的代码:

1
2
3
4
5
6
7
8
9
class Solution:
def reverse(self, x: int) -> int:
flag = -1 if x < 0 else 1
x = -x if x < 0 else x
res = 0
while x > 0:
res = res * 10 + (x % 10)
x = x // 10
return res * flag
阅读全文 »

我敢说,来到 LeetCode 的人,大多数会从这道题开始,简直就是算法界的 Hello World 好不好,特别简单,直接就能做出来,自我感觉良好,可以继续了。但是,废渣如我,在这种题目上还是掉坑了,也是没谁了吧……

阅读全文 »

先来看看题目:

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

53. 最大子序和 <– 传送门

一看到题目,我是懵逼的,完全不知道怎么做。我只知道穷举法,穷举完所有的子序列,并求和,就可以得到最大的值是多少。不过,当题目里出现了“最大”之类的字眼,然后,一看又需要穷举的情况,往往意味着,这道题目需要使用动态规划来求解。我们看看如果穷举的话,时间复杂度是多少,从第 0 个元素开始的子序列,有 n 个,从第 1 个元素开始的子序列,有 n - 1 个,……, 以此类推,总共 n ( n + 1 ) / 2 个子序列,时间复杂度是 O(n^2) ,很显然了。提示里说,数组长度不大于 3 万,平方一下就是 4.5 亿次求和,真穷举的话必然无法 AC 了。

复习一下动态规划的先决条件,重叠子问题,最优子结构,无后效性。我个人学习的感受是,我们往往很难写出合适的状态转移方程,其实就是不能把问题规约成规模更小的子问题。看官方题解,你总是会惊叹于,怎么会想到把子问题看成这个模样呢?我怎么想不到呢?

这个题目,我们来看看怎么解,比如我穷举总是会的:

穷举法

这个图,显示了我的穷举法,首先是第 1 个元素 4 开始的子序列,一共有 6 个,然后是第 2 个元素 -1 开始的子序列,一共有 5 个 ……

图里展示了穷举的前两次循环。然后我们发现一个问题,穷举第 2 个元素开始的所有子序列的 Sum 的计算,在穷举第 1 个的时候,又重新计算了一遍。如果,我们在计算第 2 个元素开始的所有子序列的和的时候,缓存下来结果,那么,计算第 1 个的时候,我们只要在利用缓存的结果,就可以大大节省求和的量。我们注意到,如果我们先算第 2 个元素开始的,再算第 1 个开始的,会比较容易一点。不过这需要倒序来穷举,倒也不难。

再进一步,我们目标是求最大值,是不是,只要缓存一个最大值就够了?例如图中,第 2 个元素开始的所有子序列里,Sum 最大的是 5,而第 1 个元素开始的所有子序列里,Sum 最大的显然就是 5 + 4 = 9 了。

使用缓存数组帮助求解,注意图里 i 是输入数组的下标

好,现在可以开始编写题解了。我们使用一个数组,记录第 i 个位置开始的所有子序列 Sum 的最大值,它左边,第 i - 1 个元素开始的子序列的最大值,就是再加上当前第 i - 1 个元素:

1
2
3
4
5
6
7
8
9
10
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
l = len(nums)
if l == 1:
return nums[0]
dp = []
dp.append(nums[l - 1])
for i in reversed(range(l - 1)):
dp.append(dp[l - 2 - i] + nums[i])
return max(dp)

从数组末尾开始,最后一个元素开始的最大子序和,就是它本身,所以,一开始我们把它加入到记录数组 dp 里(注意下标的问题,因为一开始 dp 是空的,所以第 0 个位置放的是我们想缓存的最后一个值,最后一个值的下标是 l - 1。接下来遍历的时候,为了计算第 i 个值,我们需要知道第 i - 1 个值,其下标是 l - 1 - i - 1,也即 l - 2 - i,这里有点绕)。然后,我们进行提交,就会发现,这个算法是错的。为什么呢?因为我们没有考虑负数带来的影响。

因为我们是倒着计算的,所以,每次计算第 i 个元素开始的所有子序和的时候,第 i - 1 个元素开始的所有子序和已经计算完了。我们不妨把第 i - 1 个元素开始的所有子序列,叫做第 i 个元素开始的所有子序列的后缀。那么,如果一个后缀的最大值是一个正数,那么第 i 个数加上这个后缀,一定变得更大,所以原来算法没问题。如果一个后缀是负数,那么第 i 个数加上负数后缀,就会变小,那么最大值反倒小了。这时候,最大值就是第 i 个元素本身,不用再加后缀了。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
l = len(nums)
if l == 1:
return nums[0]
dp = []
dp.append(nums[l - 1])
for i in reversed(range(l - 1)):
if dp[l - 2 - i] < 0:
dp.append(nums[i])
else:
dp.append(dp[l - 2 - i] + nums[i])
return max(dp)

然后我们把算法改成了这样,提交,oh yeah,这回就做对了!

到了这一步,我们心里压力已经解除了,因为题目做出来了。这个解法的时间复杂度是 O(n)。空间复杂度,也是 O(n),因为使用了一个缓存数组。不过,我们注意到,最后我们返回了 max(dp),意味着,其实我们只需要一个值,不必一个数组,只要始终保持最大值就可以了。另外,我们计算 dp[i] 的时候,我们还要依赖 dp[i-1],所以我们再用一个变量记住它就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
l = len(nums)
if l == 1:
return nums[0]
maxSum = preMax = nums[l - 1]
for i in reversed(range(l - 1)):
if preMax < 0:
preMax = nums[i]
else:
preMax = preMax + nums[i]
maxSum = max(preMax, maxSum)
return maxSum

然后,代码被改成了这样。我们用 preMax 代替了 dp 数组,表示上一个位置的最大值,我们用 maxSum 记录了遍历过程中,出现过的最大值。空间复杂度被优化到了 O(1)。如此,我就展示了怎么从最朴素的穷举,推导出动态规划的过程。

在推理过程中,我们发现,倒序穷举的话,比正序穷举更能复用之前的计算结果。那么,我们现在是缓存了第 i 个元素开始的最大子序和,如果逆向思考一下,我们改为缓存第 i 个元素结束的最大子序和,代码会是什么样子呢?您可以自己写一下试试。

这个题目其实可以说是很多算法比赛训练的入门题目了,我一早就知道。

给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。

提示:

num1 和num2 的长度都小于 5100

num1 和num2 都只包含数字 0-9

num1 和num2 都不包含任何前导零

你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式

415. 求两数之和 <– 传送门

阅读全文 »

先来看题目的描述:

给定一个只包括 ‘(‘,’)’,’{‘,’}’,’[‘,’]’ 的字符串,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。

  2. 左括号必须以正确的顺序闭合。

  3. 注意空字符串可被认为是有效字符串。

20. 有效的括号 <– 点此传送

主要就是判断这个括号是否配对,麻烦点就是有三种括号,这个题目,直觉上,就是需要用到数据结构——栈(Stack),但是我好像不是很熟悉,在 Python 里,栈这个数据结构怎么写,应该是用个类库或者语言自带的某种数据结构来实现。这里暴露了我不熟悉 Python 的数据结构这件事情。

所以,我想到了,我先用递归算法,把这个问题给解决掉,然后,再看答案学习这个栈怎么用好了。这个题目显然具备某种递归结构,我们在整个串里搜索第一个配对的括号,然后去掉这一对括号后,剩余的字符串,应该仍然是合法的字符串,找不到,或者剩下的字符串不合法,那就一定是不合法的。

另外,如果整个串是合法的,至少有一对连续的两个字符,正好是配对的。于是我的算法就是先找到这一对挨着的,然后去掉,递归判断剩下的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def isValid(self, s: str) -> bool:
if s == "" :
return True
mapping, i, l = {'(':')', '{':'}', '[': ']'}, 0, len(s)
while i < l:
if i + 1 < l and s[i+1] == mapping.get(s[i], ""):
left = s[:i]
right = ""
if i + 2 <= l:
right = s[i+2:]
return self.isValid(left+right)
i += 1
return False

做对题目后,我看了题解,原来 Python 的 stack 就是最简单的用 list 实现的,真是方便啊:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def isValid(self, s: str) -> bool:
mapping = {')': '(', '}': '{', ']': '['}
stack = []
for c in s :
if c in mapping:
top = stack.pop() if stack else '#'
if mapping[c] != top:
return False
else:
stack.append(c)
return not stack

知识点:

  1. Python 的栈可以用 list 实现,压栈操作是 append,出栈是 pop;
  2. list 变量可以直接在 if 条件中判断是否为空,空 list 为 False;
  3. key in dict 语句,可以直接判断一个字典是否包含某个 key;

世上许多重要的转折是在意想不到时发生的,这是否意味着人对事物发展进程无能为力?请写一篇文章,谈谈你对这个问题的认识和思考。这是今年高考,上海卷语文作文题目。

看到这个题目的时候,我正好在重温计算机系统的基础知识,重新学习了 Amdahl 定律。这个定律讲的是,在一个系统中,如果我们改善了系统的一个部分的性能,那么整个系统的性能改善,会被这个被改善部分在整个系统中重要性占比给削弱。

阅读全文 »