当前位置: 首页 > 编程笔记 >

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

翟嘉志
2023-03-14
本文向大家介绍Vue实现美团app的影院推荐选座功能【推荐】,包括了Vue实现美团app的影院推荐选座功能【推荐】的使用技巧和注意事项,需要的朋友参考一下

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

 

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

 

上图是点击 推荐选座5人

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

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

算法思考过程

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

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

 

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

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

 

(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

 

代码如下

//推荐选座,参数是推荐座位数目
 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
 },

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

 

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

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

最后

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

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

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

总结

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

 类似资料:
  • 推荐作为解决信息过载和挖掘用户潜在需求的技术手段,在美团点评这样业务丰富的生活服务电子商务平台,发挥着重要的作用。在美团App里,首页的“猜你喜欢”、运营区、酒店旅游推荐等重要的业务场景,都是推荐的用武之地。 图1 美团首页“猜你喜欢”场景 目前,深度学习模型凭借其强大的表达能力和灵活的网络结构在诸多领域取得了重大突破,美团平台拥有海量的用户与商家数据,以及丰富的产品使用场景,也为深度学习的应用提

  • 推荐系统并不是新鲜的事物,在很久之前就存在,但是推荐系统真正进入人们的视野,并且作为一个重要的模块存在于各个互联网公司,还是近几年的事情。 随着互联网的深入发展,越来越多的信息在互联网上传播,产生了严重的信息过载。如果不采用一定的手段,用户很难从如此多的信息流中找到对自己有价值的信息。 解决信息过载有几种手段:一种是搜索,当用户有了明确的信息需求意图后,将意图转换为几个简短的词或者短语的组合(即q

  • 本文向大家介绍Android实现电影院选座效果,包括了Android实现电影院选座效果的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了Android实现电影院选座效果展示的具体代码,供大家参考,具体内容如下 这是一个简单的电影院选座效果,实现该效果大致分为三步: 1.自定义view进行绘制; 2.手势缩放效果的实现; 3.手势触摸被选和未被选效果的实现; 先来看第一步,效果的绘制;

  • 本文向大家介绍php实现推荐功能的简单实例,包括了php实现推荐功能的简单实例的使用技巧和注意事项,需要的朋友参考一下 利用similar_text将这些文章标题同原文章标题做对比,按标题的相似程度重新排列标题,就得到了与原文章相似的文章列表。 程序运行结果: 与[简明现代魔法]最相关的前三个文章是: 1:简单明了的现代魔法 2:简单易懂的现代魔法 3:简明扼要的古代魔法 以上就是本次介绍的全部知

  • 自我介绍之后,还问了实验室和导师是谁,是不是博导()。 觉得我项目和岗位没什么匹配度(在实验室主要做CV的),就只问了一段实习。(好详细啊) 下面问了好多八股题: 1.BERT详解 2. 借口任务是什么意思 3.为什么自注意力要除以根号dk 4.编码器和解码器是什么关系 4.1 Transformer中编码和解码的作用 5.推荐系统中大模型的应用 6.BERT在做分类、命名实体识别、句子相似度任务

  • 昨天傍晚约的今早面试,有点猝不及防 早起下大雨,冒着雨到实验室面试,裤腿和鞋是刚淋湿的,衣服和那啥是没晒干的 耳机还坏了,临时接了个连蓝牙连了好久 面试官来迟了三四分钟,我以为是不要我了,紧张的要死,刚开始说话都有点抖 全程没问推荐系统,感觉有点凉 许愿团子oc,饿了么已经卸载了,点外卖只用美团(oc了我直接吃一周美团)