* update

* update

* Update 2.3 算法与编码能力.md

* Update 2.3 算法与编码能力.md

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update
This commit is contained in:
xiaowuhu 2022-10-19 10:30:36 +08:00 коммит произвёл GitHub
Родитель 2bb54b9b4a
Коммит aa0c646d23
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
59 изменённых файлов: 1728 добавлений и 179 удалений

Просмотреть файл

@ -33,7 +33,7 @@ $$
有的人会问:在学校里学习的软件工程知识不够用吗?笔者只能说那些知识根本不够用,否则也不会写这本书了。
#### PM
#### 项目管理 PM
$$
PM = 项目管理 + 产品管理 \tag{1.11}

Просмотреть файл

@ -16,11 +16,11 @@
第一个面试官是一个 PM他的主要职责是询问木头的技能点是否满足 JDJob Description职位描述要求。比如在这次面试中希望木头有大批量数据的处理经验因为是要做一个后台的 Pipeline数据处理流程
做为一个 Industry Hire Candidate即有多年从业经验的求职者),写简历的基本技巧如下:
做为一个 Industry Hire Candidate即有多年从业经验的面试者),写简历的基本技巧如下:
#### 让 HR 能够一眼看到关键字
因为简历都是通过 HR 筛选出来的,而大部分 HR 是不懂技术的Ta 们每天面对大量的简历,首先就是要通过关键字来匹配岗位需要和求职者的经验或技术能力。
因为简历都是通过 HR 筛选出来的,而大部分 HR 是不懂技术的Ta 们每天面对大量的简历,首先就是要通过关键字来匹配岗位需要和面试者的经验或技术能力。
- 开发语言掌握能力,比如 C++, Python, C#, Php, Java 等等。
- 设计能力和经验,比如大数据存储与处理经验、复杂系统搭建经验、大用户量的并发访问设计经验、训练神经网络模型的经验、手机应用开发的经验等等。
@ -28,17 +28,17 @@
#### 让技术面试官能看到项目经验
技术面试官通常不会被关键字所迷惑,而是看求职者的具体项目经验。在每一个项目介绍中,应该包含一下内容:
技术面试官通常不会被关键字所迷惑,而是看面试者的具体项目经验。在每一个项目介绍中,应该包含一下内容:
- 项目内容,比如企业信息管理系统、网络购物系统、游戏系统、视频点播系统、股票预测系统等等。
- 项目规模,比如有多少开发人员、代码量多大、开发时间多长、使用了多少台服务器、用户量多大等等。
- 承担的角色,比如做为普通开发人员、资深开发人员、技术专家、开发经理、系统设计师等等。
- 项目实施结果,比如上线后的用户反馈情况、如何更新升级功能、如何降低维护成本等等。
- 遇到的困难及解决方案,由于内容较多所以通常不会写在简历里,但是求职者要准备好面试官的询问。
- 遇到的困难及解决方案,由于内容较多所以通常不会写在简历里,但是面试者要准备好面试官的询问。
#### 让老板能看到能力特点
具有同等技术能力的求职者有很多,如果都能通过前面的技术面试的话,老板为什么会选择你?通常下面几点会影响老板的决策:
具有同等技术能力的面试者有很多,如果都能通过前面的技术面试的话,老板为什么会选择你?通常下面几点会影响老板的决策:
- 从经历中看你的发展方向和速度,会决定你将来在这个公司的发展曲线。
- 性格特点,比如有什么业余爱好、是否有社团经历,决定你是否可以很快地融入新的团队。一些高大上的业余爱好也是有加分的,比如音乐、桥牌、魔方等技能。
@ -93,7 +93,7 @@
### 2.2.6 解决问题的能力Problem Solving
一般情况下,面试官会把这种能力与算法编码能力以及设计能力混淆,因为他们认为一个求职者的解决问题的能力就应该体现在编码和设计上。但其实笔者认为这是两个领域的问题,解决问题的能力,应该处于更高层次,是一种可以普遍适用的能力。
一般情况下,面试官会把这种能力与算法编码能力以及设计能力混淆,因为他们认为一个面试者的解决问题的能力就应该体现在编码和设计上。但其实笔者认为这是两个领域的问题,解决问题的能力,应该处于更高层次,是一种可以普遍适用的能力。
比如,需要你用 GPU 测试一个模型,但是公司内部没有现成的 GPU 机器,你怎么办?向领导要,领导会说:你去 Azure 上找一找。于是你就应该在 Azure 上的十几个 Data Center 的好几十个 Zone 里面,找到一个恰好有 GPU 资源的并且能满足你的 subscription 的 quota 的 GPU 虚拟机,申请下来使用。

Просмотреть файл

@ -10,14 +10,41 @@
有一个 [0-100] 的数组,在随机位置无回放地从 1-100 共 100 之间取一个数填入,还富余出一个空位,随机填入 1-100 之间的任意一个数。求重复的数字是什么?至少用 5 种方法写出代码求解。
为了简化,在图 2.3.1 中,我们假设是 [0-10] 的数组,其解题原理和 [0-100] 没有区别。
我们使用 Python 语言来生成数组和写算法,在面试时,你可以使用任何语言甚至伪代码,除非职位有要求一定要用 C++ 或其它编程语言。
```python
import random
max_number = 10 # 以最大值为10为例便于说明问题读者可以自行改到100
def generate_data():
data = [i for i in range(1, max_number+1)] # 生成一个顺序数组
data.append(random.randint(1, max_number)) # 最后随机填一个数
print("顺序数组=",data)
random.shuffle(data) # 打乱顺序
print("乱序数组=", data)
return data
```
生成的数组:
```
顺序数组= [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 4]
乱序数组= [5, 10, 8, 4, 3, 2, 7, 1, 4, 6, 9]
```
以下各算法将针对乱序数组求解。
下面我们列出从简单到复杂的几种解法,其实这个问题本身就不难,所以解法也很容易理解,仅供读者开阔思路。
### 求和法
### 2.3.1 求和法
小学生都知道:
$$
\sum_{i=1}^{100}=\frac{1+100}{2} \times 100=5050
\sum_{i=1}^{100} i=\frac{1+100}{2} \times 100=5050
$$
所以把 101 个数组单元内的数字相加,在减去 5050得到的数字就是重复的数字。比如求和结果是 5061则 $5061-5050=11$11 就是重复的数字。
@ -25,20 +52,162 @@ $$
算法时间复杂度:$O(n)$,因为所有数字遍历一遍。
算法空间复杂度:$O(1)$,只需要保留求和结果。
### 哈希法
```python
def method_1_sum(data):
sum = 0
for x in data:
sum += x
#print(sum - 5050)
print(sum - int((1+max_number)*max_number/2))
```
### 2.3.2 哈希法
使用一个哈希表来顺序保存 101 个单元内的数字,那么在哈希表中重复的那个数字就很容易得到。
比如在 Python 中,字典就是有哈希表功能的。
算法时间复杂度:$O(n)$,最坏情况下所有的数字遍历一遍存放到字典中。
算法空间复杂度:$O(n)$,需要一个额外的字典来保存数据。
```python
data = [......] # 事先
def method_2_dict(data):
dict = {}
for x in data:
if dict.__contains__(x): # 字典中已经有此数,是重复的
print(x)
return
else: # 如果字典中没有此数,则保存
dict[x]=1
```
for i in range(101):
注意,这里不能使用数组的 “_ contains _ ” 函数来判断是否已经存在相同的值,因为该函数是遍历方式搜索,属于 $O(n)$ 的时间复杂度,而字典的 “_ contains _” 函数是 $O(1)$ 速度的,但是计算哈希值需要一些时间。
### 2.3.3 排序法
首先对原数组进行排序,然后从头遍历找到重复的数字。
算法时间复杂度:$O(n\log n)$,因为数组的 sort() 函数使用了快速排序的算法。
算法空间复杂度:$O(1)$,排序时需要的缓冲。
```python
def method_3_sort(data):
data.sort()
for i in range(max_number+1):
if data[i] == data[i+1]: # 相邻的两个数相等,是重复数字
print(i+1)
return
```
### 排序法
### 2.3.4 追溯
如图 2.3.2 所示,我们以 5 个数字为例来简化说明问题。
1. 从位置 0 取出数字 3放到位置 3
2. 从位置 3 取出数字 2放到位置 2
3. 从位置 2 取出数字 1放到位置 1
4. 从位置 1 取出数字 5放到位置 5
5. 从位置 5 取出数字 1放到位置 1但发现位置 1 上已经是 1 了,是重复数字。
<center>
<img src="Images/Slide5.JPG"/>
图 2.3.2 追溯法
</center>
这种解法是利用了本题的特点:数据和数组序号可以一一对应。
算法时间复杂度:$O(n)$,这是最坏的情况,大多数时候可以提前结束。
算法空间复杂度:$O(1)$,交换数字时的缓冲。
```python
def method_4_search(data):
pos = 0 # 从 0 位开始
x = data[pos] # 从 0 位取出数字
while (True):
if (x == pos): # 在目标位置上已经有一个相同的数字,是重复的
data[0] = x # 不是必须,只是把重复的数字放到第 0 个位置上
print(x)
break
pos = x # 保存 x 的值到 pos
x = data[pos] # 取出 pos 位置的数值 x
data[pos] = pos # 把 pos 位置成 pos 值,如,第 3 个数组单元就置成 3
print(data) # 打印输出排好的数组,但有可能已经找到了重复数字而没有完成排序
```
### 2.3.5 异或法
异或运算满足交换律和结合律,并且 $x \oplus x = 0, x \oplus 0 = x$。
假设重复的数字为 $x$,则包含有两个 $x$ 的乱序数据的异或结果为:
$$
T_x = 1 \oplus 2 \oplus 3 \oplus \cdots \oplus x \oplus x \oplus \cdots \oplus 100
$$
没有重复的数字的连续的 100 个整数的异或结果为:
$$
T_n = 1 \oplus 2 \oplus 3 \oplus \cdots \oplus x \oplus \cdots \oplus 100
$$
二者异或,利用交换律和结合律重新编排计算顺序:
$$
\begin{aligned}
T_x \oplus T_n &= (1 \oplus 1) \oplus (2 \oplus 2) \oplus \cdots \oplus (x \oplus x \oplus x) \oplus \cdots \oplus(100 \oplus 100)
\\
&= 0 \oplus 0 \oplus \cdots \oplus (x \oplus x \oplus x) \oplus \cdots \oplus 0
\\
&= 0 \oplus x \oplus x \oplus x
\\
&= x
\end{aligned}
$$
即二者异或结果就是重复的数字 $x$。
算法时间复杂度:$O(2n)$,两遍异或运算。
算法空间复杂度:$O(2)$,保存异或结果。
```python
def method_5_xor(data):
# 求所有数字的异或结果,包含重复的数字
tmp_x = 0
for i in range(len(data)):
tmp_x = tmp_x ^ data[i]
print(tmp_x)
# 求 1~max_number 的异或结果
tmp_n = 0
for i in range(max_number):
tmp_n = tmp_n ^ (i+1) # 注意是 i+1不是 i因为 i 从 0 开始
print(tmp_n)
# 上面两者异或,可以得到重复的数字
print(tmp_x ^ tmp_n)
```
### 2.3.6 其它
读者也许还有其它求解的办法,可以贡献到这个 GitHub 中供大家学习。
这个数组问题本身比较简单,但是在短时间内可以准确地写出 5 种方法来并非易事。所以,面试官出题的原则应该是:
- 一般水平的面试者可以部分答对;
- 超过一般水平的面试者在可以接受的时间内全部答对;
- 水平更高的面试者可以在较短的时间内全部答对;
- 最高水平的面试者可以给出 7 种解法。
微软的面试官出题,一般会用更复杂的链表、树等数据结构来考察面试者,也有人会出类似动态规划的题目(但笔者个人认为不合适)。每个面试官在一个小时的时间内,至少会出两道算法题,如果面试者两道题都答对,就再问一些考察设计能力方面的问题。
面试者在答题的过程中,可以这样做:
- 确认题目的具体细节,避免浪费时间走错方向;
- 在有思路后,立刻和面试官讨论,得到确认,然后再继续给出后续答案;
- 如果想了几分钟后还没有思路,可以请求面试官给一些提示;
- 如果遇到的题很难,或超出自己知识范围了,可以说明情况,请面试官换一道题。比如:
- 遇到动态规划的题目,你可以提出来更换题目;
- 假设你只是面试初级软件工程师,还没有能力做系统设计,但是面试官问到相关问题时,你可以坦白地说明情况,避免后面瞎编乱造。
还有一点需要注意:最好不要带着笔记本电脑去面试,否则面试官很可能要求你打开电脑写真实的代码来验证你的算法。在很多时候,你在纸上写出来的算法,如果没有明显的漏洞的话,面试官也不会计较一些隐藏的小问题(因为面试官自己也不一定能看得出来)。但是一旦上机,那些小问题可能会造成程序无法得到正确的结果,改了半天还是不对,从而造成答题失败。

Просмотреть файл

@ -0,0 +1,277 @@
## 2.4 建模与设计能力
<center>
<img src="Images/Slide6.JPG"/>
图 2.4.1 建模
</center>
### 2.4.1 建模题
建模题一般就是考察面试者对事物的观察归纳能力以及面向对象的设计思想,这种设计仅限于对象级别的,稍微复杂一些的会涉及到组件级别,而非系统级别的。
#### 给一个会议室建模
- 定义一个会议起止时间的帮助类
```Python
class BookTime():
start: time
end: time
```
- 定义会议室
```python
class MeetingRoom():
location: Geometry # 位置
no_room: str # 会议室编号
no_seat: int # 座位数
has_projetor: bool # 有无投影仪
has_whiteboard:bool # 有无白板
has_network: bool # 有无网络会议设备
status: InUse|Free # 状态:使用中|空闲
book_times: BookTime[] # 被预定时段列表
```
- 定义会议
```python
class Meeting():
book_time: BookTime # 会议时间
room: MeetingRoom # 会议室
host: People # 会议主持人
attendee: People # 与会者
topic: str # 议题或内容
owner: People # 订会议室的人
status: Normal # 状态(正常、进行中、取消、已结束)
```
给出了会议室的定义后,如果能够一鼓作气给出会议的定义,会更好,说明你思路清晰。因为有的面试者会误把会议的内容定义到会议室里面去。
#### 给一个十字路口的红绿灯系统建模
- 定义基本枚举值
```python
class Colors():
Red = 1 # 红灯
Green = 3 # 绿灯
class Status():
On = 1 # 亮起
Off = 0 # 熄灭
```
- 定义单个灯
```python
class Light():
# 属性
status: Status
# 动作
def TurnOn(color) # 开灯并指定颜色
def TurnOff() # 关灯
```
上面的定义中,开灯或关灯与具体的交通信号含义无关,还需要上层对象来调用。
- 定义灯组,至少两个灯组成一个灯组
```Python
class LightGroup():
# 属性
light_Straight: Light # 直行灯
light_LeftTurn: Light # 左转灯
# 动作
def SetStraight(On|Off) # 允许|禁止直行
def SetLeftTurn(On|Off) # 允许|禁止左转
```
上面的动作定义中,就可以对应得到具体的交通信号含义了。
- 定义中控器
```python
class ControlCenter():
light_groups: LightGroup[4] # 路口的每个方向都有一个灯组
scheduler: timer # 定时器用于对LightGroup进行定时开关操作
```
最后,每个十字路口都肯定会有一个中控器来控制四组灯的信号以保证其协调性。这一点往往是面试者容易忘记的,原因是他们并没有观察到有时候警察叔叔会手动操作中控器来控制交通流量。
#### 给一个魔方建模
很多没有玩过魔方的人,会对此问题一筹莫展。在实际的面试时,可以要求面试官提供一个魔方,供面试者动手观察。
一个魔方的单面有:
- 一个中心块
- 四个棱块
- 四个角块
而扩展到六面以后,总共会有:
- 六个中心块
- 八个角块
- 十二个棱块
如果仔细观察的话,魔方的中心块实际上只能原地旋转,六个中心块的相对位置是不会发生变化的,而所谓的还原魔方,就是把与中心块相同的颜色都拼在一起。所以可以用中心块的颜色来代表六个面。
先定义块的颜色和位置:
```python
# 颜色
class Colors(Enum):
White = 0
Red = 1
Yellow = 2
Blue = 3
Green = 4
Orange = 5
# 位置, 以魔方的中心点为坐标(0,0,0), 每个块在[-2,+2]之间
class Position():
x:int
y:int
z:int
```
再从最小的单元块定义:
```python
# 块的基类
class Block():
postion: Position
# 中心块, 一种颜色
class CenterBlock(Block):
color_0: Colors
# 棱块, 两种颜色
class EdgeBlock(Block):
color_0: Colors
color_1: Colors
# 角块, 三种颜色
class CornerBlock(Block):
color_0: Colors
color_1: Colors
color_2: Colors
```
然后定义一个面:
```python
# 一个面
class Side():
# 属性
center_block: CenterBlock # 1个中心块
corner_blocks: CornerBlock[4] # 4个角块
edge_blocks: EdgeBlock[4] # 4个棱块
# 动作
def Rotate_90(): pass
def Rotate_180(): pass
def Rotate_270(): pass
```
最后定义整个魔方:
```python
# 一个魔方
class RibukCube():
side_Front: Side
side_Back: Side
side_Up: Side
side_Down: Side
side_Left: Side
side_Right: Side
```
给魔方建模有几个误区:
- 从静态结构上把它分成上、中、下三层
- 从动作上多定义一个中心层的旋转
- 用颜色代替块的概念(颜色只是块的一个属性而已)
如果有中心层,并且可以旋转的话,整个模型会变得非常复杂而不可描述,实际上中心层的“正向”旋转可以用两个边层的逆向旋转来表达,但是所谓的“正向”是无法定义清楚的,不像每个面那样可以把旋转 90 度和旋转 270 度区分开。
### 2.4.2 设计
应用场景:
- 甲方每天早晨 8:00 传给乙方前一个交易日的股票大盘数据
- 乙方收到数据后,立刻验证数据是否齐备,然后使用已有模型做股票价格预测
- 需要在 9:30 之前把预测结果发送回甲方
限制条件:
- 保存所有的历史大盘及预测记录
- 全自动化,无人干预
- 用微软 Azure 提供的技术
这个问题需要有一定的系统设计能力了
<center>
<img src="Images/Slide7.JPG"/>
图 2.4.2 股票预测系统设计
</center>
由于是每个交易日都要进行这样的操作,所以全自动化流程是很有必要的。
1. 甲方准备好前一个交易日的数据后,安装双方约定好的格式上传到云端存储;
2. 然后通知控制中心来启动预测服务;
3. 控制中心通知乙方(可选);
4. 然后启动预测服务;
5. 预测系统首先要读取数据;
6. 等到数据返回后进行验证,然后进行预测;
7. 预测将会持续一个多小时;
8. 保存预测结果到云端存储;
9. 然后通知控制中心预测结束;
10. 控制中心同时通知甲方和乙方;
11. 甲方去约定好的地方下载结果到本地使用。
#### 关于存储
由于需要使用 Azure 技术(或其它公司的云服务),所以存储本身也是一个服务,而不是像传统的数据库或者磁盘文件那样还需要有一个本地的存取接口。
不习惯使用云端存储的人,在这里一般会选用关系式数据库做存储媒介,而股票数据确实也是符合关系式数据库的使用标准,但是在这个应用场景中,没有任何需要查询某只股票的某个字段的需求,所以使用关系式数据库就是一种浪费,容量和性能都会有问题。
所以在这里我们可以使用 Azure Storage Blob 来存储大块的数据,速度快、容量大。
#### 关于控制中心
这里一般存在两个误区:
1. 做一个网站,提供上传、下载的页面,供甲方使用。
2. 把控制中心和预测系统合并在一起。
控制中心的作用主要是隔离用户与预测系统,不让二者有直接的交互,否则容易产生误操作。
而由于有云端存储服务的存在,所以上传、下载数据可以直接从客户端发起,而不需要网页服务,否则数据就需要从客户端传到网页上,在由网站转存到云端存储服务上,影响可靠性和性能。
#### 关于预测系统
这个系统应该设计得很简单,对外接口是:
1. 和控制中心的接口
- 得到通知,参数中含有最新股票数据的位置;
- 发送预测结果通知(成功、失败)。
2. 和云端存储的接口
- 读取股票数据;
- 保存预测结果。
这里的设计误区是:预测系统和甲方、乙方直接有交互。由于预测所需要的时间很长,如果甲方误操作(比如连续多次上传数据并启动预测服务),预测系统将会不能正常工作。
#### 关于异常
由于篇幅原因,在图 2.4.2 中没有做一些异常检测及处理,这也是考察面试者的重要因素。比如:
1. 甲方上传的数据格式有问题怎么办?
2. 控制中心是 7x24 小时工作的吗?会不会宕机?
3. 预测系统启动失败怎么办?
4. 预测过程中出现异常时,如何通知甲方或乙方?

Просмотреть файл

@ -1,81 +0,0 @@
## 2.3 系统思维能力
在上一节中提到过“解决问题的能力”,是指的一些简单的问题,只需要做出一两个正确的动作就可以解决。如果问题的依赖链条较长,就需要“系统思维能力”了。
我们先用一个具体的例子来解释这两者的关系。
木头在面试求职者时,前面当然会考察算法和编码能力,如果都后面还有时间的话,总会问下面这个问题:**北京城区需要有多少个加油站,可以满足加油排队时间不超过 10 分钟?**
要解决这个问题需要一系列的假设数据支持,有些数据是可以从网上查到的,有些是常识,剩下的就需要靠推理了。
正确的解题思路如下:
#### 1. 北京有多少人口?
可以从官方的数据上得知,北京的常驻人口为 2100 万。
#### 2. 汽车保有量
假设是三个人组成一个家庭,那么一共有 700 万个家庭。假设每个家庭拥有一辆汽车,则一共有 700 万辆汽车。
但是由于以下因素,会造成这一数字的不准确:
- 摇号限制购买的措施,这个数字会减少
- 很多年轻人还没有成家,但也可能有私家车,这个数字会增加
- 北京还有很多企事业单位用车
- 北京的出租车大概有 60000 辆
上面几个条件有增有减,所以最后的汽车保有量大概就是 700 万辆左右。
#### 3. 出车率
不是每个有车的人都要开车,并且距离上班比较近的人不会开车上下班,还有限号措施(我们忽略电动汽车,因为它数量较少而且不需要加油)。据在木头居住的小区内观察,每天的出车率比疫情之前减少很多,大概只有 30%。这说明大家都有了节能环保绿色出行的理念,当然,也有可能是单位附近的停车费太高。
那么每天在马路上跑的车大概有 700 万辆 x 30% = 210 万辆
#### 4. 每天行驶多少公里?
北京的面积是 1.6 万平方公里,近似圆形,那么根据 $S=\pi r^2$ 公式,得到 $r=71$ 公里。由于此问题是估算北京城区的加油站数量,所以减去 2/3可以认为半径为约 25 公里。也就是说人们上下班开车来回的平均距离为 25公里 x 2 = 50 公里左右。
#### 5. 一箱油可以跑几天?
- 大排量汽车越来越多SUV 已经成为了购车首选,所以导致车辆每百公里耗油平均为 8 升。
- 一般的汽车的油箱容积为 50 升按一周开5天4天工作日 + 1天休息日开车出门办事或旅游
50 升 / 8升 x 100公里 = 625 公里,这是一个理想的里程。在城市里开,实际上是 500 公里左右就需要加油。
那么一箱油可以跑500公里 / 50公里每天 = 10天
#### 6. 每天有多少车需要加油?
10天 / 5天每周 = 2周即大概每个月加两次油平均 15 天加一次油。
那么每天需要加油的车辆为 210万辆 / 15天 = 14 万辆。
#### 7. 加油站的服务质量
- 以 92 号汽油为例,加油站一般有至少 4 把油枪,可以同时加油,即可以同时服务 4 辆车。
- 每辆车的加油时长大概是 2 分钟。
- 我们要保证排队时间不超过 10 分钟,加上司机付费的时间,大概是每把油枪排 4 辆车。
那么每小时可以给 60分钟 / 10分钟 x 4把油枪 = 24 俩车加油,可以约等于 25 辆车。
全天 24 小时营业,但是司机一般会在早晚上下班的时段来加油,也就是峰值时段大约是 4 个小时。
每个加油站可以一天加油 25辆车 x 4 小时峰值时段 = 100 辆车。
如果忽略了这个峰值时段,那么需要的加油站数量将会大大减少。就好比在双 11 购物节的时候,大量的用户涌入网站,需要 10000 台服务器才能处理,而在平时,只需要 1000 台服务器就可以搞定。
假设一个加油站要花很多钱,那么多加一把油枪不会花很多钱,所以可以用增加油枪数量的办法来缓冲峰值时段问题。
#### 8. 需要多少个加油站?
14万辆 / 100辆 = 1400 个加油站
所以最后的答案就是 1400 个加油站。如果推算出 1000~1500 个,都是在合理范围内。
什么是系统性思维
解决问题的能力

Просмотреть файл

@ -0,0 +1,289 @@
## 2.5 解决问题的能力
### 2.5.1 能力的评级
在前面的故事中提到过“解决问题的能力”,是指的一些简单的问题,只需要做出一两个正确的判断或者动作就可以解决。
解决问题的能力,需要五个阶段的培养或锻炼,下面我们采用倒序方法从能力高到能力低的顺序来讲述。
<center>
<img src="Images/Slide8.JPG"/>
图 2.5.1 培养解决问题的能力
</center>
#### 阶段五:啥都不给
这样的领导最省心了,开会的时候喊一句:同学们,数据到了,大家分工处理一下吧。然后木头、石头、铁头等小组成员需要自己去讨论方案,最后汇报给领导。
这一阶段需要个人能力达到了一定的高度,领导充分信任,才有可能发生。
#### 阶段四:只给目标
比如,木头在刚开始工作时,需要处理一个地图数据中的聚类问题:在一条 50 米长的路上,零零散散地分布了很多公交车的站牌。有些站牌相距只有 5 米,而有些站牌却距离 20 米远。那么,哪些情况下这些站牌可以认为在地理位置上是同一个“点”呢?
这个阶段(对领导来说)也很简单:木头同学,你需要在一个月的时间内,把所有中国重点城市的公交车的站点按名称和地理位置做聚类,这样后续的换乘算法可以利用你给出的拓扑结构。
但是有时候领导可能说得不太准确:木头同学,你需要在一个月的时间内,把所有中国重点城市的公交车的站点处理一下,后面才能写换乘算法。
<center>
<img src="Images/Slide9.JPG"/>
图 2.5.2 公交站点的聚类
</center>
这里面就有很多坑需要木头自己填平,比如:
1. “苏州街”是个站名,北京、上海、苏州都有这个站名,但是不同城市的站是不能合并的。
2. 处于街道两侧的相同名称的站是不能聚类到一起的,因为是分别属于上下行站。
3. 距离多远才算是符合聚类条件,这个需要木头自己摸索。
4. 地铁站与公交站名称相同时,是不能聚到一起的,因为虽然经纬度可能相近,但是海拔不同,一个在地面,一个在地下。
5. 如果用经纬度计算距离,那么原始数据的经纬度数值精度至少在小数点后五位数以上,否则误差将会很大。
6. 夜班车站要和白天正常运营的公交车站聚类在一起吗?
#### 阶段三:给目标,给范围
如果还用上面的例子,那么领导会是这样说:
- 目标是做公交站牌的地理位置聚类;
- 用 scikit-learn 的 Python 库里提供的聚类算法应该可以搞定。
或者是这样说:
- 目标是做公交站牌的地理位置聚类;
- 木头同学,你和石头同学一起讨论一下具体实现方案,如果需要帮忙就去找铁头同学咨询一下。
这两种说法中的第二条都是指定了范围,前者是技术范围,后者是资源范围,都会有助于木头继续开展工作。
#### 阶段二:给目标,给例子
有的时候,针对一个新问题,不能确定使用什么方法能够解决,但是以前有过类似的问题和对应的解决方案,此时不妨拿出来参考。
领导告诉木头:
- 你的目标是要做公交站牌聚类;
- 我不知道现有的聚类算法是否可以直接使用,我们以前有个例子,是这样的:
如图 2.5.2,统计了几十个人的身高体重,但是粗心的统计员忘记记录性别了,此时,可以用聚类的办法来大致把这些点分成两类,一类是男性,一类是女性。具体的数据和代码你可以在咱们的代码仓库里 (https://github.com/microsoft/ai-edu)找到,当时我们使用 DBSCAN 算法实现的。
但是,与公交站牌聚类的例子比较,可以看到二者的区别是:一个是在二维空间里的聚类,一个是在一维空间里的聚类,能保证聚类算法的效果是可信的吗?你需要做一些试验,来验证这一点。
<center>
<img src="Images/Slide10.JPG"/>
图 2.5.3 人类性别聚类问题
</center>
所以,领导即告诉了目标,又告诉了相似的例子,木头需要写代码做试验,这比“给目标,给方法”的难度要稍微高一些。
#### 阶段一:给目标,给方法
如果木头本身没接触过聚类算法,而只是朴素地思考如下:
假设相距小于 15 米的两个站牌可以认为是同一个点,那么 A 和 C 就不能聚在一起。但是如果 A 和 B 聚在一起,它们的中点就会距离 C 只有 15 米,那么 A、B、C 就会聚在一起,然后 D 又会加入......所以这种朴素的聚类思想是不可取的。
领导告诉木头:
- 首先,你的目标是要把这些站牌做一个聚类,距离很近的站牌可以让它们在拓扑结构上是同一个点,便于后面的算法执行。
- 其次,你用 KNN 算法就可以完成科学的聚类了。
所以,领导即告诉了目标,又告诉了方法,木头只需要写代码实现就好了。
总结一下,一个人解决问题的能力是需要培养的,基本上要经历以下几个阶段:
- 在阶段一,需要给定“目标 + 方法”,才能够解决一个问题;
- 在阶段二,需要给定“目标 + 例子”,举一反三,即可完成任务;
- 在阶段三,需要给定“目标 + 范围”,自己在该范围内寻找解决问题的方案;
- 在阶段四,只给“目标”,需要自己确定“范围”,通过寻找前人留下的“例子”来确定最终“方法”。
- 在阶段五,除了必要的资源以外“啥都不给”,“目标”靠自己来研究挖掘,能力高下立现。
### 2.5.2 木头遇到的技术问题
#### 复现问题
软件工程师有一句话叫做“get your hands dirty”直译为“把手弄脏”引申为“亲自动手与尝试获得第一手资料”你只有切身复现与体会到一个问题的 pain point痛点才能深刻理解它这是解决一切问题的前提。
有一个用户给木头报了一个 bug在把 ONNX 模型从 32 位浮点精度转换成 16 位浮点精度的过程中,当使用一个样本做转换过程的测试时,很容易通过;但是当使用 32 个样本(作为一批)做测试时,基本上不能通过。因为转换过程会使用 GPU 以及内存资源,所以该用户猜测是多个样本带来了更多的资源占用,从而导致失败。
木头拿到了用户的真实环境(模型、测试代码、样本)后,首先在自己的 GPU 环境中复现repro32 个样本果真会导致转换失败;然后又用 16 个、8 个、4 个、2 个样本做测试,最后得到的结果是只有 2 个或 1 个样本时,转换才会成功。
<center>
<img src="Images/Slide11.JPG"/>
图 2.5.4 培养解决问题的能力
</center>
#### 排查疑点
其次检查样本的正确性,通过对比几个样本的字段,除了数值有差异外,没有别的不同,这就避免了字段个数不同或者有些字段为空值带来的插值误差。
那么不同样本的数值的差异会带来问题吗?于是木头把一个样本复制 3 份,得到 4 个样本,输入给转换程序,但还是失败。
在此过程中木头观察计算机的内存占用情况、CPU 使用情况,以及 GPU 的内存占用情况,都没有发现有异常发生,系统各项资源使用一直很平稳,说明与资源无关。
Debug! 通过 debug 大法,木头定位到在验证转换结果时,如果使用:
```
np.allclose(x, y, rtol=1e-2, atol=1e-3)
```
这个函数返回 False表示 32 位模型的推理结果 x 与 16 位模型的推理结果 y 在数值上相差比较大。把 4 个不同样本的 32 位结果与 16 位结果打印出来如下:
```
---- 32 位模型推理结果 ----
[[0.46224755 0.53775245]
[0.4538362 0.5461638 ]
[0.39781117 0.6021888 ]
[0.44377536 0.55622464]]
---- 16 位模型推理结果 ----
[[0.46289062 0.5371094 ]
[0.44677734 0.55322266]
[0.40039062 0.5996094 ]
[0.44604492 0.5541992 ]]
```
以上面的两组数据中的第一个数为例,$(0.46289062-0.46224755)/0.46224755=0.00139118$,确实不满足 atol=1e-3 的精度。
是不是因为在模型中有一些随机因素造成了推理结果不准确呢?于是,木头检查了一遍模型,并没有返现有 dropout 等算子(操作符)出现,所以排除了这种情况。
#### 定位原因
如果只用一个样本,获得到什么结果呢?木头发现,在一个样本的情况下,转换程序在尝试过几次之后,可以完成模型转换,但是并不稳定。此时,如果把验证精度宽限到:
```
np.allclose(x, y, rtol=1e-1, atol=1e-2)
```
就可以轻松通过验证。由此可以得知就是精度问题导致验证失败。但是如果就此得出结论“16 位的模型精度有问题”,那就为时过早了。
如图 2.5.4 所示,我们一直把怀疑集中到转换的过程,在 16 位精度的模型中,一个样本 S 做两次推理得到的结果 Y 和 Y',它们有时候和 X 非常接近,有时候又不是很接近,而 Y 和 Y' 两者之间也会有精度差。
木头忽然想到:如果用 32 位模型做两次推理,得到的结果 X 和 X' 一样吗?于是得到试验结果如下:
```
---- 第一次推理结果 ----
[[0.44895846 0.55104145]]
---- 第二次推理结果 ----
[[0.45693502 0.54306495]]
```
哈哈,在 32 位模型上原来也是不稳定的!但是模型中没有任何随机因素,为什么会出现这种问题呢?于是木头咨询了领导,领导提示说看看是不是 GPU 出的问题。
于是木头在 CPU不是GPU上做了同样的试验得到结果如下
```
---- 第一次推理结果 ----
[[0.45295522 0.5470447]]
---- 第二次推理结果 ----
[[0.45295522 0.5470447]]
```
两次的推理结果完全一致!
#### 解释现象
这就说明确实是 GPU 在做推理运算时不稳定,每次的结果都会有一些精度上的误差。当使用一个样本做多次验证时,总会有一次和 32 位模型的数值精度相差不大,所以很容易通过;但是当使用 32 个样本时,虽然偏差较小,但是方差就比较大,很难同时满足 32 个样本的精度差都很小的情况,所以就不能通过验证。
#### 小结
在上面的过程中,木头经过了以下步骤来复现与理解问题:
1. 复现问题
2. 排查疑点
- 样本数量问题
- 样本字段/值问题
- 资源占用问题
- 代码逻辑问题
- 数值精度问题
- 32位/16位模型精度问题
- CPU/GPU问题
3. 定位原因
4. 解释现象
其中,在排查疑点的过程中,大量地使用了“对比法”来寻找差异,而差异往往就是问题之所在。
另外就是使用“分析法”,比如检查代码逻辑,通过日志看资源占用问题等。
最后如何 fix 这个 bug并不是木头所在的小组的职责而是由其它小组专门负责所以木头就在 GitHub Issues 里给那个小组开了一个 bug并告知了相关的 PM。
### 2.5.3 一个非技术的例子
再举一个比较通俗的例子:木头在某个级别上很长时间不能得到晋升,如何解决这个问题呢?
和解决上面的技术问题一样,我们需要排查疑点,也就是影响晋升的因素都有哪些。
<center>
<img src="Images/Slide12.JPG"/>
图 2.5.5 培养解决问题的能力
</center>
- 个人能力
个人能力是根本,这也是本章要阐述的重点。木头在这一方面还是很有自信的。
- 团队规模
在一个只有五个人的团队里,晋升的难度取决于你的老板是否受你的老板的老板重视,如果是的话,你很容易晋升,否则就很难,那么尽早离开是最好的选择。木头曾经在这个问题上用了一年多的时间才领悟到。
如果一个团队同一级别的人多,那么大家的竞争就会比较激烈,当然这种竞争不会直接表现出来,而是在老板那里做平衡,最常见的情况就是“风水轮流转,今年到我家”。
- 领导态度
领导是否认可你,是一个很关键的因素。木头曾经在一个部门中四年没有得到晋升,原因是木头的老板说:木头很不错。可是木头的老板的老板说:木头还需要努力。于是木头最终选择离开那个部门,到了另外一个部门后,就得到了很公平的待遇,晋升得很顺利,五年晋升了三级。
- 工作内容
在一个团队中,有些人工作在项目 A而另外一些人为项目 B 工作。A 项目是一个新生的领域,发展很快,影响力大。但是 B 项目是一个维护已有系统的工作,不出错就可以了。显然 A 项目中的成员会有更多的晋升机会,大老板们也会偏向给 A 项目更多的 budget。
比如微软小冰在脱离微软以前,成员们像坐着火箭一样地晋升,当然 ta 们经常加班也是不争的事实。但是在脱离微软单独成立一个公司时,每个成员都被告知会降薪(因为成员们的级别普遍太高了)。
- 积累效应
木头最近四年又一直没有得到晋升,但是心里却很平稳,因为这和上次的那个四年不一样,这一次的原因是在四年里换了四个老板:
- 第一次是因为项目被总部拿走编到别的团队了,不得不换到新团队;
- 第二次是因为整个团队都被砍掉了,不得不换到新部门;
- 第三次是因为老板跑路了,不得不换组;
- 第四次是因为上面所说的“团队规模”问题,又选择离开了,只有这一次是主动离开的,及时止损。
虽然在每个老板下的工作都很出色,但是每次都只有一年多的贡献积累,无法的得到晋升。在一个团队里,如果想得到高级别的晋升,无论如何也要两年左右,否则其它人会说闲话了:你看这个家伙刚来一年就晋升了,咱们干了三年多也没升职,不公平呀!
- 周边关系
不只是老板觉得你不错,其它同事也都觉得你不错,这样老板晋升你的决定才会服众。
还有就是你的工作成绩要有 Visibility可见性被大家知道否则你的老板一说起要晋升你时其它同级别或上级老板会有疑问ta 都做过什么项目,我怎么一直不知道这个人呀?这就比较尴尬了。
- 公司业绩
公司的盈利情况,以及所在领域的大环境是否好,都决定了公司是否有更多的 budget 来支持员工的升职。如果利润微薄,那么晋升的概率就小;本满钵满,晋升的概率就大。
OK如果你能够从以上几个方面分析好自己当前的境遇判断出哪一个因素才是“四年没有得到晋升”的关键抓住重点就能够“解决”这个问题提高自己的晋升概率。
### 2.5.4 提高解决问题的能力
善于观察,勤于思考。
造成一个问题的因素可能会有很多,找出这些因素,并通过对比测试、实例分析等手段评估自己当前所遇到的问题的根本原因是什么。
比如有人问你:为什么井盖一般都是圆的?你可能会嗤之以鼻地反问到:那为什么房子都造成方形的呢?因为你可能会下意识地认为那就是一种习惯或者约定俗成而已,为什么非得要刨根问底儿呢?
其实每一种自然现象都有背后的科学道理,每一种人文现象都有背后的社会学原理。井盖是圆形的,才不容易掉入井中;房子是方形的,设计和建造成本才会最低。
还有两个有趣的关于做饭的故事。
第一个是关于烙饼的故事:有一位中年妇女,很擅长烙饼,她每次把生面饼擀成圆形好,都要把生面饼的外缘切掉一厘米左右的宽度,在把缩小了直径的生面饼放入锅中。别人就问她:为什么要切掉一圈?她回答说:我母亲就是这么教我的。别人就去问她母亲,她母亲答道:因为以前家里的案板大但是锅小,所以要把生面饼切掉一圈再下锅烙。
还有一个关于煮饺子的故事:木头从小就爱吃饺子,妈妈总会在周末的时候包饺子吃。木头不会包饺子,就事先在锅里放上水,放在煤气炉子上煮水。在煮饺子的时候,木头在旁边等着饺子出锅。在煮到一半的时候,水总会成泡沫状溢出锅外,妈妈总会用一个大勺子盛满凉水兑入锅中,水就不会溢出了。这样反复三次,饺子就熟了,俗称“蜻蜓点水”。问妈妈为什么要点水?妈妈说以前姥姥就是这么教的。
木头觉得很奇怪,因为煤气开关明明可以关小,就可以不让水溢出。到了一定的年龄后,木头才想通:以前都是用煤球烧火,没有开关,产生的热量是无法控制的,所以只能用点水的办法控制锅内水温。之所以要点三次水,是因为祖辈的历史经验积累,点三次水后的时间大概就是 5 分钟左右,饺子刚好可以熟透。
点水据说还有其它的好处:
- 煮了一段时间后,水变成蒸汽,锅内的水量减少,面粉会导致粘稠,加水后会稀释;
- 水温保持太高的话,面皮会发软粘连,容易破皮;
- 面皮容易熟,里面的馅儿不容易熟,加凉水可以降低水温,同时保持馅儿的温度持续。
但其实上面这些好处都是可以通过用“关小火 + 盖锅盖”的方式来实现的。
有句话叫做“知其然知其所以然”,就是要告诉大家遇事要多思考,找出表面现象后面的根本原因,才能融会贯通,遇到问题才会有解决的方法。

Просмотреть файл

@ -1,33 +0,0 @@
# 软件工程师在微软
木头:
1. 最开始tester即可
2. 最底层的dev即可
3. 我想promote
4. 我可以当lead
5. 我可以当archtect
名词解释:
ICIndividual Contributer个人贡献者表示不管理任何人的工程师因为有些人即使到了技术专家级别也不想走管理路线。
Lead小领导管理几个 IC这些 IC 的级别通常都比该 Lead 要低或者同级。
Manager大领导管理 IC 和 Lead。
|段位|职位|中文解释|角色|职责|
|---|---|---|---|---|
|2|Software Engineer|初级软件工程师|小组成员 IC|具备入门知识,缺乏经验|
|3|Software Engineer 2|中级软件工程师|小组成员 IC|独立工作,有团队合作经验|
|4|Senior SE|高级软件工程师|小组成员 IC 或领导 Lead|带领5名左右的 IC完成小项目|
|5/6|Principal SE|首席软件工程师|团队成员或技术领导|带领10名以上员工项目决策|
|7|Partner SE|搭档级软件工程师|部门技术领导|带领100名以上员工方向决策|
|8|Distinguished Engineer|领域技术专家|组织技术领导|领域决策,可能会影响到一个知识领域,比如 Search、Ads 等|
|9|Technical Fellow|技术大牛|公司技术领导|战略决策,可能会影响到业界|
在以前的版本中,软件工程师的英文名称为 Software Development软件开发后来改成了 Software Engineer软件工程师笔者猜测其原因如下
1. 在微软取消了测试职位后软件工程师也要负责测试所以不能再叫做Development 了,但是又不能叫做 Software Development-Test所以就叫做 Software Engineer
2. Software Development 只负责开发Software Engineer 在字面上加入了工程的含义,即对软件开发者有更高的要求,需要熟悉产品周期各个环节的所有职责。

Просмотреть файл

@ -0,0 +1,296 @@
## 2.6 系统化思维能力
### 2.6.1 思维与系统化思维
#### 三个名词
先理清三个名词思考Think思维Thinking思想Thought
<center>
<img src="Images/Slide13.JPG"/>
图 2.6.1 三个名词的关系
</center>
思考,是一个动词,是人的大脑的一种能力和行为,就是程序员们常说的“等会儿!让我想想!脑子有点儿乱,这是我写的代码吗?看上去很乱......嗯,这个聚类算法并不适合于做有标签的数据分类学习,对吧?”
思维,是一个名词,是思考的方法和角度,也可以说是一种习惯。思维的生理基础是大脑,但是相同的生理基础可以产生不同的思维方式,这是人脑最神奇的地方。思维有很多种分类,下面具体解释。
思想,是一个名词,是思考的结果,它会形成两个层次的意识:认知、判断。“认知”是被动的,帮助人们认识世界;“判断”是主动的,帮助人们改造世界。成体系的认知叫做“知识”,伟大的认知一般才会称为“思想”,比如“毛主席是军事家、思想家、革命家”;渺小的判断叫做“主意”,大一些的叫做“决策”,商业化后叫做“解决方案”。
#### 思维方式
以下是一些互相对立的思维方式或习惯。
- 逻辑思维与逆向思维
- 逻辑思维注重因果关系无论对错。“因为你写了这段代码所以才会产生bug”这是一种不正确的推论。“因为你忘记给变量初始化为 0所以才会产生bug”这种说法听上去就合理多了。
- 逆向思维把原因和结果倒置或者是颠倒主次关系,比如司马光砸瓮(不是缸),在其他小朋友都在想办法“让人脱离水”的时候,司马光选择了“让水脱离人”,事后如何赔偿水缸就不用担心了,因为上官尚光家会赔钱。
- 线性思维与发散思维
- 程序员大部门是线性思维的,逢山开路,遇水搭桥,因为要解决问题,所以总会找最简单最直接的解决方法。
- 发散思维是人脑天生的思维方式,测试人员大部分是这种思维方式,因为要制造问题,想办法用各种测试用例让代码暴露问题。
- 结构化思维与碎片化思维
- 结构化思维,面对复杂问题时,通过刨析其内部结构,找到引发问题的关键部分,制定有相对性的解决方案。庖丁解牛就是典型的结构化思维,他看到的不是牛,而是肌肉、筋络、骨骼结构。结构化思维有助于建设知识体系。
比如遇到一个程序的性能问题,一个有经验的程序员应该从代码逻辑、硬件环境、运行环境、数据存取、网络吞吐、磁盘读写等多个方面进行分析思考。
- 碎片化思维是指人们在长期接受各类不完整、缺乏逻辑性、支离破碎信息的过程中逐渐形成的一种表面化、片面化、简单化和情绪化的思维模式。现在人们从手机接受各种碎片化信息,这些信息的发布者大多具有商业目的,或欺骗利诱,或哗众取宠,不利于知识体系的建立。
程序员们最喜爱的一个网站,恐怕就是 stackoverflow 了,因为里面有很多前人的经验总结,主要是针对某个具体问题的。如果需要解决一些比较直接的问题,比如一个 API 调用失败,或者一个 Python 包安装不上,通常用这种碎片化的信息就可以解决。
如果想解决一些复杂问题,比如“深度神经网络如何调参”,那么 stackoverflow 通常不会给予直接的答案,即使有的话,也可能不是你想要的答案。
- 平行思维与对抗思维
- 平行思维要求聆听、理解他人的观点,赞扬与自己相同的观点,然后提出与自己不同的观点与对方讨论合作的可能性。
比如,木头说:“这个系统现在运行得太慢了,我认为需要用 C++ 重写关键组件以提高性能"。石头说:”我非常同意要提高性能,咱们英雄所见略同(平行思维的典型特征),由于用 C++ 重写需要一个月时间并且有风险,咱们是不是可以先用更多的机器来分担负载,测试一周后再决定是不是要重写?” 木头欣然同意。
- 辩论大赛的双方通常采用对抗思维,只要是对方说的论点就是不对的,想办法用一些反例来证明对方的错误,进而证明自己是对的。又可以叫做垂直思维。
在平行思维的例子中,如果石头用对抗思维说:“用 C++ 重写需要一个月时间并且有巨大风险,所以根本不可行。用更多的机器来分担负载是一种最佳的解决方案。” 木头听到后肯定还要和对方争论,最后不欢而散。
当年的“3Q大战”就是对抗思维最后的结果是两败俱伤。
- 零和思维与双赢思维
- 零和思维(Win-Loss是博弈论中的一个概念博弈的双方一方得益另一方必然吃亏此消彼长二者相加总是为零。赌场中的庄家与赌客之间就是零和思维。在一个软件生态环境中的各路竞争对手也是零和思维。
- 双赢思维Win-Win是双方合作的基础在保持各自底线的前提下把竞争变成合作获得各自的利益。微软放弃自己的 IE 浏览器,转而采用 Chromium 内核做 Edge 浏览器是一种单方面的双赢思维Google 对此当然是欢迎的,而微软得到的好处是站在巨人的肩膀上让自己看得更远。
#### 从解决简单问题到系统化思维
先回忆一下 2.5 节中提到的“解决问题的能力”的主要内容。
通常来说,一个问题会包含有多个因素,这些因素都是**并列关系**,在拓扑结构上,如图 2.6.2 的**星形结构**。在某个特定条件下,只有一个因素会引发这个问题的出现。发现这个引发因素并加以解决,就是“解决问题的能力”体现。
而本节中的“系统化思维能力”所面对的问题,不仅仅是简单的一对一的因果关系,更多的是如图 2.6.2 中另外四种形式所示的复杂关系。所以,解决(简单)问题的能力也可以看作是一种简单的系统化思维能力。
<center>
<img src="Images/Slide14.JPG"/>
图 2.6.2 系统化思维的几种形式
</center>
**这些思维形式的形成,是因为系统(问题)本身就是这种结构**,而并非无中生有。“读万卷书行万里路”是形成系统化思维的必经之路,简单说就是见多识广。
### 2.6.2 串行化系统与思维形式
解决单个问题,只需要线性思维就可以了,就是所谓“头疼医头脚疼医脚”。如果问题的依赖链条较长,就需要串行化的“系统思维能力”了,这是系统化思维中最简单的形式。
木头在做面试官时,首先会考察算法和编码能力,如果到后面还有时间的话,总会问下面这个问题:**北京城区需要有多少个加油站,可以达到加油排队时间不超过 10 分钟的服务质量?**
要解决这个问题需要一系列的假设数据支持,有些数据是可以从网上查到的,有些是常识,剩下的就需要靠推理了。
<center>
<img src="Images/Slide15.JPG"/>
图 2.6.3 北京有多少个加油站
</center>
正确的解题思路如下:
#### 1. 北京有多少人口?
可以从官方的数据上得知,北京的常驻人口为 2100 万。
#### 2. 汽车保有量
从网上可以查到官方的统计数据,但是也可以这样推算:
1. 可以从官方的数据上得知,北京的常驻人口为 2100 万。
2. 假设是三个人组成一个家庭,那么一共有 700 万个家庭。假设每个家庭拥有一辆汽车,则一共有 700 万辆汽车。
3. 但是由于以下因素,会造成这一数字的不准确:
- 摇号限制购买的措施,这个数字会减少
- 很多年轻人还没有成家,但也可能有私家车,这个数字会增加
- 北京还有很多企事业单位用车
- 北京的出租车大概有 6 万辆
4. 上面几个因素有增有减,所以最后的汽车保有量大概就是 700 万辆左右。
#### 3. 出车率
不是每个有车的人都要开车,并且距离上班比较近的人不会开车上下班,还有限号措施(我们忽略电动汽车,因为它数量较少而且不需要加油)。据在木头居住的小区内观察,每天的出车率比疫情之前减少很多,大概只有 30%。这说明大家都有了节能环保绿色出行的理念,当然,也有可能是单位附近的停车费太高。
那么每天在马路上跑的车大概有 700 万辆 x 30% = 210 万辆
#### 4. 每天行驶多少公里?
北京的面积是 1.6 万平方公里,近似圆形,那么根据 $S=\pi r^2$ 公式,得到 $r=71$ 公里。由于此问题是估算北京城区的加油站数量,所以减去 2/3可以认为半径为约 25 公里。也就是说人们上下班开车来回的平均距离为 25公里 x 2 = 50 公里左右。
#### 5. 一箱油可以跑几天?
- 大排量汽车越来越多SUV 已经成为了购车首选,所以导致车辆每百公里耗油平均为 8 升。
- 一般的汽车的油箱容积为 50 升按一周开5天4天工作日 + 1天休息日开车出门办事或旅游
50 升 / 8升 x 100公里 = 625 公里,这是一个理想的里程。在城市里开,实际上是 500 公里左右就需要加油。
那么一箱油可以跑500公里 / 50公里每天 = 10天
#### 6. 每天有多少车需要加油?
10天 / 5天每周 = 2周即大概每个月加两次油平均 15 天加一次油。
那么每天需要加油的车辆为 210万辆 / 15天 = 14 万辆。
#### 7. 加油站的服务质量
- 以 92 号汽油为例,加油站一般有至少 4 把油枪,可以同时加油,即可以同时服务 4 辆车。
- 每辆车的加油时长大概是 2 分钟。
- 我们要保证排队时间不超过 10 分钟,加上司机付费的时间,大概是每把油枪排 4 辆车。
那么每小时可以给 60分钟 / 10分钟 x 4把油枪 = 24 辆车加油,可以约等于 25 辆车。
全天 24 小时营业,但是司机一般会在早晚上下班的时段来加油,也就是峰值时段大约是 4 个小时。
每个加油站可以一天加油 25 辆车 x 4 小时峰值时段 = 100 辆车。
如果忽略了这个峰值时段,那么需要的加油站数量将会大大减少。就好比在双 11 购物节的时候,大量的用户涌入网站,需要 10000 台服务器才能处理,而在平时,只需要 1000 台服务器就可以搞定。
建设一个加油站要花很多钱,但是多加一把油枪不会花很多钱,所以可以用增加油枪数量的办法来缓冲峰值时段问题。
#### 8. 需要多少个加油站?
14 万辆 / 100 辆 = 1400 个加油站。
所以最后的答案就是 1400 个加油站。如果推算出 1000~1500 个,都是在合理范围内。
#### 小结
以上 8 个问题,每个问题都不是很难,难的是可以把它们串起来,最后解决一个系统性的问题,那么市政府对加油站建设的规划就可以按照这个推断来进行。数量少的话,车主加油会等较长的时间;数量多的话,加油站的利用率较低,经济上不划算。
这种思维形式的难点是:
- 不容易找到合理的上游依赖问题,很容易发散。
- 不能正确地解决每个依赖的问题,差之毫厘谬之千里。
- 堆栈较深(依赖链条长)时,没有信心/耐心继续下去。
既然有串行化的思维形式,那么有没有并行化的思维形式呢?有!但是其具体表现形式是下面要介绍的金字塔状思维形式和网状思维形式。
### 2.6.3 金字塔状系统与思维形式
<center>
<img src="Images/Slide16.JPG"/>
图 2.6.4 如何举办一场音乐会
</center>
#### 0. 总体目标
木头有一个大乐队50 多人,都是微软员工,来自各个部门,木头是这支乐队的队长。平时排练,但是没有什么合适的演出机会,于是,木头想自己举办一个音乐会。
怎么开始筹备呢?
#### 1. 要素分解
俗话说:没吃过猪肉还没见过猪跑吗?木头虽然没有举办音乐会的经验,但是看了那么多音乐会,也大概其知道组成一场音乐会的要素有三:
- 表演部分
- 观众部分
- 场地部分
#### 2. 解决方案
- 表演部分
需要有演员(歌手乐手)和曲目(歌曲乐曲)。
人的因素是首位的,歌手乐手都有了,而且可以复用,即一个歌手可以唱多首歌,一个乐手可以在多首曲目中伴奏。
曲目也有很多可以选择的,国内的流行歌曲环境非常的好,另外乐队内还有古典乐手和民乐手。
- 观众部分
需要让观众知道具体的时间和曲目,届时能够前来观赏。在公司内部有各种宣传手段,比如电子邮件、实时通信,最合适的形式就是“演出海报”。
由于是内部演出,就免去了票务部分。
- 场地部分
公司内部有两个大的会议室,可以容纳百人以上,作为演出场地非常合适。
但是平时排练也要有场地,好在公司相关领导很支持木头的计划,排练场地就放在大会议室里,一般都是晚上排练,不会影响白天开会。
#### 3. 执行计划
- 先把演员们分成多个小组,或者自由组合,每个小组负责一个曲目。
- 曲目选择好以后,要进行编曲,然后大家开始排练。
- 排练用的乐器有“吉他、贝斯、键盘、鼓”四大件儿,其它的一些特殊乐器,比如小提琴、口琴、笛子等等由乐手自己准备。
- 由于排练场地只有一个,不同的组排练都有不同的时间,事先安排好。
- 海报部分,乐队内恰巧有 designer设计师当仁不让地负责设计海报。
- 海报设计完成后,一是形成 16:9 的电子版在楼内的各个演示屏上滚动播出,二是可以拿出去印刷,便于张贴。当然还需要中英文两个版本。
- 舞台搭建在演出日期前一天完成,各个小组按演出顺序进行彩排。
- 音响在一场音乐会里至关重要,木头特地找领导购买了调音台、线材、功放、音箱等设备。
#### 小结
- 一个复杂的问题(目标)由多个要素组成,也就是子目标。
- 而完成这些子目标需要完成一个或几个任务。
- 每个小任务都需要在时间或空间上进行周密的部署与持续的努力才能达成。
- 从时间上看,以海报任务为例,设计、印刷、张贴、回收,是前后阶段顺序,每个阶段都需要有人、有场地、有方法、有工具才能完成。排练场地本身是一个空间,但是也需要时间的计划才能安排好很多组的排练。
- 从空间上看,以场地为例,舞台搭建的位置、音响布置的覆盖面、调音台中控的地点、有长度限制的线材的连接方式、观众的座椅位置、录音录像师的机位、乐器在舞台上的摆放位置、麦克风的排列形式,等等等等,都是需要联动考虑的问题。
### 2.6.4 环状系统与思维形式
<center>
<img src="Images/Slide17.JPG"/>
图 2.6.5 环状系统的负反馈
</center>
先看一种比较简单常见的环状反馈系统。其原始系统如图 2.6.5 左上所示,只有“输入、信号处理、输出”三个环节。
但是经常有这种需求:当输出端电平太高时,需要输入端智能地调低电平,所以就在输出端增加一个采样点,把输出信号反馈给输入端,并在结合点上与输入信号电平相减(因为是负反馈),这样在输出端最终得到的就是一个相对平稳的信号。
在思考时,通常人们会认为自己的思想无比正确,主意无比美妙。但是当你说出你的主意并得到一些 feedback反馈会纠正你的一些错误想法把这些信息加入到输入端重新思考才会得到大家都认可的好主意。
还有一种比较复杂的形式,一个输入通常是经过了很多个环节处理,最后反馈回来,对最初的输入做矫正。如图 2.6.5 的右图所示的一个例子:
1. 用户:“现在的系统运行有些慢,希望能够提升一下整体效率。”
2. 需求人员:“好的,我们马上和开发部门商量。”
需求人员:“攻城狮们,咱们如何才能提升一下系统的整体效率,以便让用户高兴?”
3. 开发人员:“想整体提升的话,需要提升每一个组件的效率,要花很长时间。不如我们先提升一下关键组件的效率吧?但是这也会花几周的时间,会造成其它任务的延迟,用户不会高兴的。”
4. 开发人员:“另外,如果重写关键组件,会用空间换时间,运行快,但是占内存较大,希望最终产品的工程部署人员要注意。”
5. 产品人员:“果真如此的话,那么以前运行关键组件所在机器上的其它组件就必须部署到其它地方运行。我们需要用户另外购买几台设备来运行其它组件,这样用户不会高兴的。”
6. 产品人员:“需求大哥,你们再和用户确认一下吧?”
本来是一个好的出发点,但是经过一个环状的系统走下来,反而事与愿违。用户在得到一个好处(运行性能提高)的同时,可能会等待更长的时间获得新功能,或者花更多的钱来购买设备。
### 2.6.5 网状系统与思维形式
<center>
<img src="Images/Slide18.JPG"/>
图 2.6.6
</center>
网状系统(或者说是表格形式)思维是最复杂的一种形式,因为它涉及到了横向思维和纵向思维两个方向,甚至是多维的。而其它的思维形式都是单向的。
<center>
<img src="Images/Slide19.JPG"/>
图 2.6.6 环状系统的负反馈
</center>
在软件开发过程中,如图 2.6.6 所示,从纵向看,通常是多个小组分别完成各自的子系统,每个子系统又包含一系列的模块。从横向看,不同子系统的模块组成一个端到端的功能。项目管理者在纵向上需要协调各个小组的进度,这样才能在横向上把不同子系统的功能串联起来,尽早得到一个可运行的系统。
在机器学习中,通常有很多参数需要调节,比如深度神经网络中的步长值、样本批大小、卷积核的尺寸、正则化的参数等等,这些参数互相影响,网格状的参数搜索可以帮助我们找到最佳的参数组合。
<center>
<img src="Images/Slide20.JPG"/>
图 2.6.7 质量指标之间的竞争
</center>
在软件质量控制过程中,如图 2.6.7 所示,需要软件可以达到很多种质量指标,如:正确性、可靠性、易用性、高效率、可维护性、可移植性等等。但是其中的某些指标是相克的,一个指标的提升会带来另外一个指标的下降。
在足球场上,教练会强调两点:一是全队三条线在横向上的步调一致,一起压上进攻,或者一起收缩防守;二是在纵向上,同一边的后卫、中场球员、前锋要协调进攻。在排球场上,六个队员形成 2x3 的网格阵型,兼顾进攻与防守。
以上这些都是网状思维的典型应用场景。

Просмотреть файл

@ -0,0 +1,250 @@
## 2.7 学习能力
### 2.7.1 元能力
**学习能力**是一项元能力。元能力,就是提升其它能力的能力。而学习能力所学习的目标是**知识**和**技能**。
$$
能力 = 知识 + 技能
$$
<center>
<img src="Images/Slide21.JPG"/>
图 2.7.1 质量指标之间的竞争
</center>
**知识**是人类从各个途径中获得的,经过提升总结与凝练的对客观世界的系统认识。包括“事实知识、概念知识、过程知识、元认知知识”,在 2.7.3 小节中讲解。
**技能**是在获得相关知识的基础上,通过一定练习或实践而获得的某一领域的经验与技巧。包括“硬技能(技巧)、软技能(经验)”。“弹吉他”是一种硬技能,不一定要懂得乐理知识;“编曲”则是一种软技能,需要懂得乐理知识。
**能力**是运用相关知识和技能(组合)解决特定领域的问题的条件和素质。“条件”是“知识”的代名词,“素质”是“技能”的代名词。
在大学中,除了一些必要的基础知识是用来帮助你建立对客观世界的基本认知的,而其它一些课程呢,你会发现在工作以后基本用不上,那为什么要学习它们呢?
- 学习文学可以让你以人为本,口吐珠玑;
- 学习史学可以让你以史为鉴,与时俱进;
- 学习哲学可以让你辩证唯物,修身养性;
- 学习物理可以让你客观理性,触类旁通;
- 学习化学可以让你视微知着,达变通机;
- 学习数学可以让你胸有成算,数往知来;
......
除了数学是一切理工科的基础外,其它学科的学习除了帮助学生建立基本的世界观以外,还在帮助你“学习”一种“学习的能力”。而同样都是大学毕业的人,参加工作后的职业发展道路有好有坏,也是因为“学习能力”在工作后一直在起作用。
### 2.7.2 Bloom's Taxonomy - 布鲁姆分类
1956年美国的教育心理学家 Benjamin Bloom 本杰明·布鲁姆发现,美国学校的测试题 95% 以上是在考学生的记忆力,就相当于文科中的历史、政治、法律,这些东西都是已经发生的事件或者已经规定好的条条框框,没有任何商量的余地。
但是很多理工科的专业的学生或者毕业后从事科学研究的人他们需要的不是记忆力而是创造力而那些测试题是阻碍这些人受到良好教育的绊脚石。于是布鲁姆提出了一个新的学习教育分类法用于归类不同学习程度的认知框架。2000 年,他的学生 Lorin Anderson 修订过一些内容,这就是影响了两代美国人的“认知目标分类体系”。
<center>
<img src="Images/Slide22.JPG"/>
图 2.7.2 质量指标之间的竞争
</center>
布鲁姆是根据人的“认知过程从简单到复杂,由具体到抽象”这一规律来作为其教育目标分类理论依据的。教育目标分类强调指导教学过程和对结果进行评价,用来判断学生对一个知识点的精通程度,从低层次的“记忆、理解、应用”,到高层次的“分析、评价、创造”。
#### 第1层 - 记忆Remember
死记硬背,照本宣科。
学习的对象包括:特定名词、术语、事实、趋势、顺序、分类、标准、方法、原理、概念,理论和结构等。比如:乘法口诀、法律条文、历史知识等等。
其表现是:可以通过回忆、有线索地查询,回答或记住事实、术语、基本概念或答案,而不必理解其含义。
对于一个软件工程师来说,就是要熟悉软件开发和工程中的各种名词定义、语法、函数、工具等等。
例子:
- 什么是面向对象的继承和多态?
- 按优先级列出算数运算符。求一个数的平方的对数值的表达式如何写?
- Python 中的 slice 切片如何使用?
- 如果想对一个数组进行排序,都有哪些排序算法?哪个最快?哪个最慢?
- Azure 里有哪些存储技术可以使用?
这也是一个刚毕业的学生需要达到的基本技能。
#### 第2层 - 理解Understand
分星擘两,融会贯通。
对事物的领会,但不要求深刻的领会,而可以是初步的,可能是肤浅的。其表现是通过翻译、解释、推断、组织、描述信息来表达自己的主要观点,还可以通过分类、总结、推论、比较等方法来解释概念。
对于一个软件工程师来说Ctrl+C/Ctrl+VCopy/Paste拷贝粘贴大法是最喜欢做的事它即节省了自己的时间又增加了代码量哈哈但是笔者提醒大家要注意职业道德Copy/Paste 以后,起码要改一改变量名称再提交,哈哈哈!
但是总会有很多情况不尽人意,比如:
- 有一段用 Java 实现的算法,从描述和输入输出看,正好是自己想要的功能,不巧的是自己的项目用 Python 语言写代码,需要把 Java 代码用 Python 重写。
- 好不容易找到了一个 Python 语言实现的算法,但是它的输出只有对样本的分类结果,而自己的项目要求不仅要输出分类结果,还要改代码输出具体的分数值。比如分数值为 [0.23, 0.18, 0.59] 时输出分类结果为 [0, 0, 1]。
- 稍微花了些力气终于加上了输出分类的分数值,但是发现在测试时不同的样本时有 bug 出现,明明是属于第一类的样本却被分成了第三类。所以还需要硬着头皮 debug。
- 一切都搞定了,领导很满意,要求你给大家讲一下这段精妙的算法设计原理。
- 经过性能测试发现这个算法被调用了很多次,但是已经无法继续优化额。为了提高系统性能,领导决定要把这个算法写成 C++ 的组件,供 Python 代码调用,于是你又把 C++ 的一些基本概念复习了一下,对照着 Python 实现又写了一遍。
以上这些都是软件工程师的日常行为,它要求必须理解算法、代码、结构、系统、协议等等,才能正确地工作。在可以自己独立写代码之前,先学习一下别人的代码,深刻理解。
#### 第3层 - 应用Apply
举一反三,熟能生巧。
对所学习的概念、法则、原理的运用。它要求在没有说明问题解决模式的情况下,学会正确地把抽象概念运用于适当的情况。这里所说的应用是初步的直接应用,而不是全面地、通过分析、综合地运用知识,使用知识来解决问题,确定事物之间的联系以及它们在新情况下的应用方式。
一个刚刚结束了照猫画虎写代码的软件工程师,正洋洋得意地觉得自己已经理解了软件开发的真谛,突然又遇到了下面的问题:
- 刚刚学习的数据库设计的三范式,能够应用到这个企业信息管理的项目中吗?
- Azure 中有很多存储服务,比如 Disk、Shared File、Blob、Data Lake 等等,哪一个最适合这个企业信息管理项目呢?
- 上个月学习了 Web 开发的三个常用框架,也做了简单的比较测试,但是当前的项目要求能支持 500 个用户的并发访问,哪一个框架更适合呢?
- 上次写的 C++ 的组件可以在这个项目中重用了,简直是太好了,节省了很多时间。但是上次使用 Python 调用,有现成的例子,这次要用 Java 调用,据说是可以的,但是还没有试过,不知道两种语言之间的交互性能会不会出问题。
学到的一些技术,也通过简单的测试程序理解了其工作原理,但是能够正确地应用到实际项目中,还是需要一些努力的。这是软件工程师能够独立工作的前提。
#### 第4层 - 分析Analyze
抽丝剥茧,阐幽显微。
把知识分解成各个组成要素,从而使各概念间的相互关系更加明确,组织结构更为清晰,详细地阐明基础理论和基本原理。包括:元素分析、关系分析、组织分析。
一名有经验的软件工程师经常要给新手以必要的帮助和指导。有经验的 mentor 会这样做:
- 现在咱们做的这个模块要求性能好、速度快,因为它是个核心模块,从系统架构图中,你们应该也能看到它所处的地位。
- 核心算法如果用哈希表支持查询的话,当然是速度很快了,可以快速搭建原型,演奏 e2e 的效果。
- 但是,在实际应用中,由于数据量很大,哈希表占用的内存空间太大,对机器的内存配置要求较高,所以我们不能使用这种技术。
- 所以,我们要是用 Trie-Tree 来完成信息检索部分的核心算法,它实际上就是一个多层哈希表,但是索引数量会成几何级别减少。
- 在 C++ 中没有这种数据结构的实现,所以需要我们参考一下其它语言的实现,用一个小的数据集在 Java 的实现上做一个 Debug就很容易理解了。
- 你们两个人分工,一个人找 Java 实现,另一个人准备数据集,然后一起 Debug。搞清楚原理后一个人写代码另一个写测试用例。争取一周内搞定这件事。
听完以上的一系列分析后,新手们会得到 What, Why, How, When 等重要信息,从而可以立刻开始工作,而那位有经验的 mentor 就可以去喝杯便宜的速溶咖啡并和朋友聊聊股票行情了,毕竟他被套牢了一大笔资金,如果被老婆发现可是不得了。
#### 第5层 - 评价Evaluation
拨云见日,玉尺量才。
要求能够通过倾听、观察、思考、判断等过程给出自己的意见评判一件工作、一条信息、一个想法的价值和质量不是凭借直观的感受而是理性的、深刻的、对事物本质作出有说服力的判断其背后有一定的原则criteria和标准standards
通常是在某个领域内有多年经验的从业者才可以做到这一点,并且有自信可以发表类似如下的评价意见:
- 通过对甲和乙两名毕业生的面试,我发现:甲是研究生学历,计算机基础知识扎实,参与的校外公司的实习项目较多,沟通顺畅;乙是博士生学历,偏理论方向,在校时间较长,大多数时间是跟着导师做项目,自我意识较强。如果咱们想物色一名可以很快融入团队并做出贡献的人,那么甲比较合适。
- 如果比较阿里云与微软云的话:前者比较接地气,后期服务好,在国内数据中心多,而且便宜;微软云技术先进可靠,安全性很高,同类服务的可选择性多,但是比较贵。如果我们想扬帆出海面向全球用户的话,建议使用微软云。
- 这个架构设计方案比较全面,在可用性、可靠性、可维护性、安全性等几个方面都有考虑,并且使用可一些成熟可靠的框架,性能和可扩展性上也应该不是问题,只是在一些细节上还需要后续做具体实现之前,再和相关的小组讨论并完成详细设计。
- 这篇论文提出的想法比较清奇,剑走偏锋,避开了一些公知的难点,从另一个角度通过增加多层误差直传的方法来解决循环神经网络训练不容易收敛的问题,并且试验方法、数据、结论都比较可信,相比较其它的那些在一个小单元内做修修补补的论文不可同日而语,可以说是为后续的研究工作打开了思路。
作为一名新手,当领导征询你对一件事情的意见时,最好不要给出一些片面的、主观的评论,而应该用一种谦虚的态度在局部上补充一些细节。
#### 第6层 - 创造Create
独具匠心,别开生面。
是以分析为基础,全面加工已分解的各要素,并再次把它们按要求重新地组合成整体,以便综合地创造性地解决问题。它涉及具有特色的表达,制定合理的计划和可实施的步骤,根据基本材料推出某种规律等活动。它强调特性与首创性,是高层次的要求。
提示: 组成,建立,设计,开发,计划,支持,系统化
指想到解决问题的新方法,把大的问题化成小的块,用一种新的组合方式把算法结合起来
- 写一篇关于解决物流效率的问题的强化学习方面的论文。
- 设计一个系统来满足在特定时刻对网站的大规模突发访问,比如双十一购物节。
- 制定一个新的工作手册,来适应疫情期间很多员工需要在家远程办公的情况。
- 把已有的一些传统的机器学习算法创新性地用于脏数据处理,然后再在干净的数据上做回归或分类的学习,将会得到更好的混淆矩阵评估值。
- 在已有的缓冲区的基础上,再设计一个二级缓冲区,可以容纳更多的数据,会成倍地提高查询的效率,但只付出很小的设备代价。
### 2.7.3 Knowledge Dimensions - 知识的难度分类
<center>
<img src="Images/Slide23.JPG"/>
图 2.7.3 质量指标之间的竞争
</center>
#### 1. Factual Knowledge 事实知识
与知识领域相关的基本要素,解决简单问题所必须的知识。
包括:
- 术语知识
- 具体细节和组成元素
任何领域中的术语、具体细节和基本元素都可以定义为事实知识。比如软件和软件工程领域中的编程语言的语法知识、关于算法的名词知识KNN、DNN、RNN、CNN......)、关于测试的各种名词知识(单元测试、集成测试......)等等。
是一种非常具像的知识形式,属于纯静态的问题。如图 2.9.1 中的子图 1有 A、B、C 三个看上去独立的点,表示三个基本的事实知识。
学习这些静态知识最好的办法就是翻阅教科书,一般的书的作者都会比较严谨,在书中列出的名词及其解释会比较的准确和全面。然后再从其它渠道(比如互联网)获得更多的关于这些名词的实际应用的介绍,而不是机械地背诵它们的定义。
常用的信息当然要熟记于心,但是人类的大脑记忆不是存储事实知识的最佳场所。虽然我们经常见到律师可以博闻强记地准确背诵法律条文,算命先生也可以把六十四卦记得滚瓜烂熟,但请记住这是他们赖以生存的饭碗,他们必须记住。
我们可以用一些更聪明的现代化的办法:首先要在脑海中留存这些知识的名词(名字),然后在需要这些知识的细节时,知道在哪里获取,比如查找书籍、在线资料、笔记等等,或者干脆问你身边的专家(如果专家不嫌你烦的话)。
#### 2. Conceptual Knowledge 概念知识
描述一个大的结构内的各基本要素之间的相互关系,这种关系可以使得这些要素有机地结合起来展现一些功能。
包括:
- 分组和类别
- 原理和概括
- 理论、模型和结构
如图 2.9.1 中的子图 2A、B、C 三个点扩展了其外延,使得本来独立的点连接到了一起,形成一、两个概念知识。
概念知识与低级别的事实知识相关,可以理解为是一种上下文相关的知识,而不是局限在某一个点(事实)上。比如我们知道了软件测试包括单元测试和集成测试这个事实(包含两个元素),但是还应该进一步知道它们分别侧重于哪个方面,区别和联系是什么。再比如算法名词,应该知道 KNN 是一种聚类算法,而 DNN、RNN、CNN 其实是深度神经网络中的一些名词,把它们结合起来可以形成强大的深度学习模型。
#### 3. Procedural Knowledge 过程知识
偏向具体动作及过程的知识。如何做一件事,具体的实施方法,以及如何使用技能、算法和技术、方法的准则。
包括:
- 专业技能skill和算法algorithm
- 专业技术technique和方法method
- 了解何时使用最合适的过程
注意技能skill指的是人所具有的能力不是所有人都有技术technique指的是客观存在的东西任何人都可以使用。具有这些知识并能够灵活运用是这个级别所要求的。比如如何实施单元测试设计测试用例来覆盖各种情况是一种技能而使用开发工具提供的单元测试的接口就是一种技术。
如图 2.9.1 中的子图 3本来是静态联系在 A 旋转起来后,发现 B、C 也跟着旋转起来,形成了齿轮组。同理,旋转 B 或 C 时,另外两个齿轮也跟着旋转。
#### 4. Meta-cognitive Knowledge 元认知知识
自我认知的意识和知识。
包括:
- 全局或战略知识
- 关于认知任务的知识,即上下文和条件
- 自知之明
这个级别要求我们在充分掌握前面所说的三个层次的知识的前提下,你应该有自知之明,了解自己掌握了多少编程语言的知识、机器学习的知识、测试的知识,你在某个项目中还需要哪些技能才能胜任领导给予你的角色,你距离可以做一次讲座或者指导他人工作的能力还有多远。如果有差距的话,你可以准确地估计出还需要多长时间在哪个方向上的努力才能够达标。
如图 2.9.1 中的子图 4在了解了前三层知识后应该可以从它们“一生二、二生三、三生万物”地分解、模仿、理解、解释、甚至创造出一个齿轮箱。
### 2.7.4 当层次遇上难度
2.7.2 和 2.7.3 是两个不同维度的的分类或分层,那么当这两个维度组合在一起,会发生什么事情呢?
<center>
<img src="Images/Slide24.JPG"/>
图 2.7.4 质量指标之间的竞争
</center>
||事实知识|概念知识|过程知识|元认知知识|
|-|-|-|-|-|
|**记忆**|列出著名流行乐队<br>的名字|听歌识别出它的<br>作者|回忆如何给<br>电吉他调音|鉴别某种古筝<br>演奏学习方法的缺点|
|**理解**|总结一下Windows11<br>的主要功能|按用途对各种<br>算法进行分类|详解组装要领|解释软件工程<br>方法的原理|
|**应用**|回答用户在使用<br>Azure存储服务时<br>经常被问到的问题|给新入职的<br>程序员提供建议|对水的样本<br>做酸碱度测试|使用与个人实力<br>相匹配的编程技巧|
|**分析**|根据难易程度给各种<br>分类算法做一个排序|区分优质代码<br>和劣质代码|把敏捷开发模式<br>集成到工作流中|消除对传统机器<br>学习的偏见|
|**评价**|评价一篇论文<br>的优缺点|确定各种采样<br>结果之间的相关性|判断各种采样<br>技术的效果|给一种负载分配<br>策略评分|
|**创造**|写一篇短文介绍刚刚<br>学习的神经网络知识|设计一个分类<br>体系把各种用户<br>需求区别开来|设计出有效的<br>项目工作流|提出学习深度学习<br>理论的方式|

Просмотреть файл

@ -0,0 +1,257 @@
## 2.7 软件工程师在微软
### 2.7.1 定义、级别、职务
#### 定义 Definition
微软是这样定位软件工程师的:
*Software Engineers take end-to-end ownership for development and quality of products and services that delight customers and add strategic value to Microsoft. They evaluate requirements, estimate costs, and design and implement products and services. They define and implement the quality criteria for their products and services, using measurements and insights to understand and validate the quality of experience for customers. They manage and improve the engineering process, manage risks, dependencies and compromises, and integrate software into broader ecosystems and/or products and services.*
软件工程师负责端到端的产品和服务的开发和质量以让客户满意并为Microsoft增加战略价值。
- 评估需求,估算成本,设计和实施产品和服务。
- 他们定义并实施产品和服务的质量标准,通过有效的衡量手段和细致入微的观察来理解和验证用户体验的质量。
- 管理和改进工程过程,管理风险,捋顺依赖链条,必要时采取折中方案,将软件集成到更广泛的生态系统和/或产品和服务中。
#### 阶段 Stage
微软的软件工程师的级别划分如图 2.7.1 所示。
<center>
<img src="Images/Slide16.JPG"/>
图 2.7.1 微软软件工程师的级别
</center>
从阶段看,一共有 9 个,所以后面会简称为“几段”(类似围棋选手级别名词)。在有些区域是有“一段”的,比如微软苏州,但是微软北京都是从“二段”开始。
在以前的官方定义中,软件工程师的英文名称为 Software Development软件开发后来改成了 Software Engineer软件工程师笔者猜测其原因如下
1. 在微软取消了测试职位后软件工程师也要负责测试所以不能再叫做Development 了,但是又不能叫做 Software Development-Test所以就叫做 Software Engineer
2. Software Development 只负责开发Software Engineer 在字面上加入了工程的含义,即对软件开发者有更高的要求,需要熟悉产品周期各个环节的所有职责。
还有一个级别Level的概念比如
- 在二段中有 5960 两个级别;
- 在三段中有 61、62 两个级别;
- 在四段中有 63、64 两个级别;
- 在五段中有 65、66、67 三个级别;
......
#### 角色 Discipline
Discipline 原意是知识领域,可以引申为角色。前面也说过,在微软与软件开发有关的有以下几种主要的角色:
- SE/SDE 软件工程师
- PMProgram Manager项目经理
- Designer 设计师
#### 职务 Role
有三种职务:
- ICIndividual Contributer个人贡献者表示不管理任何人的工程师因为有些人即使到了技术专家级别也不想走管理路线。
- Lead小领导管理几个 IC这些 IC 的级别通常都比该 Lead 要低或者同级。
逐渐地,为了扁平化管理,微软渐渐取消了 Lead 职务,不在 Title 中明确标明,而是在小组内部任命一些 Technical Lead技术带头人这些 Lead 与 IC 之间不存在 Report 关系。
- Manager大领导管理 IC 和 Lead 并有 Report 关系,一般具有 Principle 段位。
### 2.7.2 主要工作考核指标
微软对软件工程师的主要考核有六个大项,如图 2.7.2 所示。
<center>
<img src="Images/Slide23.JPG"/>
图 2.7.2 微软工程师的考核指标
</center>
下面以“二段”为例,介绍一下每个大项中包含的具体内容。
#### Product and Service Design - 产品和服务设计
构建正确的产品和服务,为客户提供预期价值,并实现所需的业务目标。根据客户洞察力(数据驱动)和总体业务目标制定决策。
对于“二段”,有以下具体要求:
<center>
<img src="Images/Slide24.JPG"/>
图 2.7.3 产品和服务设计
</center>
从内容上看,这一项要求软件工程师具备 PM 的一些基本素质,这里的“设计”不是技术上的,而是功能上的。需要在横向上对产品和服务的有基本的了解,在纵向上对上下游合作者有基本的了解。
#### Technical Design and Implementation - 技术设计和实现
开发高质量的代码以满足技术要求例如可伸缩、全球交付、分布式、可监控、可维护性、可测试性、调试和维护。构建关联的测试以在单元级别和端到端级别验证代码。开发满足预期投资回报ROI的基础设施。使用技术软件开发技能来识别问题并提倡改进产品或服务设计。
对于“二段”,有以下具体要求:
<center>
<img src="Images/Slide25.JPG"/>
图 2.7.4 技术设计和实现
</center>
这一项要求首先有设计,哪怕是很简单的设计,或者口头交流,得到大家的认可之后,再继续进行开发工作。对于这个阶段的软件工程师而言,应该是使用已经定义好的接口进行局部功能的设计与开发,尽量通过看代码、查文档、向资深同事咨询等方式,来充分了解技术背景。
#### Quality Code and Validation - 代码质量和验证
创建并验证高效(低延迟、高吞吐量)、稳定、安全、可维护、可扩展、性能好、经过良好测试和可重用的代码,以实现产品或服务的客户和业务目标。构建正确的测试和工具,以验证代码是否符合质量目标或服务。分析数据并给出结论,使自己和同事能够在适当的时候理解和解决问题。确保在产品或服务的整个生命周期内保持质量。
对于“二段”,有以下具体要求:
<center>
<img src="Images/Slide26.JPG"/>
图 2.7.5 代码质量和验证
</center>
这一项强调了代码 review审查的重要性一方面要审查别人的代码另一方面让别人审查自己的代码主要是在代码风格、命名规范、与已有代码的契合程度等方面。另一方面代码逻辑的正确性需要由单元测试做保证也避免将来别人新加入的代码破坏你的代码逻辑。
#### Engineering Lifecycle - 工程生命周期
使用、定义和改进编码和测试实践、流程、工具、基础架构和标准以提高效率为Microsoft和客户提供预期的产品或服务成果。
对于“二段”,有以下具体要求:
<center>
<img src="Images/Slide27.JPG"/>
图 2.7.6 工程生命周期
</center>
这一项把代码和工程结合起来,相当于是软件工程的具体实践。
#### Effective Team - 高效的团队合作
在团队环境中积极行动,提高团队整体的效率、影响力和士气。跨团队、产品、服务或平台边界积极工作,以共享信息和技术,并确保同行团队目标一致。酌情组建团队。指导他人并主动寻求他人的指导。
对于“二段”,有以下具体要求:
<center>
<img src="Images/Slide28.JPG"/>
图 2.7.7 高效的团队合作
</center>
在团队环境中积极行动,提高团队整体的效率、影响力和士气。跨团队、产品/服务或平台边界积极工作,以共享信息和技术,并确保同行团队目标一致。
其中第三条是比较难做到的:当你意识到有些信息其它人不知道时(这一点本身就很难),需要主动地告知他人(一般人会选择等着别人来问)。
#### Product and Service Ownership - 对产品和服务的责任感
持续而有力地显示出对整个产品或服务的质量和完整性以及最终用户体验的责任感。保持一种自豪感和工艺感,使我们的产品具有美感和技术价值。
<center>
<img src="Images/Slide29.JPG"/>
图 2.7.8 对产品和服务的责任感
</center>
### 2.7.3 技术设计与实现的进阶
在 2.7.1 小节中描述的都是针对“二段”软件工程师的要求,
<center>
<img src="Images/Slide30.JPG"/>
图 2.7.9 技术设计与实现的进阶
</center>
#### SDE - 模块级别
要求:
- 开发解决各类问题的完整设计和测试接口,实现组件组之间的集成,提高重用性,并满足业务、客户、工程和运营需求。
- 在某些情况下,推动设计审查,定义代码模块之间的接口,并将现有技术应用于设计。
- 考虑可诊断性、可移植性/监控、可靠性和可维护性,并了解代码何时可以共享和交付。
- 应用编码模式和最佳实践。
- 利用来自客户和生产部门的数据和观察,为一些技术设计和实施决策做出贡献。
说明:
在 SDE 级别,主要是在软件的模块级别上工作,只为自己编写的局部功能负责。
#### SDE II - 组件级别
要求:
- 开发完整的设计和测试接口,以解决各类问题,实现组件组之间的集成,改进重用,并满足业务、客户、工程和运营需求。
- 推动所在项目的设计审查,定义代码模块之间的接口,并将现有技术应用于设计。
- 考虑可诊断性、可移植性/监控、可靠性和可维护性,并了解其代码何时可以共享和交付。
- 应用编码模式和最佳实践。
- 利用来自客户和生产部门的数据和观察,为一些技术设计和实施决策做出贡献。
说明:
在 SDE II 级别,主要是在软件的组件级别上工作,对所在的项目负有一定的责任。
#### Senior SDE - 功能级别
要求:
- 开发优雅的设计和测试,以识别和消除产品或服务范围内的问题,并与多版本产品或服务计划和功能区架构保持一致。
- 推动整个团队的设计审查,并应用可用技术的专业知识。
- 在团队中以身作则,生成简单、可扩展和可维护的代码,缺陷极少。查找并修复缺陷类别。
- 确保一致、可用、前瞻性、可维护的测试基础设施,从大量设计模式中汲取经验,是可用技术方面的专家,并且擅长识别有效的实践。
- 应用指标来提高代码的质量和稳定性。
- 利用来自客户和生产部门的数据和观察,做出功能区技术设计和实施决策。
说明:
在 Senior SDE 级别,主要是在软件的功能级别上工作,对某个产品或服务的端到端功能负有责任。
#### Principle SDE - 系统级别
要求:
- 开发持久、创新、简单、优雅的代码和测试(适当时包括架构),以满足业务和客户需求,并与产品或服务的长期计划保持一致。
- 运用其深厚的技术专长解决问题,使团队能够交付高质量的设计。
- 领导验证产品或服务或架构创新的工作,确定开发过程早期的关键设计领域和下游需要改进的领域。
- 选择适当的内部或外部技术,整合研究,创建设计和验证工具,包括在团队中重用的测试自动化,是对有效实践的优秀判断。
- 定义或重用质量指标、最佳实践和编码模式,以确保代码可测试。
- 利用来自客户和生产部门的数据和观察做出技术设计和实施决策。
说明:
在 Principle SDE 级别,主要工作在软件的系统级别上,对某个产品或服务的整体负责。
#### Partner SDE - 部门级别
要求:
- 开发持久、创新的体系结构,以满足产品/服务或整个部门的业务和客户目标。
- 利用深厚的技术专业知识,使团队能够提供高质量的产品/服务或部门范围的设计。
- 将质量融入自己的设计,并贯穿整个部门。
- 解决跨越多个版本的产品/服务或部门的最复杂问题。
说明:
Partner SDE 级别已经不能再用软件的级别来对标了,而是要负责一个部门中的好几个产品或服务。
#### Distinguished Engineer - 部门/公司
杰出的工程师代表着对微软的持续技术影响和影响力。个人需要深厚的技术知识、领导力和专业领域的重大创新。在各自的领域,对部门的业务成功至关重要,并负有责任,可能会塑造行业。
- 作为其专业领域/领域的技术权威,为其专业领域的部门、公司或行业制定方向或技术路线图。
- 无论组织或权力如何,都会影响技术业务决策。
- 跨部门、公司或行业工作,以增加其专业技术领域的价值。
- 在做出技术决策时,要考虑到部门和公司战略,而不是个人/团队的利益。
- 通过教学、参与、指导、建议和领导建立未来的技术社区。
- 体现了“一个微软”和“一个工程文化”。
- 展示微软的领导原则、文化属性和价值观。
#### Technical Fellow - 公司/行业
该名称仅适用于公司高管层相当于CVP
代表着对微软最高水平的持续技术影响和影响力。个人需要卓越的技术知识、领导力和专业领域的重大创新。在各自的领域,对微软持续的商业成功至关重要,并对其负责,他们往往塑造着整个行业。

Просмотреть файл

@ -0,0 +1,7 @@
### 参考资料
https://www.freecodecamp.org/news/how-to-think-like-a-programmer-lessons-in-problem-solving-d1d8bf1de7d2/
https://www.jianshu.com/p/0f46896ab4a0
Krathwohl, D.R. (2002) A Revision of Blooms Taxonomy: An Overview. Theory into Practice, 41, 212-218. http://dx.doi.org/10.1016/S0164-1212(98)10055-9

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 95 KiB

После

Ширина:  |  Высота:  |  Размер: 50 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 110 KiB

После

Ширина:  |  Высота:  |  Размер: 80 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 156 KiB

После

Ширина:  |  Высота:  |  Размер: 63 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 92 KiB

После

Ширина:  |  Высота:  |  Размер: 57 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 120 KiB

После

Ширина:  |  Высота:  |  Размер: 82 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 73 KiB

После

Ширина:  |  Высота:  |  Размер: 115 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 105 KiB

После

Ширина:  |  Высота:  |  Размер: 80 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 136 KiB

После

Ширина:  |  Высота:  |  Размер: 93 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 148 KiB

После

Ширина:  |  Высота:  |  Размер: 191 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 137 KiB

После

Ширина:  |  Высота:  |  Размер: 78 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 55 KiB

После

Ширина:  |  Высота:  |  Размер: 85 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 146 KiB

После

Ширина:  |  Высота:  |  Размер: 78 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 170 KiB

После

Ширина:  |  Высота:  |  Размер: 56 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 167 KiB

После

Ширина:  |  Высота:  |  Размер: 123 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 93 KiB

После

Ширина:  |  Высота:  |  Размер: 109 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 81 KiB

После

Ширина:  |  Высота:  |  Размер: 131 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 64 KiB

После

Ширина:  |  Высота:  |  Размер: 142 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 85 KiB

После

Ширина:  |  Высота:  |  Размер: 156 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 114 KiB

После

Ширина:  |  Высота:  |  Размер: 106 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 149 KiB

После

Ширина:  |  Высота:  |  Размер: 123 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 86 KiB

После

Ширина:  |  Высота:  |  Размер: 143 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 94 KiB

После

Ширина:  |  Высота:  |  Размер: 144 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 104 KiB

После

Ширина:  |  Высота:  |  Размер: 96 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 98 KiB

После

Ширина:  |  Высота:  |  Размер: 113 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 147 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 100 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 81 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 64 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 85 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 149 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 86 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 90 KiB

После

Ширина:  |  Высота:  |  Размер: 98 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 94 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 104 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 98 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 104 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 98 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 111 KiB

После

Ширина:  |  Высота:  |  Размер: 61 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 242 KiB

После

Ширина:  |  Высота:  |  Размер: 117 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 107 KiB

После

Ширина:  |  Высота:  |  Размер: 102 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 115 KiB

После

Ширина:  |  Высота:  |  Размер: 65 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 119 KiB

После

Ширина:  |  Высота:  |  Размер: 52 KiB

Просмотреть файл

@ -0,0 +1,72 @@
import random
max_number = 10 # 以最大值为10为例便于说明问题读者可以自行改到100
def generate_data():
data = [i for i in range(1, max_number+1)] # 生成一个顺序数组
data.append(random.randint(1, max_number)) # 最后随机填一个数
print("顺序数组=",data)
random.shuffle(data) # 打乱顺序
print("乱序数组=", data)
return data
def method_1_sum(data):
sum = 0
for x in data:
sum += x
#print(sum - 5050)
print(sum - int((1+max_number)*max_number/2))
def method_2_dict(data):
dict = {}
for x in data:
if dict.__contains__(x): # 字典中已经有此数,是重复的
print(x)
return
else: # 如果字典中没有此数,则保存
dict[x]=1
def method_3_sort(data):
data.sort()
for i in range(max_number+1):
if data[i] == data[i+1]: # 相邻的两个数相等,是重复数字
print(i+1)
return
def method_4_search(data):
pos = 0 # 从 0 位开始
x = data[pos] # 从 0 位取出数字
while (True):
if (x == pos): # 在目标位置上已经有一个相同的数字,是重复的
data[0] = x
print(x)
break
pos = x # 保存 x 的值到 pos
x = data[pos] # 取出 pos 位置的数值 x
data[pos] = pos # 把 pos 位置成 pos 值,如,第 3 个数组单元就置成 3
print(data)
def method_5_xor(data):
# 求所有数字的异或结果
tmp_x = 0
for i in range(len(data)):
tmp_x = tmp_x ^ data[i]
print(tmp_x)
# 求 1~max_number 的异或结果
tmp_n = 0
for i in range(max_number):
tmp_n = tmp_n ^ (i+1) # 注意是 i+1不是 i
print(tmp_n)
# 上面两者异或,可以得到重复的数字
print(tmp_x ^ tmp_n)
if __name__ == "__main__":
data = generate_data()
method_1_sum(data.copy())
method_2_dict(data.copy())
method_3_sort(data.copy())
method_4_search(data.copy())
method_5_xor(data.copy())

Просмотреть файл

@ -0,0 +1,16 @@
class Meeting():
start: time # 开始时间
end: time # 结束时间
host: People # 主持人
topic: str # 议题或内容
owner: People # 订阅者
status: Normal # 状态(正常、进行中、取消、已结束)
class MeetingRoom():
location: Geometry # 位置
no_room: str # 会议室编号
no_seat: int # 座位数
has_projetor: bool # 有无投影仪
has_whiteboard:bool # 有无白板
has_network: bool # 有无网络会议设备

Просмотреть файл

@ -0,0 +1,55 @@
from enum import Enum
# 颜色
class Colors(Enum):
White = 0
Red = 1
Yellow = 2
Blue = 3
Green = 4
Orange = 5
# 位置, 以魔方的中心点为坐标(0,0,0), 每个块在[-2,+2]之间
class Position():
x:int
y:int
z:int
# 块的基类
class Block():
postion: Position
# 中心块, 一种颜色
class CenterBlock(Block):
color_0: Colors
# 棱块, 两种颜色
class EdgeBlock(Block):
color_0: Colors
color_1: Colors
# 角块, 三种颜色
class CornerBlock(Block):
color_0: Colors
color_1: Colors
color_2: Colors
# 一个面
class Side():
# 属性
center_block: CenterBlock # 1个中心块
corner_blocks: CornerBlock[4] # 4个角块
edge_blocks: EdgeBlock[4] # 4个棱块
# 动作
def Rotate_90(): pass
def Rotate1_80(): pass
def Rotate_270(): pass
# 一个魔方
class RibukCube():
side_Front: Side
side_Back: Side
side_Up: Side
side_Down: Side
side_Left: Side
side_Right: Side

Просмотреть файл

@ -0,0 +1,27 @@
class Colors():
Red = 1
Green = 3
class Status():
On = 1
Off = 0
class Light():
color: Colors
status: Status
# 动作
def TurnOn()
def TurnOff()
class LightGroup():
# 属性
light_Straight: Light # 直行灯
light_LeftTurn: Light # 左转灯
# 动作
def SetStraight(On|Off) # 允许|禁止直行
def SetLeftTurn(On|Off) # 允许|禁止左转
class ControlCenter():
light_groups: LightGroup[4] # 路口的每个方向都有一个灯组
scheduler: time # 定时器用于对LightGroup进行开关操作

Просмотреть файл

@ -1,52 +0,0 @@
import random
def generate_data():
data = [i for i in range(1,101)]
data.append(random.randint(1,100))
print(data)
random.shuffle(data)
print(data)
return data
def method_1_sum(data):
sum = 0
for x in data:
sum += x
print(sum - 5050)
def method_2_dict(data):
dict = {}
for x in data:
if dict.__contains__(x):
print(x)
return
else:
dict[x]=1
def method_3_sort(data):
tmp = sorted(data)
for i in range(101):
if tmp[i] != i+1:
print(i)
return
def method_4_search(data):
pos = 0
while (True):
if (pos == data[pos]):
print(pos)
break
x = data[pos]
pos = data[x]
data[x] = x
print(data)
if __name__ == "__main__":
data = generate_data()
method_1_sum(data)
method_2_dict(data)
method_3_sort(data)
method_4_search(data)