summaryrefslogtreecommitdiff
path: root/content/posts
diff options
context:
space:
mode:
authorEltrac <hi@guhub.cn>2025-06-21 22:51:26 +0800
committerEltrac <hi@guhub.cn>2025-06-21 22:51:26 +0800
commitba8edc6dbc9ecfc818c46fb6f7b201bc48110b1a (patch)
tree9cb959853eea76e1dc6902e3c5769df077494f3f /content/posts
parente276f3a456980c64a80eb5cc29567703f4268fb7 (diff)
docs: update drafts
Diffstat (limited to 'content/posts')
-rw-r--r--content/posts/weekly/34.md32
-rw-r--r--content/posts/一个电梯调度算法.md194
2 files changed, 226 insertions, 0 deletions
diff --git a/content/posts/weekly/34.md b/content/posts/weekly/34.md
new file mode 100644
index 00000000..b4c216c7
--- /dev/null
+++ b/content/posts/weekly/34.md
@@ -0,0 +1,32 @@
+---
+title: 稻草人周刊 Vol.34
+subtitle:
+tags:
+date: 2025-06-20 20:20:10
+categories: 稻草人周刊
+banner:
+draft: true
+toc: true
+endnotes:
+---
+
+
+
+## 🙋 在做什么
+
+
+
+## 👀 在看什么
+
+
+
+## 💬 本期杂谈
+
+
+
+## 🗣️ 碎碎念
+
+- 和别人一起做项目的时候我们都用大模型写代码,四天的实训课被一部分组员写代码的方式给气笑了,这个时候就能看出来程序员的水平并不会因为有了 AI 工具就变得难分高下,有的人用 AI 写的代码依然是一团糟。有一个进度很慢的组员,遇到问题也不在群里吭声;最近一次他提交的代码出了问题他好像一直没修好,我猜他大概反复喂给 AI 改了好多遍,结果我运行他提交的代码一看,前端控制台的报错再明显不过了:有一个函数没被定义就被调用了。不仅如此,他的好多代码都在交给 AI 反复改的过程中改乱了,好多地方都是空引用。后来,他的代码又出了问题,和登录验证有关,他就发在群里问负责登录验证模块的组员;结果我翻开他的代码,又很快地发现了问题,他的 AI 在前端加了一串不明所以的登录相关的逻辑,他遇到的问题完全是自己的代码导致的…… *你完全不读 AI 写出来的代码吗?* 之前出去接水路过他桌子的时候,发现他在复习,没有在写代码;回来的时候又看了一次,发现他~~他妈~~是在学日语。
+- 关于那个组员没有发现前端报错的事情,大概也要怪教前端的那个老师,他 Debug 的方式就是通过右键菜单打开源代码,直接读纯的 HTML 和 CSS。他自己都不会用现代浏览器的检查元素和控制台,更别说教学生了。我很想问他:你自己写的不就是静态的 HTML 和 CSS 吗?在浏览器里浏览源代码难道会有什么不一样?
+
+[^1]: *正在写这段文字的我看着即将和时针一起指向 12 的分针陷入了沉思*
diff --git a/content/posts/一个电梯调度算法.md b/content/posts/一个电梯调度算法.md
new file mode 100644
index 00000000..8b3d9e04
--- /dev/null
+++ b/content/posts/一个电梯调度算法.md
@@ -0,0 +1,194 @@
+---
+title: 一个电梯调度算法
+subtitle:
+tags:
+ - 计算机
+ - 算法
+related_cards:
+mentioned_books:
+date: 2025-06-13T23:19:00
+categories:
+banner:
+draft: true
+toc: true
+---
+
+作为一个程序员,我在博客上写的技术文章还是太少了,原因大概有两个:一是自己的技术水平还不足以产出太多原创内容;二是觉得计算机领域的知识很难写成所有人都能读懂的文章——总之就是,不想写枯燥的操作教程,也不想在博客上复制粘贴代码。不过最近倒是有了一些可以成文的想法。
+
+[搬家](/posts/逃离群居生活/)之后,我每天都要乘电梯。尽管公寓楼的人流量比较小,基本不会出现与其他使用者争夺电梯资源的情况,但就算是这样,楼栋的电梯调度方式依然让我感到迷惑。这几天我一直尝试搞清楚电梯的运作模式,想着能不能用伪代码的方式表示出来,甚至做一些改进。好巧不巧,最近关注的一个博主就发了一篇有关电梯交互和调度算法的[文章](https://blog.codingnow.com/2025/06/elevator.html),我了解到**几乎每一个需要乘电梯上班的程序员都思考过这个问题**,还挺奇妙的。<!--more-->
+
+## 用户如何与电梯交互
+
+回想一般坐电梯的过程:在某一层按下电梯按钮——电梯来到你所在的楼层——进入电梯后按下楼层按键——电梯带你前往你想要前往的楼层——你走出电梯,结束。
+
+先把乘坐电梯的人都抽象为用户(`User`),电梯就表示为 `Elevator`,方便之后的描述。在上述交互过程中,用户会向系统输入两个指令:
+
+1. 在进入电梯前按下按钮,表示自己需要**向上**还是**向上**
+2. 在进入电梯后按下按钮,表示自己需要去**哪一层**
+
+将第一个指令命名为 `Direction`,第二个指令命名为 `Destination` 或 `to`。除了这两个输入,影响电梯行为的其实还有用户所在的楼层,命名为 `Location` 或 `from`,也作为输入的一部分传给电梯的相关模块。
+
+表示为伪代码:
+
+```javascript
+user.getDirection()
+user.getDestination()
+user.getLocation()
+```
+
+电梯的交互逻辑就是这样。[云风](https://blog.codingnow.com/2025/06/elevator.html)在他的文章中提出可以用「召唤按钮」代替常见的上和下两个按键,这个可以先搁在一边,回头再看看。
+
+电梯的调度算法就需要处理这三个输入,然后确定电梯的移动方式,即向上移动还是向下移动,途中在哪个楼层停下并开门。电梯本身的属性也要考虑到,包括电梯目前所在的楼层、是否可以移动(故障、超载或厢门未关闭时不能移动)。
+
+```javascript
+elevator.getCurrentFloor()
+elevator.isMovable()
+```
+
+如果电梯正在响应一个用户的请求时收到了另一个用户的请求,应该先完成上一个用户的请求,还是想办法同时满足两个用户的请求,也是需要考虑的问题。同样地,这个问题先放一放,先实现基本的逻辑再说。
+
+## 单用户单电梯的情况
+
+调度电梯就是决定电梯如何移动,在何时调用哪一部电梯的过程。对于单部电梯来说,调度过程分为两步,也就是接和送。
+
+### 接
+
+假设电梯有一个 `move()` 方法,输入正数向上移动一定楼层,输入负数向下移动一定楼层。要移动到用户所在的楼层,就可以简单地表示为:
+
+```javascript
+elevator.move(user.getLocation() - elevator.getCurrentFloor())
+
+// 这显然可以封装为一个单独的函数
+elevator.moveTo = (location) => {
+ this.move(location - this.getCurrentFloor())
+}
+```
+
+使用 `moveTo()` 方法的时候就不需要再考虑电梯当前所在的位置了,因为已经被封装好了。这么看来,在单用户的情况下,单个「召唤按钮」的方案的确可以代替上和下两个按钮的方案,当用户按下按钮,只需要执行 `elevator.moveTo(user.getLocation())` 就可以了,在算法层面完全不需要考虑用户在一开始按的是向上还是向下。
+
+### 送
+
+送的步骤也很简单,用户进入电梯并输入目的地后,只需要执行:
+
+```javascript
+elevator.moveTo(user.getDestination())
+```
+
+似乎讲了一堆废话,单用户单电梯的情况就是很简单嘛!
+
+## 单用户多电梯调度
+
+这更贴近我的情况,我每天都在乘电梯的时候好奇:为什么我按下了左边的电梯,响应请求的却是另一部电梯?明明左边那部电梯离我更近。
+
+虽然看起来每部电梯都有各自的物理按键,但两部电梯的控制是相通的。在算法层面,用户与两部电梯是同时交互的,而不是单独地给一部电梯下达了命令。我认为这是合理的,如果有一部电梯更近,而用户按了更远的那一部,或者如果用户按下的那一部电梯正在被使用,而另一部是空闲的,让用户独立操作某一部电梯就不是什么好事。
+
+总而言之,在不止一部电梯的情况下,我们依然只需要考虑单一的输入。
+
+### 接
+
+在有多部电梯的情况下,只需要调度一部电梯接送用户。为了最小化用户的等待时间,提高效率,选择距离用户最近的电梯就是最佳方案。由于我们正在考虑单用户的情况,所以不存在电梯繁忙、正在运送其他用户的情况。
+
+这时就不能直接让 `elevator` 对象响应请求了,可能需要一个额外的电梯调度器(`elevatorScheduler`)决定让那一部电梯前去接送用户。这个调度器应该能够访问系统中所有的电梯,对比他们与用户的距离,然后派遣最近的那部电梯。
+
+```javascript
+// in 'elevatorScheduler'
+const elevators = this.getElevatorArray()
+const userLocation = user.getLocation()
+
+let closestElevator = null
+
+for (let elevator of elevators) {
+ const thisDistance = userLocation - elevator.getCurrentFloor()
+ const closetDistance = userLocation - closestElevator.getCurrentFloor()
+
+ if (!closestElevator || cloestDistance > thisDistance) {
+ closestElevator = elevator
+ }
+}
+
+closestElevator.moveTo(userLocation)
+```
+
+### 送
+
+由于接到用户之后,任务就完全交给其中一部电梯了,其他电梯则不需要考虑。所以送的步骤还是和「单用户单电梯的情况」相同,在此不赘述。
+
+## 多用户单电梯调度
+
+我记得有一次出门,我和家人忙忙慌慌地收拾东西,把电梯一直占着,过了好久我们才下楼,走出电梯门的时候发现楼下有两个人已经不耐烦到坐在地上刷手机了,愧疚感顿时涌上心头。用户不止一个,而资源访问是互斥的,就会出现这种情况(好在电梯应该不会有死锁问题)。
+
+电梯显然不能及时响应每一个用户的请求,当一个用户正在占用资源时,就不得不等待另一个用户释放资源。不过,电梯如何才算被占用呢?事实上,只有电梯厢门打开或准备打开时才算被占用,因为电梯在接和送的过程其实都可以停靠。比如,有人从一楼前往五楼,而我打算从三楼前往四楼;当电梯正在从一楼向上移动时,我在三楼按下按钮,电梯理应响应我的请求,在三楼停下打开门;当我进入电梯按下四楼按钮,它也理应在四楼停下开门让我出去,然后再送另一个用户去五楼。
+
+在多用户的情况下,单个「召唤按钮」的模式就不那么好用了。还是那个例子,当有人从一楼前往五楼,我依然在三楼,但我打算下行前往一楼。这个时候,电梯不应该给我开门,应该在送完去五楼的用户之后返回时再给我开门。如果没有上行和下行按钮的区分,电梯就无法在接送时确定要不要响应用户请求,以及何时响应用户请求。
+
+为什么不能在另一个用户前往五楼的时候开门,让需要下楼的我乘坐电梯呢?毕竟只要等前一个人到达目的地,电梯再响应我的请求就好了。问题在于,电梯停考和开门的过程,为了保证停靠平稳,不会出现安全问题,耗费的时间是很长的,在中途给我开门会增加其他用户的等待时间。
+
+尽管电梯的上下按键可能会让人感到困惑,搞不清楚「上」指的是用户想要往上走,还是命令电梯向上运行,但这个操作逻辑在习惯之后是可以变得符合直觉的。相反,如果没有上下按键的区分,用户可能不会明白电梯为什么不响应自己的请求,自己明明想往下,而电梯却往上开。
+
+### 接
+
+在多用户单电梯的情况下,接和送这两个步骤变得耦合了,似乎很难完全分离开,因为当一个用户正乘电梯前往目的地时,另一个用户可能希望搭上这部电梯,接和送是同时进行的。
+
+不过,我们依然先考虑电梯如何响应用户「接」请求。
+
+在单用户的情况下,电梯只需要直接前往用户所在的楼层就好了;在多用户的情况下,电梯则需要排序,即先前往哪个楼层,再前往哪个楼层。我们在此假设没有人按下楼层键,「送」的算法还没有开始。
+
+#### 单向运行的情况
+
+在一些情况下,电梯只需要单向运行。假设电梯在 6 楼,有两个用户分别在 3 楼 和 4 楼按下了电梯的下行按钮,有另一个用户在 1 楼按下了电梯的上行按钮,那么电梯只需要一直往下,分别在对应的楼层停下就好了。当前两个用户到达了 1 楼,最后一个用户正好可以上楼。
+
+#### 上下楼层都有用户呼叫的情况
+
+如果电梯在 4 楼,而 2 楼、3 楼和 6 楼都有用户希望下行,电梯应该如何响应呢?如果先去离得最近的 3 楼,再去 2 楼,在把这两个用户送到一楼之后再去接 6 楼的用户,显然是不合理的;电梯应该先往上,接到 6 楼的用户后再一路向下。反之亦然,如果电梯上方和下方的楼层都有用户在等待接送,而他们选择的运送方向都是一致的,那么先向反方向运行是最有效率的,可以避免走回头路——如果当前的所有用户都希望往上,那么电梯就应该先考虑向下,然后一路向上完成运送任务。
+
+听起来很美好,但真的可行吗?比较极端的例子是,电梯在 17 楼,现在 18 楼和 19 楼有用户希望向上前往更高的楼层,此时 1 楼又有用户希望往上,电梯显然不应该先去一楼。对于特别高的楼层来说,这个方法不一定能保证效率。
+
+虽然不能保证效率,但「反方向优先」的方案的确保证了电梯的资源消耗最低,也就是**路线最短**。如果先接送离电梯最近的用户,电梯的总运行路线长度是 41 层楼;如果先接送反方向上的用户,电梯的总运行路线长度是 35 层楼。如果电梯的运行速度是恒定的,不考虑在每个楼层的停靠时间,路线的长度就决定了时间长短。如果要保证时间最短,就应该采用「反方向优先」的方案。
+
+![](https://image.guhub.cn/picgo2025/IMG_0369.PNG "上为「最近用户优先」的方案,下为「反方向优先」的方案。")
+
+刚才我们一直假设所有用户都想去同一个楼层,那如果只有 18 和 19 楼的用户想去 20 楼,而 1 楼的用户只想去 8 楼呢?
+
+![](https://image.guhub.cn/picgo2025/IMG_0370.PNG)
+
+在这种情况下,「最近用户优先」的方案一共移动了 29 层,「反方向优先」的方案还是移动了 35 层。显然,「反方向优先」的方案连总运行时间都不一定能保证最小,而且无论运行时间如何,这种方案都会让高层的用户等待很久。
+
+等等,他们只是想去 20 楼而已,为什么不走楼梯?
+
+楼梯其实也可以分摊电梯的负担,但别忘了,这两个用户还没有上电梯,他们只是按下了「上行」按钮,电梯并不知道他们要去的是 20 楼还是 40 楼。况且,电梯的制造商应该也不想培养用户走楼梯的习惯吧——这样他们还怎么卖产品?
+
+问题又来了,如果 18 和 19 层的用户真的是想要去 40 层呢?
+
+![](https://image.guhub.cn/picgo2025/IMG_0371.PNG)
+
+「最近用户优先」的方案的总路线长度是 62 层楼,「反方向优先」方案的总路线长度是 55 层楼。
+
+……
+
+扯了这么多,结果还是没得出多用户单电梯情况下应该如何调遣电梯。显然,「最近用户优先」和「反方向优先」的方案都有弊端,各取所长应该是最好的。
+
+```javascript
+if (condition) {
+ elevator.moveTowardsClosestUserFirst()
+} else {
+ elevator.moveTowardsOppositeDirectionFirst()
+}
+```
+
+那么,这个 `condition` 应该如何计算呢?
+
+### 送
+
+## 多用户多电梯调度
+
+### 接
+
+### 送
+
+## 一种新的电梯设想
+
+能不能模仿商场里的扶手电梯(Escalator),只设置一个只上行的电梯和一个只下行的电梯呢?
+
+---
+
+另,感觉电梯的调度其实可以让 AI 实时生成最优调度方案,编写精密的算法实在是太费脑子了。 \ No newline at end of file