最近成都的疫情又加重了,然后这边的健康码还弄出了一个时空伴随者,我刚好最近在听课集训信息学竞赛,然后呢就想着要不咱模拟一下这个算法,当然啦,想要完全模拟也是不肯能的 我太弱啦,所以我就弄出来一个时空伴随者简化版,然后顺便用 python 做了个可视化。这样我们就可以在画布上清楚的看到那些人的健康码是绿的,黄的和红的了 qwq。
关于这个时空伴随者是什么东西,有些读者可能还不知道,那这里我就来解释一下这到底是什么。首先官方的定义如下(以下内容来自度娘):
指本人的电话号码与确诊号码在同一时空网格(范围是800M*800M)共同停留超过10分钟,且最近14天任一方号码累计停留时长超过30小时以上,查出的号码为时空伴随号码。
在时空伴随者之前,排查新冠感染风险人员都以密切接触者来进行管理。通过对比两者的定义,可以发现,最大的区别在于两者在空间上范围的进一步扩大。
是不是看完有点懵,其实用也没多复杂,就是说人们把一个城市划分成了许多以一个信号基站为重心的 800 m × 800 m 800m \times 800m 800m×800m 的区块,然后如果你和另一个红码的人在同一个区块里面在同一段时间停留了超过 10 分钟,那么及就会被标记为时空伴随者(不管你和他有没有直接或者间接的接触)。还有另一种标注的方式,也就是在最近 14 天,你和一个红码人在同一个区块里的共同停留时间累计超过了 30 小时,那么一也会被标记为时空伴随者。
因为我的能力问题,所以我只能将问题极度简化,最后成为一个非常简单的小模拟题。大致是这样的。
首先是区块划分,我们就不在利用基站来划分区块了,我们就直接简单粗暴的用方格来分割一片空间,就有点像那种田字格本子的样子。然后,对于成为时空伴随者的两个条件,我们就直接去掉第二个条件,因为我的模拟也没打算模拟太久的时间qwq。
第三,对于人群的流动,我也使用了最简单的正态分布来模拟,也就是说先给定一个这个人的意愿行走方向,再按照正态分布随机的让他往各个方向走,这样就能保证他大概率的移动方向是一致的。一开始就随机给每个人分配他们的坐标和意愿移动方向就好了。关于人群的行走还有一个问题那就是人走着走着走出我们一开始圈定的地图了怎么办。我在这里用了一个非常简单粗暴的办法,我们让走出边界的人直接穿越空间,从地图的另一端出来(就像某些版本的贪吃蛇一样qwq)。
我选择的语言是 python,典型的面向对象的语言。然后我引用了两个非常常用的库,一个是 matplotlib,用来画散点图,还有一个 numpy,不说都知道是干啥的吧。因为我平时用的语言都是 c++,所以已经对以前的一点点 python 的知识忘得差不多了,所以在代码中有一些很明显可以定义一个 class 的地方我还是硬着头皮写了好几遍几个几乎一摸一样的数组(已经快忘了 class 怎么写的了,写出来估计还是一堆做错,还要调半天,还不如不写)。还有,本人码风清奇而且还很丑,请各位大佬见谅。
首先先弄出来 200 个人(也就是一堆随机的坐标和一堆随机的意愿移动方向),全部装进健康人的数组里面去,然后就是模拟按照意愿方向随机行走了。为什么我说正态分布简单呢,因为 numpy 的随机数库里面就正好有正态分布的随机值。所以我只需要稍微调用一下并改一点点东西就能用了(根本不用自己写qwq)。
在我模拟的环境中,人们分别有三种状态,绿码(健康人),红码(感染者)和黄码(时空伴随者)。先初始化,所有人都是绿码。然后我们就随机挑出一名幸运市民把他变成红的。然后就直接模拟第一种情况把绿码变成黄码就好了(第二种情况已经被简化掉了)。
import numpy as np
from matplotlib import pyplot as plt
import time
import random
mapSize = 6400 # 地图大小
peopleNum = 200 # 总人数
data = np.arange(0, mapSize)
def Random(mu, sigma): # 正态分布 让每一次人们的移动方向大概率相同
return np.random.normal(mu, sigma, 1)
def showLines():
for i in range(int(mapSize / 800) + 1):
ax.plot(data, data * 0 + 800 * i, color = 'grey')
ax.plot(data * 0 + 800 * i, data, color = 'grey')
def initmap():
plt.title("MAP")
plt.xlabel("x axis")
plt.ylabel("y axis")
plt.xlim(0, mapSize)
plt.ylim(0, mapSize)
plt.ion()
figure, ax = plt.subplots(figsize = (8, 6))
# green
gx = [] # x 和 y 是坐标
gy = []
gdire = [] # 每个人的移动方向
gtime = [] # 每个人在同一个区块停留的时间
gpre = [] # 上一个时间内所在的区块编号
# red
rx = []
ry = []
rdire = []
rtime = []
rpre = []
# yellow
yx = []
yy = []
ydire = []
ytime = []
ypre = []
# 打个表
block = [
[0, 800, 0, 800], [0, 800, 800, 1600], [0, 800, 1600, 2400], [0, 800, 2400, 3200], [0, 800, 3200, 4000], [0, 800, 4000, 4800], [0, 800, 4800, 5600], [0, 800, 5600, 6400],
[800, 1600, 0, 800], [800, 1600, 800, 1600], [800, 1600, 1600, 2400], [800, 1600, 2400, 3200], [800, 1600, 3200, 4000], [800, 1600, 4000, 4800], [800, 1600, 4800, 5600], [800, 1600, 5600, 6400],
[1600, 2400, 0, 800], [1600, 2400, 800, 1600], [1600, 2400, 1600, 2400], [1600, 2400, 2400, 3200], [1600, 2400, 3200, 4000], [1600, 2400, 4000, 4800], [1600, 2400, 4800, 5600], [1600, 2400, 5600, 6400],
[2400, 3200, 0, 800], [2400, 3200, 800, 1600], [2400, 3200, 1600, 2400], [2400, 3200, 2400, 3200], [2400, 3200, 3200, 4000], [2400, 3200, 4000, 4800], [2400, 3200, 4800, 5600], [2400, 3200, 5600, 6400],
[3200, 4000, 0, 800], [3200, 4000, 800, 1600], [3200, 4000, 1600, 2400], [3200, 4000, 2400, 3200], [3200, 4000, 3200, 4000], [3200, 4000, 4000, 4800], [3200, 4000, 4800, 5600], [3200, 4000, 5600, 6400],
[4000, 4800, 0, 800], [4000, 4800, 800, 1600], [4000, 4800, 1600, 2400], [4000, 4800, 2400, 3200], [4000, 4800, 3200, 4000], [4000, 4800, 4000, 4800], [4000, 4800, 4800, 5600], [4000, 4800, 5600, 6400],
[4800, 5600, 0, 800], [4800, 5600, 800, 1600], [4800, 5600, 1600, 2400], [4800, 5600, 2400, 3200], [4800, 5600, 3200, 4000], [4800, 5600, 4000, 4800], [4800, 5600, 4800, 5600], [4800, 5600, 5600, 6400],
[5600, 6400, 0, 800], [5600, 6400, 800, 1600], [5600, 6400, 1600, 2400], [5600, 6400, 2400, 3200], [5600, 6400, 3200, 4000], [5600, 6400, 4000, 4800], [5600, 6400, 4800, 5600], [5600, 6400, 5600, 6400]
]
def findblock(x, y): # 找到这个坐标所在的区块编号
for i in range(0, 64):
if (block[i][0] <= x and x <= block[i][1]) and (block[i][2] <= y and y <= block[i][3]):
return i
def initred(): # 选择一个被感染的 "幸运" 市民
rd = np.random.randint(0, peopleNum)
rx.append((gx[rd]))
ry.append((gy[rd]))
rdire.append((gdire[rd]))
rtime.append((gtime[rd]))
rpre.append(gpre[rd])
del gx[rd]
del gy[rd]
del gdire[rd]
del gtime[rd]
del gpre[rd]
def dire():
rd = np.random.randint(0, 3)
if rd == 0:
tempx = -1
elif rd == 1:
tempx = 1
else:
tempx = 0
rd = np.random.randint(0, 3)
if rd == 0:
tempy = -1
elif rd == 1:
tempy = 1
else:
tempy = 0
return [tempx, tempy]
def Initialize():
initmap() # 初始化画布
for i in range(0, peopleNum): # 初始随机生成每个人的坐标
gtime.append(0)
gdire.append(dire())
gx.append((Random(0, 0.5) * mapSize) % mapSize)
gy.append((Random(0, 0.5) * mapSize) % mapSize)
gpre.append(findblock(gx[i], gy[i]))
initred()
def moveperson():
# 为了让所有点都不超出地图的限制,我把所有的点的坐标进行微扰之后都对地图大小取模
# 但是这样就会出现某一些点到达地图边界之后就从地图的另一端直接冒出来的情况
for i in range(0, len(gx)):
gx[i] = (gx[i] + Random(gdire[i][0], 0.5) * 10) % mapSize
gy[i] = (gy[i] + Random(gdire[i][1], 0.5) * 10) % mapSize
for i in range(0, len(rx)):
rx[i] = (rx[i] + Random(rdire[i][0], 0.5) * 10) % mapSize
ry[i] = (ry[i] + Random(rdire[i][1], 0.5) * 10) % mapSize
for i in range(0, len(yx)):
yx[i] = (yx[i] + Random(ydire[i][0], 0.5) * 10) % mapSize
yy[i] = (yy[i] + Random(ydire[i][1], 0.5) * 10) % mapSize
def showgragh():
ax.clear()
showLines()
ax.scatter(gx, gy, color="limegreen", marker=".")
ax.scatter(rx, ry, color="red", marker=".")
ax.scatter(yx, yy, color="yellow", marker=".")
figure.canvas.draw()
figure.canvas.flush_events()
def movegtoy(idx): # 将绿色的健康码变为黄色的
yx.append((gx[idx]))
yy.append((gy[idx]))
ydire.append((gdire[idx]))
ytime.append((gtime[idx]))
ypre.append(gpre[idx])
gx.pop(idx)
gy.pop(idx)
gdire.pop(idx)
gtime.pop(idx)
gpre.pop(idx)
def adjustcondition(): # 看看是否能改变每个人的状态
for i in range(0, len(gx)):
bidx = findblock(gx[i], gy[i])
if bidx == gpre[i]:
gtime[i] = gtime[i] + 1
else:
gtime[i] = 0
gpre[i] = bidx
for i in range(0, len(rx)):
bidx = findblock(rx[i], ry[i])
if bidx == rpre[i]:
rtime[i] = rtime[i] + 1
else:
rtime[i] = 0
rpre[i] = bidx
if rtime[i] >= 10:
for j, item in enumerate(gx): # 枚举所有的健康人
bidx = findblock(gx[j], gy[j])
if (bidx == rpre[i]) and (gtime[j] >= 15): # 如果他和感染者在同一个区块而且他们共同停留时间超过 15
movegtoy(j) # 这个人将成为时空伴随者
for i in range(0, len(yx)):
bidx = findblock(yx[i], yy[i])
if bidx == ypre[i]:
ytime[i] = ytime[i] + 1
else:
ytime[i] = 0
ypre[i] = bidx
def adjustdire(): # 随即改变人们行走的方向
for i in range(0, len(gx)):
gdire[i] = dire()
for i in range(0, len(rx)):
rdire[i] = dire()
for i in range(0, len(yx)):
ydire[i] = dire()
if __name__ == '__main__':
Initialize()
time = 0
while 1:
time = time + 1
moveperson()
adjustcondition()
showgragh()
if (time % 100 == 0):
adjustdire()
最后的最后,我需要声明我的代码只模拟 6400 m × 6400 m 6400m \times 6400m 6400m×6400m 范围的人的活动,而且对人的移动行为做了极大的简化,也就是说这个是一种非常理想的情况(我甚至没有考虑感染者再把其他人感染的情况)。还有,这段代码是在较短的时间内写出来的,所以关于筛选绿码使他们变黄的这一段的复杂度是 O ( n 2 ) O(n^2) O(n2) 的,要是放在现实中肯定是不够用的。而且我也不是模拟的显示情况,现实情况应该是多线程的,而我这个是单线程,我想做的只是再画布上用理想的情况大致模拟出时间伴随者的产生,后续有时间的话还会继续改良算法,各位大佬就当看个乐吧qwq。