Vue实现美团app的影院推荐选座功能【推荐】


Posted in Javascript onAugust 29, 2018

经常用美团app买电影票,不禁对它的推荐选座功能产生了好奇,于是打算自己实现一个类似的算法,美团app的推荐选座界面如下

Vue实现美团app的影院推荐选座功能【推荐】 

最多可以选5个座位,本demo的选座界面如下图

Vue实现美团app的影院推荐选座功能【推荐】 

上图是点击 推荐选座5人

后选出的座位(绿色),这个demo和美团app不同的地方在于可以连续进行推荐选座,美团app点击了推荐选座就必须买票才能继续选择。

本demo采用Vue-cli搭建,github地址点此 ,clone后直接npm start即可进行具体操作

算法思考过程

对于这个推荐座位算法,我尝试了不同场次的电影进行推荐选座,总结出以下几点

(1)推荐算法首先从影院中间排数的后一排的正中间开始搜索如下图

Vue实现美团app的影院推荐选座功能【推荐】 

可以确定是这个逻辑,试了其他几个场次也是一样

(2)优先向后排方向进行搜索,后排搜索完成后再从中间起始位置向前排搜索这个大多数情况是对的,如下图,偶尔会出现不同

Vue实现美团app的影院推荐选座功能【推荐】 

(3)后排搜索完成后,每一行都会有一个结果(每一行的结果是最靠近中轴线的那一组座位),取这些结果中距离中轴线最小的那个结果作为最终结果,而不是距离屏幕越近的

这一点也是大多数情况是对的,有些情况不对,很奇怪

(4)只考虑并排且连续的座位,不能不在一排或者一排中间有分隔,比如过道之类的这一条可以理解,毕竟坐一排肯定观影体验好得多

影院座位数据结构

可以肯定的是用一个二维数组 seatArray 代表影院座位,注意到影院座位分布是不规则的,因此需要确定一个 seatRow 和 seatCol 来确定影院座位的数组尺寸,分别代表行列数,对于那些没有座位的地方, seatArray 对应的位置填-1,下面是座位具体的值和代表的含义

-1 非座位
0  可选座位   (白色)
1  已选座位   (绿色)
2  已购票座位 (红色)

然后在mounted中初始化座位,初始值都为0(可选座位),如下代码

//初始座位数组
  initSeatArray: function(){
  let seatArray = Array(this.seatRow).fill(0).map(()=>Array(this.seatCol).fill(0));
  this.seatArray = seatArray;
  //均分父容器宽度作为座位的宽度
  this.seatSize = this.$refs.innerSeatWrapper
      ? parseInt(parseInt(window.getComputedStyle(this.$refs.innerSeatWrapper).width,10) / this.seatCol,10)
      :0;
  //初始化不是座位的地方
  this.initNonSeatPlace();
  },
  
  //初始化不是座位的地方
  initNonSeatPlace: function(){
  for(let i=0;i<9;i++){
   this.seatArray[i][0]=-1;
  }
  for(let i=0;i<8;i++){
   this.seatArray[i][this.seatArray[0].length-1]=-1;
   this.seatArray[i][this.seatArray[0].length-2]=-1;
  }
  for(let i=0;i<9;i++){
   this.seatArray[i][this.seatArray[0].length-3]=-1;
  }
  for(let i=0;i<this.seatArray[0].length;i++){
   this.seatArray[2][i]=-1;
  }
  }

初始化好之后用一个二重循环来构建html结构,2个v-for嵌套循环出整个结构,如下代码

<div class="inner-seat-wrapper" ref="innerSeatWrapper" >
  <div v-for="row in seatRow">
  <!--这里的v-if很重要,如果没有则会导致报错,因为seatArray初始状态为空-->
  <div v-for="col in seatCol"
    v-if="seatArray.length>0"
    class="seat"
    :style="{width:seatSize+'px',height:seatSize+'px'}">
   <div class="inner-seat"
    @click="handleChooseSeat(row-1,col-1)"
    v-if="seatArray[row-1][col-1]!==-1"
    :class="seatArray[row-1][col-1]===2?'bought-seat':(seatArray[row-1][col-1]===1?'selected-seat':'unselected-seat')">
   </div>
  </div>
  </div>
</div>

上述的inner-seat类的div就是具体的座位div,v-if说明了如果是-1也就是是过道之类的就不渲染,然后:class一句控制了该座位对应状态的类的值,一个嵌套三目运算符来控制,对于每个座位绑定点击事件 handleChooseSeat(row-1,col-1) 进行状态切换

//处理座位选择逻辑
 handleChooseSeat: function(row,col){
 let seatValue = this.seatArray[row][col];
 let newArray = this.seatArray;
  //如果是已购座位,直接返回
  if(seatValue===2) return
  //如果是已选座位点击后变未选
  if(seatValue === 1){
   newArray[row][col]=0
  }else if(seatValue === 0){
   newArray[row][col]=1
  }
  //必须整体更新二维数组,Vue无法检测到数组某一项更新,必须slice复制一个数组才行
  this.seatArray = newArray.slice();
 },

这里注意vue中改变data中的二维数组必须先缓存二维数组,修改后,最终将二维数组重新赋值,否则修改不生效,因为Vue无法侦测到数组内的变动。

推荐座位的具体代码

首先给每个推荐座位的按钮绑定事件smartChoose

Vue实现美团app的影院推荐选座功能【推荐】 

代码如下

//推荐选座,参数是推荐座位数目
 smartChoose: function(num){
  //找到影院座位水平垂直中间位置的后一排
  let rowStart = parseInt((this.seatRow-1)/2,10)+1;
  //先从中间排往后排搜索
  let backResult = this.searchSeatByDirection(rowStart,this.seatRow-1,num);
  if(backResult.length>0){
   this.chooseSeat(backResult);
   return
  }
  //再从中间排往前排搜索
  let forwardResult = this.searchSeatByDirection(rowStart-1,0,num);
  if(forwardResult.length>0) {
   this.chooseSeat(forwardResult);
   return
  }
  //提示用户无合法位置可选
  alert('无合法位置可选!')
 },

第一步是找到影院座位水平垂直中间位置的后一排,然后调用 this.searchSeatByDirection 进行该方向的搜索,先从中间排往后排搜索,再从中间排往前排搜索。如果任意一个方向搜索到结果,直接返回,否则提示用户无合法位置, chooseSeat 用于改变座位的状态

重点就是 searchSeatByDirection 的实现,代码如下

//向前后某个方向进行搜索的函数,参数是起始行,终止行,推荐座位个数
 searchSeatByDirection: function(fromRow,toRow,num){
 /*
  * 推荐座位规则
  * (1)初始状态从座位行数的一半处的后一排的中间开始向左右分别搜索,取离中间最近的,如果满足条件,
  * 记录下该结果离座位中轴线的距离,后排搜索完成后取距离最小的那个结果作为最终结果,优先向后排进行搜索,
  * 后排都没有才往前排搜,前排逻辑同上
  * (2)只考虑并排且连续的座位,不能不在一排或者一排中间有分隔
  * */

 /*
  * 保存当前方向搜索结果的数组,元素是对象,result是结果数组,offset代表与中轴线的偏移距离
  * {
  * result:Array([x,y])
  * offset:Number
  * }
  */
 let currentDirectionSearchResult = [];
 //确定行数的大小关系,从小到大进行遍历
 let largeRow = fromRow>toRow?fromRow:toRow,
  smallRow = fromRow>toRow?toRow:fromRow;
 //逐行搜索
 for(let i=smallRow;i<=largeRow;i++){
  //每一排的搜索,找出该排里中轴线最近的一组座位
  let tempRowResult = [],
   minDistanceToMidLine=Infinity;
  for(let j=0;j<=this.seatCol - num;j++){
  //如果有合法位置
  if(this.checkRowSeatContinusAndEmpty(i,j,j+num-1)){
   //计算该组位置距离中轴线的距离:该组位置的中间位置到中轴线的距离
   let resultMidPos = parseInt((j+num/2),10);
   let distance = Math.abs(parseInt(this.seatCol/2) - resultMidPos);
   //如果距离较短则更新
   if(distance<minDistanceToMidLine){
   minDistanceToMidLine = distance;
   //该行的最终结果
   tempRowResult = this.generateRowResult(i,j,j+num-1)
   }
  }
  }
  //保存该行的最终结果
  currentDirectionSearchResult.push({
  result:tempRowResult,
  offset:minDistanceToMidLine
  })
 }

 //处理后排的搜索结果:找到距离中轴线最短的一个
 //注意这里的逻辑需要区分前后排,对于后排是从前往后,前排则是从后往前找
 let isBackDir = fromRow < toRow;
 let finalReuslt = [],minDistanceToMid = Infinity;
 if(isBackDir){
  //后排情况,从前往后
  currentDirectionSearchResult.forEach((item)=>{
  if(item.offset < minDistanceToMid){
   finalReuslt = item.result;
   minDistanceToMid = item.offset;
  }
  });
 }else{
  //前排情况,从后往前找
  currentDirectionSearchResult.reverse().forEach((item)=>{
  if(item.offset < minDistanceToMid){
   finalReuslt = item.result;
   minDistanceToMid = item.offset;
  }
  })
 }
 //直接返回结果
 return finalReuslt
 },

代码有点长,不过逻辑不难,就是前面那几条规则的实现,对于每一行的搜索,是可能存在多个合理的座位结果的

Vue实现美团app的影院推荐选座功能【推荐】 

我这里采用的是从左往右遍历,如果是推荐5个座位,先判断1-5位置是否合理,如果合理则记录下其中间位置(3号)到中轴线的距离以及座位结果数组,然后再右移一位检查2-6位置是否合理,如果合理则比较2-6位置的中间位置(4号)距离中轴线的距离和之前的距离,取最短的一个,同时更新座位结果数组。 这样遍历下来,该行的最终结果就能确定, 每一行的最佳结果会存放在currentDirectionSearchResult数组中
然后后排方向的所有排遍历完后,就得到了由每一行最佳结果组成的数组currentDirectionSearchResult,再遍历这个数组根据规则取距离中轴线最近的一个作为 最终返回的结果

这个算法可以优化,直接从中间向2边找,找到就返回,不过写起来有点麻烦,但是效率肯定高。需要注意的是前排情况下要 currentDirectionSearchResult.reverse() 反向数组一下,因为对于前排部分是优先选择前排的后面部分的(谁都不想坐第一排!),同后排相反

最后

这个算法不过有点问题,如下图

Vue实现美团app的影院推荐选座功能【推荐】

最左边的2个绿色座位是最后一次点击推荐选座2人的结果,不过该位置却不如另外一个箭头处那2个位置合理,说明该算法其实不完美,可能上面的分析不到位,其实美团的算法也有问题,如下图

Vue实现美团app的影院推荐选座功能【推荐】

这个推荐的合理位置应该是4个位置往左移动一格,这才是正中央位置,这个推荐的有偏移量,不知道是为啥,网上也没有搜到具体的算法逻辑,只能靠猜想和实验。

总结

以上所述是小编给大家介绍的Vue实现一个美团app的影院推荐选座功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
js实现拉伸拖动iframe的具体代码
Aug 03 Javascript
javascript中setTimeout和setInterval的unref()和ref()用法示例
Nov 26 Javascript
javascript实现倒计时并弹窗提示特效
Jun 05 Javascript
jQuery实现的简单折叠菜单(折叠面板)效果代码
Sep 16 Javascript
jQuery Easyui学习之datagrid 动态添加、移除editor
Jan 27 Javascript
BootStrap实用代码片段之一
Mar 22 Javascript
关于JavaScript数组你所不知道的3件事
Aug 24 Javascript
浅谈Vue.js
Mar 02 Javascript
js异步编程小技巧详解
Aug 14 Javascript
vue实现长图垂直居上 vue实现短图垂直居中
Oct 18 Javascript
详解微信小程序的不同函数调用的几种方法
May 08 Javascript
vue element-ui读取pdf文件的方法
Nov 26 Javascript
Vuex 使用及简单实例(计数器)
Aug 29 #Javascript
浅谈Vue.use的使用
Aug 29 #Javascript
微信小程序支付前端源码
Aug 29 #Javascript
微信小程序使用swiper组件实现类3D轮播图
Aug 29 #Javascript
javascript中UMD规范的代码推演
Aug 29 #Javascript
详解webpack自定义loader初探
Aug 29 #Javascript
在vue中使用SockJS实现webSocket通信的过程
Aug 29 #Javascript
You might like
php强制文件下载而非在浏览器打开的自定义函数分享
2014/05/08 PHP
php代码检查代理ip的有效性
2016/08/19 PHP
thinkphp5框架前后端分离项目实现分页功能的方法分析
2019/10/08 PHP
图片Slider 带左右按钮的js示例
2013/08/30 Javascript
jQuery Mobile 导航栏代码
2013/11/01 Javascript
jquery对单选框,多选框,文本框等常见操作小结
2014/01/08 Javascript
laytpl 精致巧妙的JavaScript模板引擎
2014/08/29 Javascript
JavaScript中的条件判断语句使用详解
2015/06/03 Javascript
jQuery热气球动画半透明背景的后台登录界面代码分享
2015/08/28 Javascript
js获取及判断键盘按键的方法
2015/12/01 Javascript
JS动态给对象添加属性和值的实现方法
2016/10/21 Javascript
jquery滚动条插件(可以自定义)
2016/12/11 Javascript
AngularJS Select(选择框)使用详解
2017/01/18 Javascript
Mongoose中document与object的区别示例详解
2017/09/18 Javascript
JS中offset和匀速动画详解
2018/02/06 Javascript
jQuery中元素选择器(element)简单用法示例
2018/05/14 jQuery
element-ui的回调函数Events的用法详解
2018/10/16 Javascript
微信小程序的开发范式BeautyWe.js入门详解
2019/07/10 Javascript
JS控制GIF图片的停止与显示
2019/10/24 Javascript
[原创]python爬虫(入门教程、视频教程)
2018/01/08 Python
Python决策树之基于信息增益的特征选择示例
2018/06/25 Python
Python反射和内置方法重写操作详解
2018/08/27 Python
python mqtt 客户端的实现代码实例
2019/09/25 Python
python文字转语音的实例代码分析
2019/11/12 Python
DRF框架API版本管理实现方法解析
2020/08/21 Python
python 读取yaml文件的两种方法(在unittest中使用)
2020/12/01 Python
如何利用Python matplotlib绘制雷达图
2020/12/21 Python
Python机器学习工具scikit-learn的使用笔记
2021/01/28 Python
旅游管理实习自我鉴定
2013/09/29 职场文书
高等教育专业自荐信范文
2014/03/26 职场文书
《生命 生命》教学反思
2014/04/19 职场文书
优秀少先队工作者事迹材料
2014/05/13 职场文书
企业授权委托书范本
2014/09/22 职场文书
2014年大学生村官工作总结
2014/11/19 职场文书
大国崛起英国观后感
2015/06/02 职场文书
离职员工给领导和同事的感谢信
2015/11/03 职场文书