选座验证的优化

也许你看到过这个新闻:

也许你还看过这个图:

自从某FFF团把情人节电影院的单号位置电影票全买走之后,电影院发现,不能再这么纵容他们了,于是就给了售票系统验证用户选座的要求。

在重构系统过程中,发现原来的验证逻辑非常复杂,搬运逻辑不如直接重写,选座验证逻辑也是下手的好机会

回到出发点

原始代码很复杂,所以需要去找这里的原始需求;经过各种沟通,发觉这里的出发点其实很简单:中间不要剩余一个座位。

如果中间只有一个位置,那么情侣就没办法买,只能卖给单身狗了,这怎么可以呢

正经一点说就是:

避免产生新的单一空余座位

但是电影厅的座位分布并不是理想化的,而且卖出去的座位分布也可能是各种各样,容易产生各种情况,也导致了原始的判断逻辑异常复杂。

原始的逻辑类似上图,由许多if嵌套组成

思考路径

直接从旧代码进行梳理比较麻烦,不如从原始需求着手

电影院选座场景目前已经被简化为类似上图的模型,里面包含了电影院的座位排布信息,以及已经出售的座位信息(红色),以及没有座位的位置(通道等)

如果我选择了上图绿色的座位,那么其左边就会有一个单独的空位,这也是需要避免的部分

那么,是不是在我选择的座位旁边有一个空位,再旁边有一个其他位,就是要避免的情况呢?

再展开一下,其他座位包含了上图所述的四种情况:另一个我选择的座位、已售座位、无座椅位置、场地边缘

但是有一种情况是例外,如上图,我选了中间任意一个位置,都会造成旁边的空位变成单独位置,但是这样是一种可以接受的场景;

否则可能会出现整场(或者某行)剩余两个座位,却买不了一张票的情况。

再进行一下整理,如上图所示,“绿空绿”的情况下,可以肯定是至少三个连续可选座位,所以不用考虑 可能是中间只剩余两个可选座位的情况

“绿空红”,“绿空无”,“绿空边” 的情况下,需要考虑可能是中间只剩余两个可选座位的情况。

本图绿框里面的即为前者,红框里面的情况即为后者。

转化模型

思路已经整理好了,怎么把这个逻辑转为代码呢?

也许很多人第一时间想到的是用if(很多的if),但是会导致代码冗长且难以理解。

仔细想想,这个思路其实就是一种模式匹配,而且是一维(线性)的,说道线性模式匹配,其实js中有一个利器。

没错!就是Regex,正则表达式

那么我们继续整理

因为我们可以选择多个座位,所以上面的思路整理为这样几种情况:

  • N绿,空,N绿 (我刚选择的座位中间出现了一个单独的空位)
  • 空,N绿,空,红或无或边 (我刚选择的座位,和右边其他非空位情况的位置产生了一个单独空位,而且左边还有一个空位可以选)
  • 红或无或边,空,N绿,空 (我刚选择的座位,和左边其他非空位情况的位置产生了一个单独空位,而且右边还有一个空位可以选)

后面两种都是在至少有三个连续空位,所以可以不留单独空位的情况下,产生了一个单独空位

(N表示一个或多个)

是不是感觉有点熟悉?对,N就和正则表达式中的加号是一样的,那么整理一下我们可以写成:

  • 绿写成S
  • 空写成A
  • 边(边缘)写成E,无(无座椅)写成V,红(已售)写成L

就可以写成图上的三个正则表达式

写成代码就是:

再使用正则表达式的 test 来验证每行有没有以上三种情况即可。如果有则当前选座不合法

优化结果

原来的259行多重嵌套的if代码

重写为29行逻辑清晰的代码

 

 

 

 

 

 

 

 

 


以下为第一版文章后半部分

 

 

 

 

逻辑梳理

首先,每次判断只需要对单行来进行,(毕竟这个是为了情侣考虑的,基本上情侣左右相邻,而不会前后相邻而坐),那么就有这么几种情况是不可以的:

  • 刚选的两个座位中间有一个空位
  • 刚选的座位和其他位置中间有一个空位

后者展开为:

  • 刚选的两个座位中间有一个空位
  • 刚选的座位和已售出的座位中间有一个空位
  • 刚选的座位和无座位置(没有椅子的位置)中间有一个空位
  • 刚选的座位和影厅边缘中间有一个空位

其中后边几种可以归纳起来,就会变成:

  • 刚选的两个座位中间有一个空位
  • 刚选的座位和(已售出的座位或无座位置或影厅边缘)中间有一个空位

但是这个第二种情况有特殊情况是可以选择的,即:如果只有两个空位(旁边是已售出的座位或无座位置或影厅边缘),则可以只选其中一个位置。(不然单身童鞋可能面临还有俩位置却买不了票的奇葩情况)

明明剩俩座位却不能只买一张,看个电影也要欺负单身狗,这世界没救了

所以讲上述条件的第二个加上限制,就会变成 以下两种情况是不可以的:

  • 刚选的两个座位中间有一个空位
  • 刚选的座位和(已售出的座位或无座位置或影厅边缘)中间有一个空位,但是另外一边是(已售出的座位或无座位置或影厅边缘)的情况除外

这里可以想到,(已售出的座位或无座位置或影厅边缘)的情况除外,那不就是可选的空座吗?于是就可以转化为:

  • 刚选的两个座位中间有一个空位
  • 刚选的座位和(已售出的座位或无座位置或影厅边缘)中间有一个空位,但是另外一边也必须是可选的空位

转为代码

逻辑整理的差不多了,那么可以开始转化为代码了,如果用一般的思路,大概需要双重循环加上很多个if判断语句,上述的简洁思想就没办法传承到代码中了

这时候,我想到一个很方便的方式,我们这里要做的事情基本上就是pattern recognizing,即模式识别;想到这里,就可以想到作为擅长处理字符串的js有一个内置大杀器,即regex 正则表达式。正则表达式最擅长的就是字符串模式识别、模式套用。

那么想到这里,我们要做的就是把每行的座位先转换成字符串,然后用相应的pattern去套,看是不是能够匹配了。

这里这样去转换:

说明代码
用户选择的座位:V_SELECT='S'
边缘:V_EDGE='E'
没有座:V_VOID='V'
已经卖出去:V_LOCK='L'
空位:V_AVAILABLE='A'

然后将我们的规则转化为正则表达式:

  • 刚选的两个座位中间有一个空位
    • S+AS+
  • 刚选的座位和(已售出的座位或无座位置或影厅边缘)中间有一个空位,但是另外一边也必须是可选的空位
    • BAS+A+
    • A+S+AB

那么这里的验证选座的代码部分就是这个样子:

 

有两个循环,一个是循环所有座位行,一个是循环这几个正则表达式,然后每行都会被每个正则表达式所验证,如果验证通过了,说明选座是不合法的,就会在界面提示,阻挡下一部动作。

优化结果

原选座判定

新版选座判定

实现的最终效果是相同的,这里还将判断逻辑巧妙的缩减,将分散的座位判断改换为连续的模式验证。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注