WZH

Back

1. 等坑位问题

公司每层只有 4 个坑位,需要供至少两百人使用,每次需要解决生理问题的时候都需要等待一段时间,在等待的过程中想到了这个问题:

如果 4 个坑位供 200 个人使用,假设每人平均需要使用 20 分钟,每天上班时间的坑位使用都不会断(即每出来一个人必定有一个人进去),并且每天在正式上班之前就已经有 4 个人在任意时间开始使用了。请问,我在上班时候的任意时刻去到厕所,发现 4 个坑位满员且前面没有人排队,如果我等的话我需要等待多长时间?

回到原来这道题,因为设定了每个人蹲 20 分钟(实际上是数学期望为 20 分钟,这里为了简化问题,直接定死为 20 分钟),因此在最好情况下,我到的时候刚好有人出来,需要等待 0 分钟;在最差情况下,我到的时候刚好 4 个人同时入坑,我需要等待 20 分钟,那么我们只需要考虑在这 20 分钟以内,第一个人出来的时间的期望。 又因为我们只需要考虑我们到厕所的时刻的前 20 分钟,因此问题转化为了:在 0~20 之间随机取 4 个数,这 4 个数里的最小值就是我需要等待的时间。

1.1 蒙特卡罗方法

身为一个程序猿,我们先用百试不爽的蒙特卡罗方法模拟一下,假设每人蹲坑时间 T=20T = 20,坑位数量 N=4N = 4,模拟 100 万次:

Python
import random

NUM_TESTS = 1000000  # Num of tests
NUM_TOILETS = 4      # Num of toilets
TOILET_TIME = 20     # Average toilet time

total_time = 0
for _ in range(NUM_TESTS):
    total_time += min([random.random() * TOILET_TIME for _ in range(NUM_TOILETS)])

print(f'Num of tests: {NUM_TESTS:,}')
print(f'Num of toilets: {NUM_TOILETS}')
print(f'Average toilet time: {TOILET_TIME} mins')
print(f'You need to wait for {total_time / NUM_TESTS:.3f} mins.')

模拟结果如下:

Text
Num of tests: 1,000,000
Num of toilets: 4
Average toilet time: 20 mins
You need to wait for 3.996 mins.

在这种情况下,我们需要等待约 4 分钟时间。

如果你多试几次不同的值,很快就会发现实际上我们要等待的平均时间是 TN+1\frac{T}{N + 1}

1.2 解析方法

下面我们尝试分析。

假设每人蹲坑时间为 TT,坑位数量为 NN,每个坑位在上一个 TT 分钟的时间周期内进去的时刻为 tit_ii{1,2,,n}i \in \{1, 2, \cdots, n\}。那么根据上面的分析,我们要等待的时长应该是 x=min(t1,t2,,tN)x = \min(t_1, t_2, \cdots, t_N),现在要求 xx 的期望 E[x]E[x]

我们试着算一下 xx 的累积分布函数(Cumulative Distribution Function,CDF):

CDF(x)=P(xt)=1P(x>t)=1i=1Np(x>ti)=1(1xT)N\begin{aligned} \operatorname{CDF}(x) &= P(x \le t) \\ &= 1 - P(x > t) \\ &= 1 - \prod_{i = 1}^{N} p(x > t_i) \\ &= 1 - (1 - \frac{x}{T})^N \end{aligned}

对累积分布函数求导,可以得到 xx 的概率密度函数(Probability Density Function,PDF):

PDF(x)=(1)(N)(1xT)N1(1T)=NT(1xT)N1\begin{aligned} \operatorname{PDF}(x) &= (-1) \cdot (N) \cdot (1 - \frac{x}{T})^{N - 1} \cdot (-\frac{1}{T}) \\ &= \frac{N}{T} \cdot (1 - \frac{x}{T})^{N - 1} \end{aligned}

那么 xx 的数学期望 E[x]E[x] 计算如下:

E[x]=0TxPDF(x) dx=0TxNT(1xT)N1 dx\begin{aligned} E[x] &= \int_0^T x \cdot \operatorname{PDF}(x) ~ dx \\ &= \int_0^T x \cdot \frac{N}{T} \cdot (1 - \frac{x}{T})^{N - 1} ~ dx \end{aligned}

我们采用换元积分法:

t(x)=1xTt(x)=1Tx=(1t)TE[x]=0TxNT(1xT)N1 dx=N0Tx(1xT)N1(1T) dx=N10(1t)TtN1 dt=NT10(tN1tN) dt=NT10(tNNtN+1N+1) dt=NT(01N(N+1))=TN+1\begin{aligned} t(x) &= 1 - \frac{x}{T} \\ t'(x) &= -\frac{1}{T} \\ x &= (1 - t) \cdot T \\ E[x] &= \int_0^T x \cdot \frac{N}{T} \cdot (1 - \frac{x}{T})^{N - 1} ~ dx \\ &= - N \int_0^T x \cdot (1 - \frac{x}{T})^{N - 1} \cdot (-\frac{1}{T}) ~ dx \\ &= - N \int_1^0 (1 - t) \cdot T \cdot t^{N - 1} ~ dt \\ &= - NT \int_1^0 (t^{N - 1} - t^N) ~ dt \\ &= - NT \int_1^0 (\frac{t^N}{N} - \frac{t^{N + 1}}{N + 1})' ~ dt \\ &= - NT \cdot (0 - \frac{1}{N (N + 1)}) \\ &= \frac{T}{N + 1} \end{aligned}

结论与蒙特卡罗方法模拟的结果一致。

2. 感知错觉:检查悖论

回顾一下上一节的结论:

假设每人蹲坑时间为 TT,坑位数量为 NN,每个坑位在上一个 TT 分钟的时间周期内进去的时刻为 tit_ii{1,2,,n}i \in \{1, 2, \cdots, n\}。那么我们在任意时刻进入,发现坑位满员且无人等待,那么我们需要等待的时长 xx 的数学期望为:

E[x]=TN+1E[x] = \frac{T}{N + 1}

然而在现实生活中,我们往往会觉得等了远远不止这个时间。以我司坑位举例,坑位数量 N=4N=4,假设每人蹲坑时间 T=20T=20,那么这种情况下我需要等待的时间应该是 TN+1=204+1=4\frac{T}{N + 1} = \frac{20}{4 + 1} = 4 分钟,但是我感觉经常需要等待 10 分钟以上,记忆中最长的一次能到 15 分钟以上,这是为什么?

事实上,这是采样偏差所导致的问题。

按照我们前面的设定,每人的蹲坑时间固定,每个坑位的初始开始时间随机且独立,我们在任意时刻到达厕所(视为一次采样),那么需要等待时间的采样应该是一个均匀分布。而实际上,每人的蹲坑时间有长有短,更有可能是一个均值为 TT 的高斯分布,而不是一个均匀分布,这就导致了每个坑位在时间轴上被使用的情况的「区间划分」是不均匀的,那么如果我们依然是在均匀的任意时刻到达厕所(视为一次采样),那么我们会对等待时间较长的区间采样得更多,而对等待时间较短的区间采样得更少,因此导致了我们实际上确实等了更长的时间(而不仅仅是在感受上)!

下面再基于蒙特卡罗方法重新模拟。我们将每人的蹲坑时间从固定的 T=20T=20,改为符合均值为 T=20T=20、方差为 55 的正态分布,其余变量不变,模拟 100 万次:

Python
import random

NUM_TESTS = 1000000    # Num of tests
NUM_TOILETS = 4        # Num of toilets
TOILET_TIME = 20       # Average toilet time
TOILET_TIME_SIGMA = 5  # Standard deviation of toilet time

total_time = 0
for _ in range(NUM_TESTS):
    waiting_time = []
    while len(waiting_time) < NUM_TOILETS:
        t = random.normalvariate(TOILET_TIME, TOILET_TIME_SIGMA)
        if t > 0:
            waiting_time.append(t)
    total_time += min(waiting_time)

print(f'Num of tests: {NUM_TESTS:,}')
print(f'Num of toilets: {NUM_TOILETS}')
print(f'Average toilet time: {TOILET_TIME} mins')
print(f'You need to wait for {total_time / NUM_TESTS:.3f} mins.')

模拟结果如下:

Text
Num of tests: 1,000,000
Num of toilets: 4
Average toilet time: 20 mins
You need to wait for 14.857 mins.

Amazing,需要等待的时间从 4 分钟涨到了 15 分钟!😂

当然,这里出于习惯假设了高斯分布,并且指定了到达时间的方差,并不一定正确,目的只是为了在直观上理解这种现象。事实上,这个现象也有科学家进行了相关研究,例如,美国计算机学家 Allen Downey 对普渡大学的班级平均人数进行统计,发现通过统计得出的平均人数是 90 人,而教务处给出的真正的平均人数是 35 人,从而提出了 检查悖论(Inspection Paradox,维基百科),大意如下:

如果我们等待一些预先定义好的时间 tt,然后观察包含 tt 的更新区间有多大,我们应该期望它比平均大小的更新区间大。

有关准确的数学描述与理论证明,请参考 维基百科 或相关论文。

这里再举一些跟采样偏差相关的现象:

  • 等公交的时候,明明站牌上写每 10 分钟一趟,却感觉等了远不止 10 分钟。
  • 开车的时候,无论走哪条路,总是感觉经常碰上绿灯。
  • 总感觉明星挣钱、职业电竞选手挣钱、直播带货挣钱,那是因为挣钱的人曝光量大。
  • 航空公司经常表示航班上座率不够,公司亏损,可坐飞机时候却经常感觉飞机拥挤。
  • Allen Downey:在社交网络上,每名用户拥有 44 名好友,而每名用户的好友平均拥有 104 名好友。
  • Allen Downey:联邦监狱每名囚犯的平均判决时间是 3.6 年,但正在服刑的所有囚犯的平均判决时间是 13 年。

有时,我们也会用 幸存者偏差维基百科)来解释这些现象,实际上不管是幸存者偏差还是这里的检查悖论,其实都是因为统计或采样的方式不同。这里就不再展开了。

3. 启示

这对我们有什么启示?

首先,从个人角度出发,等车、等蹲坑时间比平均时间确实是客观世界的规律,当遇到这些事情的时候,想开点,放宽心。🙂

其次,如果作为公司「如厕机制」的设计人,有什么措施能够有效降低等待时间?我们复用前面基于高斯分布的蒙特卡罗模拟方法,计算一下真实等待时间与厕坑数量的关系,代码如下:

Python
import random

NUM_TESTS = 1000000    # Num of tests
TOILET_TIME = 20       # Average toilet time
TOILET_TIME_SIGMA = 5  # Standard deviation of toilet time

for num_toilets in range(2, 22, 2):

    total_time1 = 0
    for _ in range(NUM_TESTS):
        total_time1 += min([random.random() * TOILET_TIME for _ in range(num_toilets)])

    total_time2 = 0
    for _ in range(NUM_TESTS):
        waiting_time = []
        while len(waiting_time) < num_toilets:
            t = random.normalvariate(TOILET_TIME, TOILET_TIME_SIGMA)
            if t > 0:
                waiting_time.append(t)
        total_time2 += min(waiting_time)

    print(f'| {num_toilets} | {total_time1 / NUM_TESTS:.3f} | {total_time2 / NUM_TESTS:.3f} |')

模拟结果如下:

厕坑数量均匀蹲坑等待时间(min)高斯蹲坑等待时间(min)
26.66417.181
43.99914.858
62.85413.664
82.22412.881
101.82312.311
121.53811.857
141.33211.491
161.17711.178
181.05210.908
200.95110.667

均匀蹲坑等待时间降低明显,但高斯蹲坑等待时间似乎降低不多,这里面的差距在哪?多装了坑位,理论上总的蹲坑时长肯定是按比例增加,但是似乎对我们的心理感受提升得却不是很明显?这里其实还忽略了我们的一个很重要的假设:每次到厕所的时候坑位用满,且无人排队

事实上,随着坑位的增加,坑位用满的情况也会降低,举个比较极端的例子,如果坑位从 4 个扩展到 20 个,事实上我们到达的时候往往是有坑位的,等待时间几乎可以变成 0,而这部分人群是无法被采样到的。

如果要考虑更细致的话,应该将公司楼层总人数、如厕流行时间段等等因素考虑进来,那就变成了一个复杂的规划问题了。不过无论如何,在坑位只有 4 个、等待时间经常要超过 10 分钟甚至 15 分钟来说,增加坑位总是好的。

4. 参考资料