自从某FFF团把情人节电影院的单号位置电影票全买走之后,电影院发现,不能再这么纵容他们了,于是就给了售票系统验证用户选座的要求。
从老系统重构过来,选座验证逻辑也是下手的好机会,经过优化,效果不错
优化结果
原选座判定
function seatPolicy (event, $seat) {
...这里有257行...
}
新版选座判定
validateSelect(){
...这里有29行...
}
实现的最终效果是相同的,除却之前提到的Model Class带来的进一步抽象之外,这里还将判断逻辑巧妙的缩减,将分散的座位判断改换为连续的模式验证。
回到出发点
原始代码很复杂,所以需要去找这里的最终逻辑;经过各种沟通,发觉这里的出发点其实很简单:中间不要剩余一个座位。
如果中间只有一个位置,那么情侣就没办法买,只能卖给单身狗了,这怎么可以呢
但是电影厅的座位分布并不是理想化的,而且卖出去的座位分布也可能是各种各样,容易产生各种情况,也导致了原始的判断逻辑异常复杂。
但是经过仔细思考,可以发现这里有很大的简化空间。
逻辑梳理
首先,每次判断只需要对单行来进行,(毕竟这个是为了情侣考虑的,基本上情侣左右相邻,而不会前后相邻而坐),那么就有这么几种情况是不可以的:
- 刚选的两个座位中间有一个空位
- 刚选的座位和其他位置中间有一个空位
后者展开为:
- 刚选的两个座位中间有一个空位
- 刚选的座位和已售出的座位中间有一个空位
- 刚选的座位和无座位置(没有椅子的位置)中间有一个空位
- 刚选的座位和影厅边缘中间有一个空位
其中后边几种可以归纳起来,就会变成:
- 刚选的两个座位中间有一个空位
- 刚选的座位和(已售出的座位或无座位置或影厅边缘)中间有一个空位
但是这个第二种情况有特殊情况是可以选择的,即:如果只有两个空位(旁边是已售出的座位或无座位置或影厅边缘),则可以只选其中一个位置。(不然单身童鞋可能面临还有俩位置却买不了票的奇葩情况)
明明剩俩座位却不能只买一张,看个电影也要欺负单身狗,这世界没救了
所以讲上述条件的第二个加上限制,就会变成 以下两种情况是不可以的:
- 刚选的两个座位中间有一个空位
- 刚选的座位和(已售出的座位或无座位置或影厅边缘)中间有一个空位,但是另外一边是(已售出的座位或无座位置或影厅边缘)的情况除外
这里可以想到,(已售出的座位或无座位置或影厅边缘)的情况除外,那不就是可选的空座吗?于是就可以转化为:
- 刚选的两个座位中间有一个空位
- 刚选的座位和(已售出的座位或无座位置或影厅边缘)中间有一个空位,但是另外一边也必须是可选的空位
转为代码
逻辑整理的差不多了,那么可以开始转化为代码了,如果用一般的思路,大概需要双重循环加上很多个if判断语句,上述的简洁思想就没办法传承到代码中了
这时候,我想到一个很方便的方式,我们这里要做的事情基本上就是pattern recognizing,即模式识别;想到这里,就可以想到作为擅长处理字符串的js有一个内置大杀器,即regex 正则表达式。正则表达式最擅长的就是字符串模式识别、模式套用。
那么想到这里,我们要做的就是把每行的座位先转换成字符串,然后用相应的pattern去套,看是不是能够匹配了。
这里这样去转换:
| 说明 | 代码 |
|---|---|
| 用户选择的座位: | V_SELECT='S' |
| 边缘: | V_EDGE='E' |
| 没有座: | V_VOID='V' |
| 已经卖出去: | V_LOCK='L' |
| 空位: | V_AVAILABLE='A' |
let oneBlock=`(${V_EDGE}|${V_VOID}|${V_LOCK})`;
let S=V_SELECT,
A=V_AVAILABLE,
B=oneBlock;
然后将我们的规则转化为正则表达式:
- 刚选的两个座位中间有一个空位
-
S+AS+
-
- 刚选的座位和(已售出的座位或无座位置或影厅边缘)中间有一个空位,但是另外一边也必须是可选的空位
-
BAS+A+ -
A+S+AB
-
那么这里的验证选座的代码部分就是这个样子:
rowToValidatingString(row){
return V_EDGE+ (row.map((seat)=>{return seat.toValidatingChar()})).join("") +V_EDGE;
}
validateSelect(){
let oneBlock=`(${V_EDGE}|${V_VOID}|${V_LOCK})`;
let S=V_SELECT,
A=V_AVAILABLE,
B=oneBlock;
let leftOneSpace= `${B}${A}${S}+${A}+`;
let rightOneSpace= `${A}+${S}+${A}${B}`;
let centerOneSpace= `${S}+${A}${S}+`;
let tip='请不要留下单独空座';
let valis=[
[new RegExp(leftOneSpace),tip],
[new RegExp(rightOneSpace),tip],
[new RegExp(centerOneSpace),tip],
];
for (let i=0; i < this.matrix.length; i++){ let oneRow = this.matrix[i];
if(!oneRow){continue}
let s=this.rowToValidatingString(oneRow);
for (let j=0; j<valis.length; j++){ let oneVali = valis[j];
if(oneVali[0].test(s)) {
return {
validate:false,
reason:oneVali[1]
}
}
}
}
return {
validate:true,
reason:null
}
}
有两个循环,一个是循环所有座位行,一个是循环这几个正则表达式,然后每行都会被每个正则表达式所验证,如果验证通过了,说明选座是不合法的,就会在界面提示,阻挡下一部动作。