每逢过节网上购物,不论哪个平台都会发放很对优惠券,如何在结账时最合理的使用已有的优惠券平台是不会告诉你的,作为程序员的我们写一套算法得到最优使用方法是一件很有趣的事,关键是目前全网好像没有比较靠谱的答案哦!
理想状态下的最优使用券算法
首先看下最理想状态下的优惠券使用算法,假设客户手中有[1,2,5,10,50,100]6种优惠券,而且每种优惠券个数无限(银行),
此种情况类似银行给客户兑现金,我们唯一要求“尽可能使用大额券”。
没水平的代码(为了更好的阅读后面的代码,用心良苦):
//优惠券种类(假设每种券个数无限)
let coupons = [1,2,5,10,20,50,100].reverse();
//用券记录
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
}
};
//获取最优用券的算法
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;
}
//打印用券明细方法
let printResult = function(amount){
let str = "总计"+amount+"元用券明细如下:\n";
for(let item in uesage){
if(uesage[item].count>0){
str+="使用"+uesage[item].unitCoupon+"元券"+uesage[item].count+"张"+"\n"
}
}
console.log(str);
return str;
}
//================================测试===================
getMincou(268)
printResult(268);
结果如下:
优化算法
高逼格写法,可以随心更换所拥有的优惠券面额,约束条件是每种优惠券数量无限且所拥有的优惠券能够满足结账金额(小额券多多益善)
let OptimalCoupons = function(coupons){
let uesage = {};
this.coupons = coupons;
this.initAmount = 0;
/**
* @method initUseage 记录用券记录
* @param {arry} coupons 拥有的优惠券列表
* */
this.initUseage = function(coupons){
coupons.forEach(p=>{
uesage["coupon_"+p]={
unitCoupon:p,
count:0
}
});
}(this.coupons);
/**
* @method getMincou 获取最优用券方案方法
* @param {int} 结账金额
* @return {useage} 用券记录
* */
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 判断当前金额使用什么券并记录结果
* @param {int,int,int} 当前剩余金额,券区间最大最小值
* @return {int} 用券之后剩余金额
* */
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 打印用券明细
* */
this.printResult = function (){
let amount = this.initAmount;
let str = "总计"+amount+"元用券明细如下:\n";
for(let item in uesage){
if(uesage[item].count>0){
str+=uesage[item].unitCoupon+"元券"+uesage[item].count+"张"+"\n"
}
}
console.log(str);
return str;
}
}
测试:
//=================测试1================
const coupons = [1,2,5,10,20,50,100].reverse();
console.log("优惠券列表:"+coupons.toString() )
optimalCoupons = new OptimalCoupons(coupons);
optimalCoupons.getMincou(268);
optimalCoupons.printResult(268);
//=================测试2================
const coupons2 = [1,5,15,50,100].reverse();
console.log("优惠券列表:"+coupons2.toString() )
optimalCoupons = new OptimalCoupons(coupons2);
optimalCoupons.getMincou(268);
optimalCoupons.printResult(268);
结果:
非理想状态下 券个数有限的最优用券算法
上面的算法我们解决了理想状态下最优使用券的算法,但实际种不可能每种券的数量无限,因此基于上面算法再次改进。
首先看券个数有限,但所有券组合能够满足付款金额的情况
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 记录用券记录
* @param {arry} coupons 拥有的优惠券列表
* */
this.initUseage = function(coupons){
coupons.couponTypes.forEach((p,i)=>{
uesage["coupon_"+p]={
unitCoupon:p,
count:0,//已使用的券个数
countInit:coupons.couponsNums[i] //原始券数量
}
});
}(this.coupons);
/**
* @method getMincou 获取最优用券方案方法
* @param {int} 结账金额
* @return {useage} 用券记录
* */
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 判断当前金额使用什么券并记录结果
* @param {int,int,int} 当前剩余金额,券区间最大最小值
* @return {int} 用券之后剩余金额
* */
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 打印用券明细
* */
this.printResult = function (){
let amount = this.initAmount;
let str = "总计"+amount+"元用券明细如下:\n";
for(let item in uesage){
if(uesage[item].count>0){
str+="使用"+uesage[item].unitCoupon+"元券"+uesage[item].count+"张,剩余券数量"+uesage[item].countInit+"\n"
}
}
console.log(str);
return str;
}
}
//=================测试1================
const coupons = {
couponTypes : [1,2,5,10,20,50,100].reverse(),
couponsNums : [1000,20,5,3,2,2,1].reverse()
}
console.log("优惠券列表:"+coupons.couponTypes.toString() )
console.log("各种优惠券对应原始个数:"+coupons.couponsNums.toString() )
optimalCoupons = new OptimalCoupons(coupons);
optimalCoupons.getMincou(268);
optimalCoupons.printResult();
// =================测试2================
const coupons2 = {
couponTypes : [1,5,15,50].reverse(),
couponsNums : [1000,3,2,1].reverse()
}
console.log("优惠券列表:"+coupons2.couponTypes.toString())
console.log("各种优惠券对应原始个数:"+coupons2.couponsNums.toString())
optimalCoupons = new OptimalCoupons(coupons2);
optimalCoupons.getMincou(78);
optimalCoupons.printResult();
测试:
优化实现券个数有限,而且所有券组合之后不能满足付款金额,还需付现及浪费券额
let OptimalCoupons = function(coupons){
let uesage = {};
this.coupons = coupons;
this.initAmount = 0;//付款金额
this.cash = 0;//用券后还需付款
this.waste = 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 记录用券记录
* @param {arry} coupons 拥有的优惠券列表
* */
this.initUseage = function(coupons){
coupons.couponTypes.forEach((p,i)=>{
uesage["coupon_"+p]={
unitCoupon:p,
count:0,//已使用的券个数
countInit:coupons.couponsNums[i] //原始券数量
}
});
}(this.coupons);
/**
* @method getMincou 获取最优用券方案方法
* @param {int} 结账金额
* @return {useage} 用券记录
* */
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 判断当前金额使用什么券并记录结果
* @param {int,int,int} 当前剩余金额,券区间最大最小值
* @return {int} 用券之后剩余金额
* */
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 打印用券明细
* */
this.printResult = function (){
let amount = this.initAmount;
let str = "总计用券"+amount+"元,用券明细如下:\n";
for(let item in uesage){
if(uesage[item].count>0){
str+="使用"+uesage[item].unitCoupon+"元券"+uesage[item].count+"张,剩余券数量"+uesage[item].countInit+"\n";
}
}
str+="还需使用现金:"+this.cash+"元,您浪费券额:"+this.waste+"元";
console.log(str);
return str;
}
}
测试:
//=================测试1================
const coupons = {
couponTypes : [1,2,5,10,20,50,100].reverse(),
couponsNums : [1000,20,5,3,2,2,1].reverse()
}
console.log("优惠券列表:"+coupons.couponTypes.toString() )
console.log("各种优惠券对应原始个数:"+coupons.couponsNums.toString() )
optimalCoupons1 = new OptimalCoupons(coupons);
optimalCoupons1.getMincou(268);
optimalCoupons1.printResult();
// =================测试2================
const coupons2 = {
couponTypes : [20,3,5].reverse(),
couponsNums : [1,2,1].reverse()
}
console.log("优惠券列表:"+coupons2.couponTypes.toString())
console.log("各种优惠券对应原始个数:"+coupons2.couponsNums.toString())
optimalCoupons2 = new OptimalCoupons(coupons2);
optimalCoupons2.getMincou(70);
optimalCoupons2.printResult();
// =================测试3================
const coupons3 = {
couponTypes : [20,3,5].reverse(),
couponsNums : [1,2,1].reverse()
}
console.log("优惠券列表:"+coupons3.couponTypes.toString())
console.log("各种优惠券对应原始个数:"+coupons3.couponsNums.toString())
optimalCoupons3 = new OptimalCoupons(coupons3);
optimalCoupons3.getMincou(3);
optimalCoupons3.printResult();
//=================测试4================
const coupons4 = {
couponTypes : [100].reverse(),
couponsNums : [1].reverse()
}
console.log("优惠券列表:"+coupons4.couponTypes.toString() )
console.log("各种优惠券对应原始个数:"+coupons4.couponsNums.toString() )
optimalCoupons1 = new OptimalCoupons(coupons4);
optimalCoupons1.getMincou(5);
optimalCoupons1.printResult();
结果:
总结
以上已经初步实现优惠券最优使用的算法,因为各个平台用券场景不同,比如超过一定金额才能使用大额券,或者用户用大额券买小价值货物浪费太大 等算法都是建立在上面的算法之上,如需进一步完善可以评论区留言讨论哦。求点赞啊!