万事不求人系列之-智能点餐算法实现-JavaScript实现智能点餐
作为一个成长中的架构师,编码能力是万不能停止的,这个算法是之前在上一家单位帮助同事们自助订餐写的,纯爱好自己码敲的,刚好这段时间重新整理代码,发现了它,分享给大家,请大家品评指教。
- 使用场景介绍:随着各种订餐APP的出现,找饭馆团购券,为自己订点好吃的或者团购固定人数的券都很方便,但是很多时候我们遇到的是这样的场景:知道用餐人数是几人,但是总经费或人均消费标准有限制,让你点菜,你就得一方面考虑荤素搭配,有菜有汤有主食,另一方面还得考虑经费限制;还有一种情况是就这么多总经费,人数又不确定(如之前答应去临走又有事告假的),人少可以多点几个硬菜,人多只能综合考虑,拿主食顶上。这两种场景,对点菜的人提出了很高的要求,本算法就是针对此种情况,只要给出用餐人数或固定金额,自动为你科学点菜,妈妈再也不用担心你是个点菜盲。
- 基本效果截图:
如上图所示,输入人数点击开始点菜,系统自动会为你按照人均20的标准给出合理的菜单,这个人均标准是系统默认设置的,可以调整参数。如果输入人数的同时输入限定金额,则会以此金额为总花费的参考,保证不超过此金额下最优的给出建议菜单。当然如果同时输入了人数和限定金额,那么限定金额/人数不能低于系统设置的人均最低值,比如人数6人,限定金额50,人均很不到10块,下个毛管子。
3.基本原理:根据人数或限定金额得到本次菜单的可用总金额,同时根据人数按照一定荤素比例计算各类菜需要的个数,如6个人需要三个肉菜,一个蔬菜,一个凉菜,6分主食。(这个菜个数的计算是随机的,凉菜随机出现,主食可以是米饭也可以是饺子之类的,这个也是随机的。同时蔬菜和肉菜的比例虽然固定,但是每次随机会有小的调整,有上下浮动。);得到每类菜的个数后开始从对应类别中随机选择,得到结果后按照金额的限制先排序再进行适当剔除或重选,使得总限定金额最优化,最后得到菜单并输出。
4.核心JS函数解释说明:
- 初始化默认参数:
1 var dishRate=[0.4,0.5,0.1]; // meat,vege,cold 2 var leastAveragePayed = 10; // Average consumption money 3 var defaultAveragePayed = 20; // default consumption money 4 var eatRiceFlag = true; // if eat rice or other things 5 var eatRiceRate = 8; // the rate people eat rice; 6 var eachRicePayed = 3; // each rice cost how much 7 var moneyLimit = false; 8 var outRangeMoney = 5; // can over money
9 var allDishArray = []; // 饭店所有的菜品基本参数有:荤蔬搭配比例、人均最少消费、默认人均消费、要米饭还是其他主食、要米饭的概率、每碗米饭的价钱、是否有总消费限制、上下浮动的空间、饭店所有的菜品(这个需要初始化,将菜品按荤蔬凉菜汤米饭等类别分开,具体代码没有贴上来,看附件里)
- 点击“开始点菜"执行的方式解释:
1 function execute(){ 2 var peoples=eatPeoples.value; 3 var money=payMoney.value; 4 if("" == peoples){ 5 resultMes.innerText = "请输入用餐人数!"; 6 return; 7 } 8 if(!/^\d+$/.test(peoples) || (("" != money) && !/^\d+$/.test(money))){ 9 resultMes.innerText = "输入格式不对,请重新输入!"; 10 return; 11 } 12 if(""!=money.replace(/[\s]+/g,"")){ 13 moneyLimit = true; 14 } 15 randomChooseDish(peoples,money); 16 }
做了一些基本的输入有效性验证,比如人数不能为空,输入格式校验等,然后进入randomChooseDish方法开始点菜
-
randomChooseDish方法如下:
1 function randomChooseDish(peoples,money){ 2 var tempPeoples=parseInt(peoples); 3 var tempSumMoney= (""==money)?tempPeoples*parseInt(defaultAveragePayed):parseInt(money); 4 if(!checkCondition(tempPeoples,tempSumMoney)){ 5 return; 6 } 7 var dishNumArray= getDishNumArray(tempPeoples); //get dishNumArray 8 9 var hasPayedMoney=0; 10 if(eatRiceFlag){ 11 // eat rice,reduce the rice money 12 hasPayedMoney = eachRicePayed*tempPeoples; 13 } 14 15 var beenChoosedArray = beginChooseDishesAndIndexs(dishNumArray); 16 17 sortChoosedArray(beenChoosedArray); 18 // when dishes are been choosed ,should check 19 checkAndChangeDishes(beenChoosedArray,hasPayedMoney,tempSumMoney); 20 21 // show result 22 showChooseResult(beenChoosedArray,hasPayedMoney,tempPeoples); 23 }
确定人数和总金额,checkCondition做基本的条件判断,比如人数不能少于2人,总金额/人数不能低于人均最低值等;getDishNumArray用于根据人数和初始化荤素比例计算每类菜品需要点的数量;beginChooseDishesAndIndexs用于开始随机点菜;sortChoosedArray用于排序,从贵到便宜,这样对于便宜的菜可以有更多搭配的方式;checkAndChangeDishes用于对选择的菜进行金额限制检查,如果超过限制则开始从最便宜的菜调整菜,直到菜单合格;showChooseResult用于将结果显示到页面上。下面是具体每个函数的源码,有注释。
- checkCondition做基本的条件判断
1 function checkCondition(tempPeoples,tempSumMoney){ 2 if(tempPeoples<2){ 3 //alert(); 4 resultMes.innerText = "一个人下馆子?太奢侈了."; 5 return false; 6 } 7 if(tempPeoples>25){ 8 //alert(); 9 resultMes.innerText = "人数太多,一桌坐不下!"; 10 return false; 11 } 12 13 if(tempSumMoney<tempPeoples*leastAveragePayed){ 14 //alert(); 15 resultMes.innerText ="太抠了吧,都不到人均消费10块!"; 16 return false; 17 } 18 return true; 19 }
- getDishNumArray用于根据人数和初始化荤素比例计算每类菜品需要点的数量
1 // get meat,vege,cold numArray 2 function getDishNumArray(tempPeoples){ 3 var numArray=[Math.ceil(tempPeoples*dishRate[0]),getRandomRate(8)?Math.ceil(tempPeoples*dishRate[1]):Math.floor(tempPeoples*dishRate[1]),Math.round(tempPeoples*dishRate[2])]; // meat,vege,cold 4 5 if(getSumArray(numArray)<=tempPeoples+1 || tempPeoples>=10){ 6 var soupNum = Math.floor(tempPeoples/4) 7 numArray[numArray.length]=soupNum>2?2:soupNum; // add soup,soup num small then 2 8 } 9 10 eatRiceFlag = getRandomRate(eatRiceRate); 11 if(!eatRiceFlag){ 12 // eat others 13 var mainRiceNum = Math.floor(tempPeoples/3); 14 numArray[numArray.length]=mainRiceNum>5?5:mainRiceNum; // add rice, mainrice nums small then 5 15 } 16 return numArray; 17 }
- beginChooseDishesAndIndexs用于开始随机点菜
1 function beginChooseDishesAndIndexs(dishNumArray){ 2 var resultArray=[]; 3 var hasChoosedDishes=[]; // save be choosed dish 4 var hasChoosedIndexs=[]; // save be choosed in sourceArray index 5 var m = getRandom(dishNumArray.length); //random pos start 6 var dishLength=dishNumArray.length; 7 for(var i=0;i<dishLength;i++){ 8 var index = ((i+m)>=dishLength)?i+m-dishLength:(i+m); 9 var dishNum=dishNumArray[index]; 10 var tempSingleChoosed = []; // temp singleType choosed array 11 for(var n=0;n<dishNum;n++){ 12 var singleTypeArray = allDishArray[index]; 13 var singleTypeIndex = getRandom(singleTypeArray.length); 14 //alert(tempSingleChoosed+"and"+singleTypeIndex); 15 while(tempSingleChoosed.length <= singleTypeArray.length && checkIfInArray(tempSingleChoosed,singleTypeIndex)){ 16 singleTypeIndex = getRandom(singleTypeArray.length); // if now index is choosed,choose again 17 //alert("reGet"+singleTypeIndex); 18 } 19 if(tempSingleChoosed.length == singleTypeArray.length){ 20 continue; // if singleTypeDish all been choosed, beak this circle,to next type dish 21 } 22 hasChoosedDishes[hasChoosedDishes.length] = singleTypeArray[singleTypeIndex] 23 tempSingleChoosed[tempSingleChoosed.length] = singleTypeIndex; // ramark the temp position 24 hasChoosedIndexs[hasChoosedIndexs.length] = index+","+singleTypeIndex; // ramark the position 25 } 26 } // all dish has choosed 27 resultArray.push(hasChoosedDishes); 28 resultArray.push(hasChoosedIndexs); 29 return resultArray; 30 }
- sortChoosedArray用于排序
1 // when dishes been choosed ,sort it,from big to small 2 function sortChoosedArray(beenChoosedArray){ 3 var hasChoosedDishes=beenChoosedArray[0]; // save be choosed dish 4 var hasChoosedIndexs=beenChoosedArray[1]; // save be choosed in sourceArray index 5 for(var i=0;i<hasChoosedDishes.length;i++){ 6 for(var j=i;j<hasChoosedDishes.length;j++){ 7 if(getDishAmount(hasChoosedDishes[i])>getDishAmount(hasChoosedDishes[j])){ 8 var temp = hasChoosedDishes[i]; 9 hasChoosedDishes[i] = hasChoosedDishes[j]; 10 hasChoosedDishes[j] = temp; 11 // also should syn the choosedIndex 12 var temp2 = hasChoosedIndexs[i]; 13 hasChoosedIndexs[i] = hasChoosedIndexs[j]; 14 hasChoosedIndexs[j] = temp2; 15 } 16 } 17 } 18 //alert(hasChoosedDishes); 19 }
- checkAndChangeDishes用于对选择的菜进行金额限制检查
1 // check if over money ,change less cost dish 2 function checkAndChangeDishes(beenChoosedArray,hasPayedMoney,tempSumMoney){ 3 var outRange = moneyLimit?0:outRangeMoney; 4 while((hasPayedMoney+getSumArray(beenChoosedArray[0]))>tempSumMoney+outRange){ 5 if(getRandomRate(8)){ 6 changeOneToLessExpensive(beenChoosedArray);// random choose one dish then change it to less expensive 7 sortChoosedArray(beenChoosedArray); // reSort 8 }else{ 9 removeDish(beenChoosedArray); // remove the most or least Expensive dish 10 } 11 } 12 }
- showChooseResult用于将结果显示到页面上
1 // show the choose result 2 function showChooseResult(beenChoosedArray,hasPayedMoney,tempPeoples){ 3 var hasChoosedDishes=beenChoosedArray[0]; // save be choosed dish 4 var hasChoosedIndexs=beenChoosedArray[1]; // save be choosed in sourceArray index 5 var tempcoldMes="凉菜:",tempVegeMes="蔬菜:",tempMeatMes="肉菜:",tempSoupMes="汤:",tempRiceMes="主食:"; 6 for(var i in hasChoosedDishes){ 7 var choosedIndex = hasChoosedIndexs[i]; 8 var thisChoosedDish = hasChoosedDishes[i]; 9 var thisDishArray = thisChoosedDish.split("@"); 10 var allDishArrayIndex = (choosedIndex.split(","))[0]; 11 switch (allDishArrayIndex){ 12 case "0":tempMeatMes += thisDishArray[0]+":"+thisDishArray[1]+",";break; 13 case "1":tempVegeMes += thisDishArray[0]+":"+thisDishArray[1]+",";break; 14 case "2":tempcoldMes += thisDishArray[0]+":"+thisDishArray[1]+",";break; 15 case "3":tempSoupMes += thisDishArray[0]+":"+thisDishArray[1]+",";break; 16 case "4":tempRiceMes += thisDishArray[0]+":"+thisDishArray[1]+",";break; 17 default:break; 18 } 19 hasPayedMoney += parseInt(thisDishArray[1]); 20 } 21 var resultMessage=""; 22 if(tempcoldMes.length>3){ 23 resultMessage += tempcoldMes.slice(0,-1)+"\n\n"; 24 } 25 if(tempVegeMes.length>3){ 26 resultMessage += tempVegeMes.slice(0,-1)+"\n\n"; 27 } 28 if(tempMeatMes.length>3){ 29 resultMessage += tempMeatMes.slice(0,-1)+"\n\n"; 30 } 31 if(tempSoupMes.length>2){ 32 resultMessage += tempSoupMes.slice(0,-1)+"\n\n"; 33 } 34 if(tempRiceMes.length>3){ 35 resultMessage += tempRiceMes.slice(0,-1)+"\n\n"; 36 }else if(eatRiceFlag){ 37 resultMessage += "主食:"+tempPeoples+"碗米饭("+eachRicePayed+"元/碗)"+"\n\n"; 38 } 39 resultMessage += "共花费"+hasPayedMoney+"元"+"\n"; 40 41 resultMes.innerText = resultMessage; 42 }
- 初始化默认参数:
其他都是一些辅助性的函数,见附件。
5.附件点击下载: