2753 字
14 分钟
【简单小项目】JavaScript实现停车场管理系统

前言#

最近,帮一个同学编写了一个可视化的停车场管理系统程序,用于辅助他们的管理学的 pre。所以这个小项目也非常的简单,刚好运用了之前算法设计与分析与 WEB 技术的结合。既然是可视化程序,第一反应可以使用 C++,但是恰好上学期学习了 JavaScript,于是就用 JavaScript 编写这个程序。

项目在线 demo 地址:https://park.hoyue.eu.org/

信息描述#

对于一个有限的停车场空间内,有一个固定的入口和出口。例子中设置为 4 列车位,除了最左侧的一列有 8 个车位外,每一列有 7 个车位。 入口和出口在同一个位置(1,0) ,每一列从左往右依次以 A0/B1/C2/D3 开头命名车位。根据纵坐标,也依次从 0 开始向上递增。概念图如下:

我们抽象成表格,就是如下的情况:

y \ xA0B1C2D3
8
7
6
5
4
3
2
1
0Entrance

这是按照坐标轴的表格,其中如果这个位置是可用的,那么它的背景颜色就是 绿色(green)。如果这个位置是无效的,那么它的背景颜色就是 海蓝宝石色(aquamarine)。如果它的背景颜色是 红色(red) 的,那么表示该位置已经被占用,存在车辆了。

现在需要实现如下功能:

  1. 车辆停入:输入入场时间和车牌号,将车辆停入 距离入口最近 的且 未被占用 的车位,并显示停入信息。
  2. 车辆驶离:输入出场时间和车牌号, 释放被占用的车位,并输出驶离信息。
  3. 费用计算:输出停车产生的费用,该例子中为比较简单的 (t-1)×1
  4. 表格显示:在表格上可视化展示停车位情况,在被占用的停车位中需要写入车牌号。

问题分析#

距离最短问题#

对于这个问题,还是比较简单解决的。首先是解决距离问题。

因为要求中, 每个格子距离起点的距离是固定的,我们不必每次停入的时候都重新计算找最小值,只需要一直维护一个容易保持有最小值的数据结构就可以了。于是很容易想到 使用一个最小堆去存储这些距离

对于在 JavaScript 中实现堆,只需要稍微根据《Introduction to Algorithm》中的伪代码进行修改即可。

信息存储#

这个问题中有很多信息,该如何存储呢?我想到的是使用两个类。

  • 一个类是 Heap,用于维护堆数组。它有属性 x 和 y 可以用于定位单元格,有一个属性 distance 用于记录距离,这是堆数组的排列依据。
  • 另一个类 Position 是用来维护车位(单元格),有属性 status 用于记录当前单元格的状态,分为 “occupied”(已占用)、“available”(可用)和 “null”(无效),一个属性 name 用于记录车辆信息,若车位非占用状态则为空,一个属性 t 用来记录车辆的入场时间。

初始情况下,定义一个一维数组 heap 作为 Heap 类的变量,定义一个二维数组 pos 作为 Position 类的变量。根据表格现有信息,初始化这两个数组即可。

入场问题#

当车入场时,要做的事很简单。首先就是取到最短可用的车位的坐标,通过获取堆首的信息中 x, y 属性就可以知道它的坐标。然后在单元格数组 pos 中,标记指定位置,填入特定信息即可。

出场问题#

当车出场时,若输入的车牌在停车场中,那么就恢复指定单元格位置,并且输出指定信息。我们可以在每次删去堆的时候,将其放到数组的末尾,这样出场的时候只需要匹配 heapSize + 1SIZE 即可。


代码#

代码按照部分展示在本文中,全文代码请看:https://github.com/Aki-Hoyue/parking_system

初始化#

堆实现#

这里是参考《Introduction to Algorithm》的堆实现部分的 JavaScript 代码。

伪代码部分以及分析请见:

https://hoyue.fun/algorithm_4.html

首先是基本堆函数:

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]](file://pointer_c3.xml);
A[[1]](file://pointer_c3.xml) = 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 \ xA0B1C2D3
8
7
6
5
4
3
2
1
0Entrance
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.rows9-i].cells+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”,并且使用正则表达式来检查用户的输入。
  • 对于车牌,首先先判断是否为空,再在 heap 数组中 heapSize + 1SIZE 之间(这之间的是已经入场的位置)查找是否有相同的车牌。

于是代码为:

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.rows9-temp.y].cells.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.");
}
}

费用结算#

费用计算就比较简单了,计算公式就是 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 技术,是个不错的小实践。

【简单小项目】JavaScript实现停车场管理系统
https://hoyue.fun/parking_javascript.html
作者
Hoyue
发布于
2023-06-26
最后更新于
2023-06-26
许可协议
CC BY-NC-SA 4.0
评论