Every festival online shopping, no matter which platform will issue very good coupons, how to use the existing coupons platform in the most reasonable way when checking out will not tell you, as a programmer, we write a set of algorithm to get the best use method is a very interesting thing, the key is that the whole network seems to have no reliable answer at present!
Optimal coupon algorithm in ideal state
First, let's look at the optimal coupon usage algorithm. Suppose that the customer has [1,2,5,10,50100] 6 kinds of coupons, and the number of each kind of coupons is unlimited (bank),
This situation is similar to the situation in which the bank cashes the customer, and we only require "the use of large amount bonds as much as possible".
No level of code (in order to better read the following code, take pains):
//Types of coupons (assuming unlimited number of coupons) let coupons = [1,2,5,10,20,50,100].reverse(); //Coupon record let uesage = { coupon1:{ unitCoupon:1, count:0 }, coupon2:{ unitCoupon:2, count:0 }, coupon5:{ unitCoupon:5, count:0 }, coupon10:{ unitCoupon:10, count:0 }, coupon20:{ unitCoupon:20, count:0 }, coupon50:{ unitCoupon:50, count:0 }, coupon100:{ unitCoupon:100, count:0 } }; //An algorithm for obtaining the optimal coupon let getMincou = function (amount){ if ( typeof(amount) != "number" || amount<1)return 0; let currtAmount = 0; while(amount>=1){ if(amount>=100){ currtAmount = amount-100; uesage.coupon100.count++; }else if(amount >= 50 && amount < 100){ currtAmount = amount-50; uesage.coupon50.count++; }else if(amount >= 20 && amount < 50){ currtAmount = amount-20; uesage.coupon20.count++; }else if(amount >= 10 && amount < 20){ currtAmount = amount-10; uesage.coupon10.count++; }else if(amount >= 5 && amount < 10){ currtAmount = amount-5; uesage.coupon5.count++; }else if(amount >= 2 && amount < 5){ currtAmount = amount-2; uesage.coupon2.count++; }else{ currtAmount = amount-1; uesage.coupon1.count++; } amount = currtAmount; } return uesage; } //Method of printing voucher details let printResult = function(amount){ let str = "Total"+amount+"The details of RMB bonds are as follows:\n"; for(let item in uesage){ if(uesage[item].count>0){ str+="Use"+uesage[item].unitCoupon+"Yuan coupon"+uesage[item].count+"Zhang"+"\n" } } console.log(str); return str; } //================================Testing=================== getMincou(268) printResult(268);
The results are as follows:
optimization algorithm
In the high force case, you can change the face value of the coupons you own at will. The constraint is that the number of coupons is unlimited and the coupons you own can meet the closing amount (more small coupons are better)
let OptimalCoupons = function(coupons){ let uesage = {}; this.coupons = coupons; this.initAmount = 0; /** * @method initUseage Record voucher use record * @param {arry} coupons List of coupons owned * */ this.initUseage = function(coupons){ coupons.forEach(p=>{ uesage["coupon_"+p]={ unitCoupon:p, count:0 } }); }(this.coupons); /** * @method getMincou How to get the best scheme of using coupons * @param {int} Amount of settlement * @return {useage} Coupon record * */ this.getMincou = function (amount){ this.initAmount = amount; if ( typeof(amount) != "number" || amount<1)return 0; while(amount>=1){ let max = Math.max.apply(null,coupons); let min = Math.min.apply(null,coupons) if(amount >= max){ amount = this.useRangeIn(amount,null,max); }else if(amount <= min){ amount = this.useRangeIn(amount,min,null); }else{ coupons.forEach( (coupon,index)=>{ amount = this.useRangeIn(amount,coupon,coupons[index+1]); } ) } } return uesage; }; /** * @method useRangeIn Determine what vouchers are used for the current amount and record the results * @param {int,int,int} Current remaining amount, maximum and minimum value of bond range * @return {int} Remaining amount after voucher use * */ this.useRangeIn = function (amount,maxnum,minnum ) { var num = parseInt(amount); if(num <1)return 0; if(! maxnum){ amount = amount-minnum; uesage["coupon_"+minnum].count++; }else if(! minnum){ amount = amount-maxnum; uesage["coupon_"+maxnum].count++; }else{ if(num <maxnum && num>=minnum){ amount = amount-minnum; uesage["coupon_"+minnum].count++; } } return amount; }; /** * @method printResult Print voucher details * */ this.printResult = function (){ let amount = this.initAmount; let str = "Total"+amount+"The details of RMB bonds are as follows:\n"; for(let item in uesage){ if(uesage[item].count>0){ str+=uesage[item].unitCoupon+"Yuan coupon"+uesage[item].count+"Zhang"+"\n" } } console.log(str); return str; } }
Test:
//=================Test 1================ const coupons = [1,2,5,10,20,50,100].reverse(); console.log("Coupon list:"+coupons.toString() ) optimalCoupons = new OptimalCoupons(coupons); optimalCoupons.getMincou(268); optimalCoupons.printResult(268); //=================Test 2================ const coupons2 = [1,5,15,50,100].reverse(); console.log("Coupon list:"+coupons2.toString() ) optimalCoupons = new OptimalCoupons(coupons2); optimalCoupons.getMincou(268); optimalCoupons.printResult(268);
Result:
The optimal algorithm of using coupons with limited number of coupons in non ideal state
In the above algorithm, we have solved the optimal use of coupons in the ideal state, but the actual number of coupons can not be unlimited, so based on the above algorithm, we improve it again.
First of all, the number of coupons is limited, but all combinations of coupons can meet the payment amount
let OptimalCoupons = function(coupons){ let uesage = {}; this.coupons = coupons; this.initAmount = 0; let removeByVal = function(arrylist , val) { let newArrylist = []; for(var i = 0; i < arrylist.length; i++) { if(arrylist [i] == val) { newArrylist = arrylist.splice(i, 1); break; } } return newArrylist; } /** * @method initUseage Record voucher use record * @param {arry} coupons List of coupons owned * */ this.initUseage = function(coupons){ coupons.couponTypes.forEach((p,i)=>{ uesage["coupon_"+p]={ unitCoupon:p, count:0,//Number of used coupons countInit:coupons.couponsNums[i] //Number of original coupons } }); }(this.coupons); /** * @method getMincou How to get the best scheme of using coupons * @param {int} Amount of settlement * @return {useage} Coupon record * */ this.getMincou = function (amount){ this.initAmount = amount; if ( typeof(amount) != "number" || amount<1)return 0; while(amount>=1){ let max = Math.max.apply(null,coupons.couponTypes); let min = Math.min.apply(null,coupons.couponTypes) if(amount >= max){ amount = this.useRangeIn(amount,null,max); }else if(amount <= min){ amount = this.useRangeIn(amount,min,null); }else{ coupons.couponTypes.forEach( (coupon,index)=>{ amount = this.useRangeIn(amount,coupon,coupons.couponTypes[index+1]); } ) } } return uesage; }; /** * @method useRangeIn Determine what vouchers are used for the current amount and record the results * @param {int,int,int} Current remaining amount, maximum and minimum value of bond range * @return {int} Remaining amount after voucher use * */ this.useRangeIn = function (amount,maxnum,minnum ) { var num = parseInt(amount); if(num <1)return 0; if(! maxnum){ amount = amount-minnum; uesage["coupon_"+minnum].count++; uesage["coupon_"+minnum].countInit--; if(uesage["coupon_"+minnum].countInit <1){ removeByVal(this.coupons.couponTypes,minnum); } }else if(! minnum){ amount = amount-maxnum; uesage["coupon_"+maxnum].count++; uesage["coupon_"+maxnum].countInit--; if(uesage["coupon_"+maxnum].countInit <1){ removeByVal(this.coupons.couponTypes,maxnum); } }else{ if(num <maxnum && num>=minnum){ amount = amount-minnum; uesage["coupon_"+minnum].count++; uesage["coupon_"+minnum].countInit--; if(uesage["coupon_"+minnum].countInit <1){ removeByVal(this.coupons.couponTypes,minnum); } } } return amount; }; /** * @method printResult Print voucher details * */ this.printResult = function (){ let amount = this.initAmount; let str = "Total"+amount+"The details of RMB bonds are as follows:\n"; for(let item in uesage){ if(uesage[item].count>0){ str+="Use"+uesage[item].unitCoupon+"Yuan coupon"+uesage[item].count+"Number of remaining coupons"+uesage[item].countInit+"\n" } } console.log(str); return str; } } //=================Test 1================ const coupons = { couponTypes : [1,2,5,10,20,50,100].reverse(), couponsNums : [1000,20,5,3,2,2,1].reverse() } console.log("Coupon list:"+coupons.couponTypes.toString() ) console.log("Original number of coupons:"+coupons.couponsNums.toString() ) optimalCoupons = new OptimalCoupons(coupons); optimalCoupons.getMincou(268); optimalCoupons.printResult(); // =================Test 2================ const coupons2 = { couponTypes : [1,5,15,50].reverse(), couponsNums : [1000,3,2,1].reverse() } console.log("Coupon list:"+coupons2.couponTypes.toString()) console.log("Original number of coupons:"+coupons2.couponsNums.toString()) optimalCoupons = new OptimalCoupons(coupons2); optimalCoupons.getMincou(78); optimalCoupons.printResult();
Test:
The number of coupons is limited, and after all coupons are combined, the payment amount cannot be satisfied, and the amount of cash and waste coupons are also needed
let OptimalCoupons = function(coupons){ let uesage = {}; this.coupons = coupons; this.initAmount = 0;//Payment amount this.cash = 0;//You need to pay after using the voucher this.waste = 0;//Waste coupon value let removeByVal = function(arrylist , val) { let newArrylist = []; for(var i = 0; i < arrylist.length; i++) { if(arrylist [i] == val) { newArrylist = arrylist.splice(i, 1); break; } } return newArrylist; } /** * @method initUseage Record voucher use record * @param {arry} coupons List of coupons owned * */ this.initUseage = function(coupons){ coupons.couponTypes.forEach((p,i)=>{ uesage["coupon_"+p]={ unitCoupon:p, count:0,//Number of used coupons countInit:coupons.couponsNums[i] //Number of original coupons } }); }(this.coupons); /** * @method getMincou How to get the best scheme of using coupons * @param {int} Amount of settlement * @return {useage} Coupon record * */ this.getMincou = function (amount){ this.initAmount = amount; if ( typeof(amount) != "number" || amount<1)return 0; let couponTypes = this.coupons.couponTypes; while(amount>=1){ if(this.coupons.couponTypes.length>0){ let max = Math.max.apply(null,couponTypes); let min = Math.min.apply(null,couponTypes) if(amount >= max){ amount = this.useRangeIn(amount,null,max); }else if(amount <= min){ amount = this.useRangeIn(amount,min,null); }else{ this.coupons.couponTypes.forEach( (coupon,index)=>{ amount = this.useRangeIn(amount,coupon,couponTypes[index+1]); } ) } }else{ this.cash = amount; return; } } return uesage; }; /** * @method useRangeIn Determine what vouchers are used for the current amount and record the results * @param {int,int,int} Current remaining amount, maximum and minimum value of bond range * @return {int} Remaining amount after voucher use * */ this.useRangeIn = function (amount,maxnum,minnum ) { var num = parseInt(amount); if(num <1)return 0; if(! maxnum){ amount = amount-minnum; uesage["coupon_"+minnum].count++; uesage["coupon_"+minnum].countInit--; if(uesage["coupon_"+minnum].countInit <1){ removeByVal(this.coupons.couponTypes,minnum); } }else if(! minnum){ amount = amount-maxnum; uesage["coupon_"+maxnum].count++; uesage["coupon_"+maxnum].countInit--; if(uesage["coupon_"+maxnum].countInit <1){ removeByVal(this.coupons.couponTypes,maxnum); } }else{ if(num <maxnum && num>=minnum){ amount = amount-minnum; uesage["coupon_"+minnum].count++; uesage["coupon_"+minnum].countInit--; if(uesage["coupon_"+minnum].countInit <1){ removeByVal(this.coupons.couponTypes,minnum); } } } if(amount<0){ this.waste = Math.abs(amount); } return amount; }; /** * @method printResult Print voucher details * */ this.printResult = function (){ let amount = this.initAmount; let str = "Total coupon"+amount+"Details of the bonds are as follows:\n"; for(let item in uesage){ if(uesage[item].count>0){ str+="Use"+uesage[item].unitCoupon+"Yuan coupon"+uesage[item].count+"Number of remaining coupons"+uesage[item].countInit+"\n"; } } str+="Cash is also required:"+this.cash+"element,Your wasted voucher amount:"+this.waste+"element"; console.log(str); return str; } }
Test:
//=================Test 1================ const coupons = { couponTypes : [1,2,5,10,20,50,100].reverse(), couponsNums : [1000,20,5,3,2,2,1].reverse() } console.log("Coupon list:"+coupons.couponTypes.toString() ) console.log("Original number of coupons:"+coupons.couponsNums.toString() ) optimalCoupons1 = new OptimalCoupons(coupons); optimalCoupons1.getMincou(268); optimalCoupons1.printResult(); // =================Test 2================ const coupons2 = { couponTypes : [20,3,5].reverse(), couponsNums : [1,2,1].reverse() } console.log("Coupon list:"+coupons2.couponTypes.toString()) console.log("Original number of coupons:"+coupons2.couponsNums.toString()) optimalCoupons2 = new OptimalCoupons(coupons2); optimalCoupons2.getMincou(70); optimalCoupons2.printResult(); // =================Test 3================ const coupons3 = { couponTypes : [20,3,5].reverse(), couponsNums : [1,2,1].reverse() } console.log("Coupon list:"+coupons3.couponTypes.toString()) console.log("Original number of coupons:"+coupons3.couponsNums.toString()) optimalCoupons3 = new OptimalCoupons(coupons3); optimalCoupons3.getMincou(3); optimalCoupons3.printResult(); //=================Test 4================ const coupons4 = { couponTypes : [100].reverse(), couponsNums : [1].reverse() } console.log("Coupon list:"+coupons4.couponTypes.toString() ) console.log("Original number of coupons:"+coupons4.couponsNums.toString() ) optimalCoupons1 = new OptimalCoupons(coupons4); optimalCoupons1.getMincou(5); optimalCoupons1.printResult();
Result:
summary
The above algorithm has preliminarily realized the optimal use of coupons, because different platforms use different scenarios of coupons, such as using large coupons for more than a certain amount, or users use large coupons to buy small value goods with too much waste, etc., which are all based on the above algorithm. If you need to further improve, you can leave a message in the comment area to discuss. Please like it!