The best coupon algorithm in the whole network

Keywords: network

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!

Published 65 original articles, won praise 5, visited 908
Private letter follow

Posted by RobinTibbs on Mon, 13 Jan 2020 02:07:30 -0800