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 left,right,mid函数
Jun 10 Javascript
基于jQuery的固定表格头部的代码(IE6,7,8测试通过)
May 18 Javascript
12306验证码破解思路分享
Mar 25 Javascript
jQuery实现图片与文字描述左右滑动自动切换的方法
Jul 27 Javascript
JavaScript动态设置div的样式的方法
Dec 26 Javascript
js实现上传图片及时预览
May 07 Javascript
浅谈JS继承_借用构造函数 &amp; 组合式继承
Aug 16 Javascript
详解JavaScript时间处理之几个月前或几个月后的指定日期
Dec 21 Javascript
JavaScript仿聊天室聊天记录
Dec 27 Javascript
JS基于面向对象实现的多个倒计时器功能示例
Feb 28 Javascript
Vue-cli创建项目从单页面到多页面的方法
Sep 20 Javascript
elementui的默认样式修改方法
Feb 23 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 和 XML: 使用expat函数(三)
2006/10/09 PHP
Ajax+PHP 边学边练 之二 实例
2009/11/24 PHP
PDO防注入原理分析以及注意事项
2015/02/25 PHP
PHP实现获取并生成数据库字典的方法
2016/05/04 PHP
探究Laravel使用env函数读取环境变量为null的问题
2016/12/06 PHP
php中的钩子理解及应用实例分析
2019/08/30 PHP
javascript实现 在光标处插入指定内容
2007/05/25 Javascript
Javascript图像处理—虚拟边缘介绍及使用方法
2012/12/27 Javascript
Function.prototype.apply()与Function.prototype.call()小结
2016/04/27 Javascript
JS识别浏览器类型(电脑浏览器和手机浏览器)
2016/11/18 Javascript
解析NodeJS异步I/O的实现
2017/04/13 NodeJs
JS实现定时任务每隔N秒请求后台setInterval定时和ajax请求问题
2017/10/15 Javascript
聊聊Vue 中 title 的动态修改问题
2019/06/11 Javascript
JavaScript this在函数中的指向及实例详解
2019/10/14 Javascript
[46:47]完美世界DOTA2联赛PWL S2 FTD vs Magma 第二场 11.20
2020/11/23 DOTA
python中getattr函数使用方法 getattr实现工厂模式
2014/01/20 Python
Python编程之属性和方法实例详解
2015/05/19 Python
解决Django生产环境无法加载静态文件问题的解决
2019/04/23 Python
使用Python检测文章抄袭及去重算法原理解析
2019/06/14 Python
对django中foreignkey的简单使用详解
2019/07/28 Python
英国羊皮鞋类领先品牌:Just Sheepskin
2019/12/12 全球购物
在C#中如何实现多态
2014/07/02 面试题
机电专业个人自荐信格式模板
2013/09/23 职场文书
北大自主招生自荐信
2013/10/19 职场文书
高考自主招生自荐信
2013/10/20 职场文书
初一体育教学反思
2014/01/29 职场文书
大学学习个人的自我评价
2014/02/18 职场文书
地质灾害防治方案
2014/05/14 职场文书
庆祝新中国成立65周年“向国旗敬礼”网上签名寄语
2014/09/27 职场文书
销售业务员岗位职责
2015/02/13 职场文书
微信搭讪开场白
2015/05/28 职场文书
亮剑精神观后感
2015/06/05 职场文书
运动会800米赞词
2015/07/22 职场文书
2019年浪漫婚礼证婚词
2019/06/27 职场文书
2019如何书写演讲稿?
2019/07/01 职场文书
Java并发编程之Executor接口的使用
2021/06/21 Java/Android