文章目录
前言
最近,帮一个同学编写了一个可视化的停车场管理系统程序,用于辅助他们的管理学的pre。所以这个小项目也非常的简单,刚好运用了之前算法设计与分析与WEB技术的结合。既然是可视化程序,第一反应可以使用C++,但是恰好上学期学习了JavaScript,于是就用JavaScript编写这个程序。
项目在线demo地址:https://park.hoyue.eu.org/
信息描述
对于一个有限的停车场空间内,有一个固定的入口和出口。例子中设置为4列车位,除了最左侧的一列有8个车位外,每一列有7个车位。入口和出口在同一个位置(1,0),每一列从左往右依次以A0/B1/C2/D3开头命名车位。根据纵坐标,也依次从0开始向上递增。概念图如下:
我们抽象成表格,就是如下的情况:
y \ x | A0 | B1 | C2 | D3 |
---|---|---|---|---|
8 | ||||
7 | ||||
6 | ||||
5 | ||||
4 | ||||
3 | ||||
2 | ||||
1 | ||||
0 | Entrance |
这是按照坐标轴的表格,其中如果这个位置是可用的,那么它的背景颜色就是绿色(green)的。如果这个位置是无效的,那么它的背景颜色就是海蓝宝石色(aquamarine)。如果它的背景颜色是红色(red)的,那么表示该位置已经被占用,存在车辆了。
现在需要实现如下功能:
- 车辆停入:输入入场时间和车牌号,将车辆停入距离入口最近的且未被占用的车位,并显示停入信息。
- 车辆驶离:输入出场时间和车牌号,释放被占用的车位,并输出驶离信息。
- 费用计算:输出停车产生的费用,该例子中为比较简单的
(t-1)×1
。 - 表格显示:在表格上可视化展示停车位情况,在被占用的停车位中需要写入车牌号。
问题分析
距离最短问题
对于这个问题,还是比较简单解决的。首先是解决距离问题。
因为要求中,每个格子距离起点的距离是固定的,我们不必每次停入的时候都重新计算找最小值,只需要一直维护一个容易保持有最小值的数据结构就可以了。于是很容易想到使用一个最小堆去存储这些距离。
对于在JavaScript中实现堆,只需要稍微根据《Introduction to Algorithm》中的伪代码进行修改即可。
信息存储
这个问题中有很多信息,该如何存储呢?我想到的是使用两个类。
一个类是Heap,用于维护堆数组的,它有属性x和y可以用于定位单元格,有一个属性distance用于记录距离,这是堆数组的排列依据。
另一个类Position是用来维护车位的,即单元格,有属性status用于记录当前单元格的状态,分为"occupied"(已占用)、"available"(可用)和"null"(无效),一个属性name用于记录车辆信息,若车位非占用状态则为空,一个属性t用来记录车辆的入场时间。
初始情况下,定义一个一维数组heap作为Heap类的变量,定义一个二维数组pos作为Position类的变量。根据表格现有信息,初始化这两个数组即可。
入场问题
当车入场时,要做的事很简单。首先就是取到最短可用的车位的坐标,通过获取堆首的信息中x,y属性就可以知道它的坐标。然后在单元格数组pos中,标记指定位置,填入特定信息即可。
出场问题
当车出场时,若输入的车牌在停车场中,那么就恢复指定单元格位置,并且输出指定信息。我们可以在每次删去堆的时候,将其放到数组的末尾,这样出场的时候只需要匹配heapSize + 1到SIZE即可。
代码
代码按照部分展示在本文中,全文代码请看:https://github.com/Aki-Hoyue/parking_system
初始化
堆实现
这里是参考《Introduction to Algorithm》的堆实现部分的JavaScript代码。
伪代码部分以及分析请见:
首先是基本堆函数:
function heapParent(i) {
return Math.floor(i / 2);
}
function left(i) {
return i * 2;
}
function right(i) {
return i * 2 + 1;
}
维护最小堆:
function minHeapify(A, i) {
var l = left(i);
var r = right(i);
var smallest = i;
if (l <= heapSize && A[l].distance < A[i].distance)
smallest = l;
if (r <= heapSize && A[r].distance < A[smallest].distance)
smallest = r;
if (smallest !== i) {
var temp = A[i];
A[i] = A[smallest];
A[smallest] = temp;
minHeapify(A, smallest);
}
}
建堆:
function buildMinHeap(A, n) {
for (var i = Math.floor(n / 2); i >= 1; i--) {
minHeapify(A, i);
}
}
删除堆首并放到堆后:
function extractMinFromHeap(A) {
if (heapSize < 1)
throw new Error("heap underflow");
const minElement = A[1];
A[1] = A[heapSize];
A[heapSize] = minElement;
heapSize --;
minHeapify(A, 1);
return minElement;
}
插入进堆:
function insertHeap(A,index){
if (heapSize == SIZE)
throw new Error("heap overflow");
const element = A[heapSize + 1];
A[++heapSize] = A[index];
A[index] = element;
for (var i = Math.floor(heapSize / 2); i >= 1; i--) {
minHeapify(A, i);
}
}
这是一个非常经典的算法导论的JavaScript实现,接下来就是把堆当做数据结构去使用堆。
堆初始化
var heapSize = 0;
class Heap{
constructor(x,y)
{
this.x = x;
this.y = y;
this.distance = Math.sqrt((x-1)*(x-1) + y * y);
}
}
var heap = new Array(4 * 9);
heap[0] = -1;
var index = 1;
heap[index++] = new Heap(0, 1);
for (var x = 0; x < 4; x++) {
for (var y = 2; y < 9; y++) {
heap[index++] = new Heap(x, y);
}
}
heapSize = index - 1;
const SIZE = heapSize;
buildMinHeap(heap,heapSize);
设置Heap类的三个属性x,y和distance,distance会根据x,y的值自动计算,初始化0位置为一个非常小的值,这里因为距离是正数就设置为-1。index
作为堆数组的索引,创建每个位置上的Heap类元素。
设置一个常量SIZE,作为最大的情况,之后检测的时候用到。
单元格初始化
单元格就简单很多,根据之前的表格初始化即可,如下:
y \ x | A0 | B1 | C2 | D3 |
---|---|---|---|---|
8 | ||||
7 | ||||
6 | ||||
5 | ||||
4 | ||||
3 | ||||
2 | ||||
1 | ||||
0 | Entrance |
class Position {
constructor(status, name, t) {
this.status = status;
this.name = name;
this.t = t;
}
}
var pos = new Array(4);
for (var x = 0; x < 4; x++) {
pos[x] = new Array(9);
for (var y = 0; y < 9; y++) {
if (!y || (y == 1 && x)) {
pos[x][y] = new Position("null", "", "");
continue;
}
pos[x][y] = new Position("available", "", "");
}
}
当页面被加载的时候,还需要初始化单元格的背景颜色:
function loding(){
var table = document.getElementById("ParkingArea");
for(var i = 0; i < 9; i++) //y
for(var j = 0; j < 4; j++) //x
{
var cell = table.rows[9-i].cells[j+1];
if(cell.innerHTML == "Entrance") continue;
if(pos[j][i].status == "null")
cell.style.backgroundColor = "aquamarine";
else cell.style.backgroundColor = "green";
}
}
入场
我们需要考虑一些意外条件,先行判断:
- 堆(停车场)是否已满。
- 输入的入场时间是否符合标准。
- 车牌是否为空或者已经停入。
我们需要先特判这些问题:
- 对于堆是否已满,只需要检查heapSize的大小(heapSize < 1则已满)即可。
- 对于时间的格式,我们提示用户应该输入格式为“yyyy/mm/dd hh:mm:ss”,并且使用正则表达式来检查用户的输入。
- 对于车牌,首先先判断是否为空,再在heap数组中heapSize + 1到SIZE之间(这之间的是已经入场的位置)查找是否有相同的车牌。
于是代码为:
function input()
{
if (heapSize < 1)
{
window.alert("The parking area is full!");
throw new Error("heap overflow.");
}
var entranceTime = document.getElementById("entrance_time");
var entranceName = document.getElementById("entrance_name");
var acTest = /^([0-9]{4})\/([0-1][0-9])\/([0-3][0-9]) ([0-2][0-9]):([0-5][0-9]):([0-5][0-9])$/;
if(!acTest.test(entranceTime.value))
{
window.alert("The time is not formatted correctly!");
throw new Error("Time format error.");
}
if(!entranceName.value)
{
window.alert("The name of license could not be empty!");
throw new Error("name error.");
}
for(var i = heapSize + 1; i <= SIZE; i++)
{
var temp = heap[i];
if(pos[temp.x][temp.y].name == entranceName.value)
{
window.alert("The car has already been parked!");
throw new Error("repark error.");
}
}
var parkArea = extractMinFromHeap(heap);
pos[parkArea.x][parkArea.y].status = 'occupied';
pos[parkArea.x][parkArea.y].name = entranceName.value;
pos[parkArea.x][parkArea.y].t = entranceTime.value;
var table = document.getElementById("ParkingArea");
var cell = table.rows[9-parkArea.y].cells[parkArea.x+1];
cell.style.backgroundColor = "red";
cell.innerHTML = entranceName.value;
cell.style.color = "green";
var info = document.getElementById("info_box");
info.innerHTML = "<b>" + entranceName.value + "</b> parks at <b>" +
String.fromCharCode(parkArea.x + 65) + parkArea.x + parkArea.y + "</b>";
}
这个代码它会检查停车场是否已满,如果已满,就会显示一个警告讯息并抛出一个错误。接著,它会检查输入的时间和车牌是否符合格式要求,如果不符合,就会显示一个警告讯息并抛出一个错误。如果输入的车牌已经被停在停车场上,就会显示一个警告讯息并抛出一个错误。如果以上都没有问题,就会从停车场中取出一个空格并将车辆停在上面,并在停车场的表格中显示车辆的车牌和停车时间。最后,它会显示一个讯息,显示车辆的车牌和停车的位置。
出场
对于出场,我们同样考虑如下特殊情况:
- 堆(停车场)是否已满。
- 输入的出场时间是否符合标准,是否满足出场时间必须晚于入场时间。
- 车牌是否为空已经停入。
解决方法还是如上入场内容。特别的是,因为要计算时间差值,要求出场时间必须晚于入场时间。
代码如下:
function output()
{
var time = document.getElementById("out_time");
var acTest = /^([0-9]{4})\/([0-1][0-9])\/([0-3][0-9]) ([0-2][0-9]):([0-5][0-9]):([0-5][0-9])$/;
if(!acTest.test(time.value))
{
window.alert("The time is not formatted correctly!");
throw new Error("Time format error.");
}
var outTime = new Date(time.value);
var outName = document.getElementById("out_name");
if(!outName.value)
{
window.alert("The name of license could not be empty!");
throw new Error("name error.");
}
var flag = 0;
for(var i = heapSize + 1; i <= SIZE; i++)
{
var temp = heap[i];
if(pos[temp.x][temp.y].status == 'occupied' && pos[temp.x][temp.y].name == outName.value)
{
flag = 1;
var inTime = new Date(pos[temp.x][temp.y].t);
var diff = (outTime.getTime() - inTime.getTime()) / (1000 * 60 * 60);
if(diff < 0)
{
window.alert("The exit time must not be less than the entry time!");
throw new Error("Time error.");
}
var fee = wealth(Math.ceil(diff));
var table = document.getElementById("ParkingArea");
var cell = table.rows[9-temp.y].cells[temp.x+1];
cell.style.backgroundColor = "green";
cell.innerHTML = "";
var info = document.getElementById("info_box");
info.innerHTML = "<b>" + outName.value + "</b> was parked for <b>" + Math.floor(diff) +
"hours and " + Math.round((diff - Math.floor(diff)) * 60) + "minutes</b> from " +
pos[temp.x][temp.y].t + " to " + time.value + "<br>Fee: <b>" + fee + "</b> yuan";
pos[temp.x][temp.y].name = "";
pos[temp.x][temp.y].t = "";
pos[temp.x][temp.y].status = "available";
insertHeap(heap,i);
}
}
if(!flag)
{
window.alert("License mismatch!");
throw new Error("No parking.");
}
}
它首先获取一个时间值和一个车辆牌照号码,然后检查这些值是否符合格式要求。接下来,它将出场时间转换为Date对象,并检查车辆牌照号码是否存在于堆中。如果存在,则计算出场时间与入场时间之间的差异,并根据差异计算费用。然后,它将车辆牌照号码从堆中删除,并将车辆状态设置为“available”,并将车辆所在的位置设置为空闲。最后,它将信息显示在页面上,并将页面背景颜色设置为绿色。如果车辆牌照号码不存在于堆中,则显示警告信息。如果时间格式不正确或车辆牌照号码为空,则抛出错误。
费用结算
费用计算就比较简单了,计算公式就是1小时免费,超过一小时收费(t-1)元。代码如下:
function wealth(t) {
return (t > 0)? (t - 1) : 0;
}
HTML
最后在HTML页面中,我设计了左右结构的视图,如下图所示:
代码为:
<!DOCTYPE html>
<html>
<head>
<title>Parking System</title>
<link rel="stylesheet" href="style.css">
<script src="parking.js" async></script>
<meta http-equiv="Content-Script-Type" content="text/javascript" />
</head>
<body onload="loding()">
<table>
<tr>
<td width="50%">
<table id="ParkingArea">
<caption>Parking Area</caption>
<tr>
<th></th>
<th>A / 0</th>
<th>B / 1</th>
<th>C / 2</th>
<th>D / 3</th>
</tr>
<tr>
<th>8</th>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th>7</th>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th>6</th>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th>5</th>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th>4</th>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th>3</th>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th>2</th>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th>1</th>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th>0</th>
<td></td>
<td id="entrance_point">Entrance</td>
<td></td>
<td></td>
</tr>
</table>
</td>
<td width="50%">
<h2>Dashboard</h2>
<form id="parking_form">
<fieldset>
<legend>Enter</legend>
<label for="entrance_time">Entrance Time:</label>
<input type="text" id="entrance_time" name="entrance_time" maxlength="80" placeholder="yyyy/mm/dd hh:mm:ss" size="30" required><br>
<label for="entrance_name" >License:</label>
<input type="text" id="entrance_name" name="entrance_name" maxlength="30" size="30" required><br>
<input type="button" value="Park" onclick="input()">
</fieldset>
<fieldset>
<legend>Exit</legend>
<label for="out_time">Exit Time:</label>
<input type="text" id="out_time" name="out_time" maxlength="80" placeholder="yyyy/mm/dd hh:mm:ss" size="30" required><br>
<label for="out_name">License:</label>
<input type="text" id="out_name" name="out_name" maxlength="30" size="30" required><br>
<input type="button" value="Check Out" onclick="output()">
</fieldset>
<fieldset>
<legend>Output</legend>
<label id="info_box">Information here...</label><br><br>
<label>Notice: The format of the time must be "<em>yyyy/mm/dd hh:mm:ss</em>"</label><br>
<label>Comment: a cell in <span style="color: aquamarine;">aquamarine</span> is <em>unavailable</em>, and that in <span style="color: green;">green</span> is <em>available</em>, in <span style="color: red;">red</span> is <em>occupied</em>.</label><br>
<input type="reset" value="Clear">
</fieldset>
</form>
</td>
</tr>
</table>
</body>
</html>
CSS
这个CSS设计得就比较简单了,如下:
#ParkingArea{
width: 100%;
border-collapse: collapse;
float: left;
}
table caption{
font-size: 2em;
font-weight: bold;
margin: 1em 0;
}
#ParkingArea th,#ParkingArea td{
border: 1px solid black;
text-align: center;
padding: 14px 0;
}
#ParkingArea tbody tr{
background-color: #eee;
}
#ParkingArea th{
background-color:cadetblue;
color:whitesmoke;
}
#entrance_point{
background-color: goldenrod;
color: purple;
}
h2{
font-size: 2em;
font-weight: bold;
text-align: center;
}
form{
font-size: 14pt;
}
后记
项目在线demo地址:https://park.hoyue.eu.org/
好了,这个小项目就简单地完成了,这个项目联系了算法与WEB技术,是个不错的小实践。
Comments 1 条评论
博主 尼斯里