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 相关文章推荐
国外的为初学者写的JavaScript教程
Jun 09 Javascript
javascript 进阶篇3 Ajax 、JSON、 Prototype介绍
Mar 14 Javascript
通过javascript把图片转化为字符画
Oct 24 Javascript
使用js如何实现全选与全不选
Dec 30 Javascript
jQuery拖动div、移动div、弹出层实现原理及示例
Apr 08 Javascript
js基于cookie记录来宾姓名的方法
Jul 19 Javascript
JS防止网页被嵌入iframe框架的方法分析
Sep 13 Javascript
谈谈第三方App接入微信登录 解读
Dec 27 Javascript
深入理解Vue 的条件渲染和列表渲染
Sep 01 Javascript
iview通过Dropdown(下拉菜单)实现的右键菜单
Oct 26 Javascript
JS实现的Object数组去重功能示例【数组成员为Object对象】
Feb 01 Javascript
js实现简易拖拽的示例
Oct 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
html中select语句读取mysql表中内容
2006/10/09 PHP
用PHP编写和读取XML的几种方式
2013/01/12 PHP
PHP-Java-Bridge使用笔记
2014/09/22 PHP
php中HTTP_REFERER函数用法实例
2014/11/21 PHP
php实现图片上传、剪切功能
2016/05/07 PHP
动态创建script标签实现跨域资源访问的方法介绍
2014/02/28 Javascript
使用jQuery实现input数值增量和减量的方法
2015/01/24 Javascript
jQuery实现的产品自动360度旋转展示特效源码分享
2015/08/21 Javascript
JS实现鼠标框选效果完整实例
2016/06/20 Javascript
Node.js开发教程之基于OnceIO框架实现文件上传和验证功能
2016/11/30 Javascript
使用 vue-i18n 切换中英文效果
2018/05/23 Javascript
js form表单input框限制20个字符,10个汉字代码实例
2019/04/12 Javascript
vue视频播放插件vue-video-player的具体使用方法
2019/11/08 Javascript
python创建和使用字典实例详解
2013/11/01 Python
python使用webbrowser浏览指定url的方法
2015/04/04 Python
Python获取任意xml节点值的方法
2015/05/05 Python
python提取页面内url列表的方法
2015/05/25 Python
Python实现的爬虫功能代码
2017/06/24 Python
详解python Todo清单实战
2018/11/01 Python
使用python将mysql数据库的数据转换为json数据的方法
2019/07/01 Python
PyCharm更改字体和界面样式的方法步骤
2019/09/27 Python
浅谈python中统计计数的几种方法和Counter详解
2019/11/07 Python
Python如何实现的二分查找算法
2020/05/27 Python
python 制作网站小说下载器
2021/02/20 Python
Black Halo官方网站:购买连衣裙、礼服和连体裤
2018/06/13 全球购物
French Connection官网:女装、男装及家居用品
2019/03/18 全球购物
DIY蛋糕店的创业计划书范文
2013/12/26 职场文书
库房管理员岗位职责
2014/03/09 职场文书
物业保安岗位职责
2014/07/02 职场文书
大学计划书范文800字
2014/08/14 职场文书
合作协议书模板2014
2014/09/26 职场文书
高校师德师风自我剖析材料
2014/09/29 职场文书
海南召开党的群众路线教育实践活动总结大会新闻稿
2014/10/21 职场文书
公诉意见书范文
2015/06/05 职场文书
nginx作grpc的反向代理踩坑总结
2021/07/07 Servers
html5+实现plus.io进行拍照和图片等获取
2022/06/01 HTML / CSS