国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

Vue實現美團app的影院推薦選座功能【推薦】

2019-11-19 13:07:05
字體:
來源:轉載
供稿:網友

經常用美團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的影院推薦選座功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對武林網網站的支持!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 阿城市| 富阳市| 陕西省| 铜川市| 合阳县| 宁阳县| 荥阳市| 凌源市| 渑池县| 斗六市| 无为县| 永福县| 山阳县| 泽库县| 锦州市| 巴青县| 巫山县| 灌南县| 壶关县| 马尔康县| 昌吉市| 开封市| 巴东县| 金秀| 深州市| 南阳市| 南丰县| 贵定县| 安庆市| 六安市| 巴彦淖尔市| 洞口县| 汝南县| 枣阳市| 瓦房店市| 尼勒克县| 龙江县| 金华市| 峡江县| 阳春市| 乡城县|