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获取xml节点的最大值(实现代码)
Dec 11 Javascript
js检测用户输入密码强度
Oct 22 Javascript
每天一篇javascript学习小结(Date对象)
Nov 13 Javascript
JS获取当前使用的浏览器名字以及版本号实现方法
Aug 19 Javascript
Bootstrap 轮播(Carousel)插件
Dec 26 Javascript
微信小程序中hidden不生效原因的解决办法
Apr 26 Javascript
JS实现匀加速与匀减速运动的方法示例
Sep 04 Javascript
深入理解Vue 组件之间传值
Aug 16 Javascript
详解使用webpack+electron+reactJs开发windows桌面应用
Feb 01 Javascript
Webpack4 使用Babel处理ES6语法的方法示例
Mar 07 Javascript
详解Vue依赖收集引发的问题
Apr 22 Javascript
Vue指令实现OutClick的示例
Nov 16 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
在任意字符集下正常显示网页的方法一
2007/04/01 PHP
php读取html并截取字符串的简单代码
2009/11/30 PHP
PHP 写文本日志实现代码
2010/05/18 PHP
PHP 处理TXT文件(打开/关闭/检查/读取)
2013/05/13 PHP
ThinkPHP的Widget扩展实例
2014/06/19 PHP
Thinkphp关闭缓存的方法
2015/06/26 PHP
详解php实现页面静态化原理
2017/06/21 PHP
关于图片验证码设计的思考
2007/01/29 Javascript
window.onload和$(function(){})的区别介绍
2013/10/30 Javascript
nodejs实现黑名单中间件设计
2014/06/17 NodeJs
jQuery晃动层特效实现方法
2015/03/09 Javascript
使用nodejs开发cli项目实例
2015/06/03 NodeJs
Angular4学习笔记之实现绑定和分包
2017/08/01 Javascript
Angular5中提取公共组件之radio list的实例代码
2018/07/10 Javascript
Vue+webpack+Element 兼容问题总结(小结)
2018/08/16 Javascript
爬虫利器Puppeteer实战
2019/01/09 Javascript
layui 弹出层回调获取弹出层数据的例子
2019/09/02 Javascript
40行代码把Vue3的响应式集成进React做状态管理
2020/05/20 Javascript
vue通过接口直接下载java生成好的Excel表格案例
2020/10/26 Javascript
vue-amap根据地址回显地图并mark的操作
2020/11/03 Javascript
vue 授权获取微信openId操作
2020/11/13 Javascript
python中实现精确的浮点数运算详解
2017/11/02 Python
对Python中gensim库word2vec的使用详解
2018/05/08 Python
python实现广度优先搜索过程解析
2019/10/19 Python
Python倒排索引之查找包含某主题或单词的文件
2019/11/13 Python
tensorflow 分类损失函数使用小记
2020/02/18 Python
Python使用多进程运行含有任意个参数的函数
2020/05/02 Python
Numpy ndarray 多维数组对象的使用
2021/02/10 Python
英国最好的温室之家:Greenhouses Direct
2019/07/13 全球购物
阿迪达斯中国官网:Adidas中国
2020/12/14 全球购物
电大毕业生自我鉴定
2014/04/10 职场文书
娱乐节目策划方案
2014/06/10 职场文书
赤壁观后感(2)
2015/06/15 职场文书
2019同学聚会主持词
2019/05/06 职场文书
导游词之苏州阳澄湖
2019/11/15 职场文书
实战Python爬虫爬取酷我音乐
2022/04/11 Python