多對多自動拆分總數相等

看到這個題目大家可能會一頭霧水,本人表達能力有限,無法用簡單語言描述該算法,標題湊合一下唄。工作後才發現,處理很多生活中業務問題就是在,就像再處理數學題。  

用一個案例和大家描述一下吧。總得來說就是總數想等的情況下,如何如何將多對多(n對n)的對應關係拆分成一對一(1對1)的關係,java實現。

假設有

三張付款單(單號,金額) 總數300 :  (FK01,100) (FK02, 100) (FK03, 100) 

兩張發票(單號,金額)    總數300   : (FP01,   100),(FP02,  200)。

我們在進行賬務覈銷的時候,通常是一張付款單,對應一直髮票金額一致,類似的案例應該還有很多。

 

方案一(先進先出)

按照先進先出的原則,先拿第一張付款,用第一張發票與他匹配,多還少補,多餘的發票用於下一張付款單,多餘的付款單去下一張發票的金額補上,直到補全爲止,依次類推。

(FK01,FP01, 100)、(FK02,FP02,100)、(FK03,FP02,100)

 

方案二(按比例拆分、尾差倒擠保證總額一致)

第一步:按比例拆分付款單,倒擠尾差。拆分付款單

(FK01,100,33%) (FK02, 100,33%) (FK03, 100,1-33%-33% ) 

第二步:按付款單各張單所展的比例拆分,發票金額,餘下的付款單金額,尾差倒擠到最後一張發票。

(FK01,100,33%,FP01,33)

(FK01,100,33%,FP02,(100-33)=67)

(FK02,100,33%,FP01,33)

(FK02,100,33%,FP02,(100-33)=67)

(FK03,100,34%,FP01,34)

(FK03,100,34%,FP02,66)

最終付款單總額

FK01 = 33+67=100

FK02 = 33+67=100

FK03 = 34+66=100

最終發票總額

FP01 = 33+33+34 = 100

FP02 = 66+67+67= 200

 

案例一代碼實現:

 /**
     * 先近先出原則 依次匹配每一單
     * 金額溢出分配到下一單, 金額不足從後面一單取出來
     *建立每張付款對應每張發票的取值關係  N 對 N
     * @param mapPay
     * @param mapInvoice
     * @return
     */
    private static Map<String,BigDecimal> getResultByOrder(Map<String,String> mapPay,Map<String,String> mapInvoice){

        Map<String,BigDecimal> payInvoiveResult =new HashMap<>();
        //需要移除的發票集合
        List<String> listRemove =new ArrayList<>();
        
        for(Map.Entry<String,String> entryPay :mapPay.entrySet()){

            BigDecimal payMoney = new BigDecimal(entryPay.getValue());
            //移除 已匹配過的發票
            listRemove.stream().forEach(t->{
                mapInvoice.remove(t);
            });
            listRemove.clear();

            for(Map.Entry<String,String> entryInvoice :mapInvoice.entrySet()){

                BigDecimal invoiceMoney = new BigDecimal(entryInvoice.getValue());
                //當付款金額 正好等於 某張發票餘下的金額 直接取該發票並從集合中移除
                if(payMoney.compareTo(invoiceMoney)==0){
                    payInvoiveResult.put(entryPay.getKey() +"==" +entryInvoice.getKey(),invoiceMoney);
                    listRemove.add(entryInvoice.getKey());
                    break;

                    //當付款金額 小於 某張發票餘下的金額時 直接取該發票,並將發票中的金額改成 扣除改付款單金額後的值
                }else if(payMoney.compareTo(invoiceMoney)==-1){
                    payInvoiveResult.put(entryPay.getKey() +"==" +entryInvoice.getKey(),payMoney);
                    BigDecimal reduceMoney = invoiceMoney.subtract(payMoney);
                    mapInvoice.put(entryInvoice.getKey(),reduceMoney.toString());
                    break;
                    //當付款金額 大於某張發票餘下的金額 直接取該發票 ,餘下不足的金額 繼續往後遍歷  並移除該發票
                }else{
                    payInvoiveResult.put(entryPay.getKey() +"==" +entryInvoice.getKey(),invoiceMoney);
                    listRemove.add(entryInvoice.getKey());
                    payMoney = payMoney.subtract(invoiceMoney);
                }

            }

        }
        return payInvoiveResult;

    }

案例二代碼實現

 /**
     * 獲取各條記錄所佔的比重 ,最後一條尾差倒擠
     * @param mapPay
     * @return
     */
    private static Map<String,BigDecimal>  getPayRadio(Map<String,String> mapPay){

        BigDecimal total = new BigDecimal("0");
        //計算總和
        for (Map.Entry<String,String> entry:mapPay.entrySet()) {
            total =total.add(new BigDecimal(entry.getValue()));
        }

        Map<String,BigDecimal> resultRaido =new HashMap<>();
        // 付款單總數 用於比對是否是最後一張付款單

        long countall = mapPay.size();
        long count=0;
        //已遍歷付款單 所佔比重的和
        BigDecimal radioSum =new BigDecimal("0");
        for(Map.Entry<String,String> entry:mapPay.entrySet()){
            count =count+1;
            //當前金額
            BigDecimal money = new BigDecimal(entry.getValue());
            BigDecimal radio = money.divide(total,2,RoundingMode.HALF_UP);
            resultRaido.put(entry.getKey(),radio);

            //最後一條記錄 直接尾差倒擠 用1減去 已遍歷付款單 所佔比重的和 (1-radioSum) 確保總數一致
            if(count == countall){
                resultRaido.put(entry.getKey(),new BigDecimal("1").subtract(radioSum));
            }
            radioSum = radioSum.add(radio);

        }

        return resultRaido;
    }


    /**
     *  按比例拆分
     * @param mapPay 付款單對應金額
     * @param mapPayRadio 每張付款單對應金額所佔比重  尾差倒擠
     * @param mapInvoice 發票對應金額
     * @return
     */
    private static Map<String,BigDecimal> getSplitResult(Map<String,String> mapPay,
                                                         Map<String,BigDecimal> mapPayRadio,Map<String,String>  mapInvoice){

        Map<String,BigDecimal> mapPayInvoiceSplit = new HashMap<>();

        for(Map.Entry<String,BigDecimal> entryPayRadio:mapPayRadio.entrySet()){
            //每張付款單 佔總數的權重
            BigDecimal payRadio = entryPayRadio.getValue();
            //每張付款單金額
            BigDecimal payMoney = new BigDecimal(mapPay.get(entryPayRadio.getKey()));
            //發票總數量,用於判斷是否是最後一張  是否倒擠尾差
            long countAll = mapInvoice.size();
            //當前已遍歷的發票數量  最後一張倒擠該張付款單的尾差 到最後一張發票     (注 發票總額 與 付款單總額是相等的)
            long count =0;
            //
            BigDecimal invoiceSum =new BigDecimal("0");
            for(Map.Entry<String ,String> entryInvoice : mapInvoice.entrySet()){
                //當前已遍歷的發票數量  最後一張倒擠該張付款單的尾差 到最後一張發票     (注 發票總額 與 付款單總額是相等的)
                count = count+1;
                //每張發票的金額
                BigDecimal invoiceMoney = new BigDecimal(entryInvoice.getValue());
                //獲取每張發票  對應比重的金額
                BigDecimal invoiceSplit = payRadio.multiply(invoiceMoney).setScale(2,RoundingMode.HALF_UP);
                //最後一條尾差 倒擠 該付款單 分配到最後一單 餘下的金額  因爲總數是一致的
                if(count ==countAll){
                    invoiceSplit = payMoney.subtract(invoiceSum);
                }
                //建立對應關係
                mapPayInvoiceSplit.put(entryPayRadio.getKey()+"_"+payRadio+"=="+entryInvoice.getKey(),invoiceSplit);
                //計算 每張付款單  已分配出去的金額彙總
                invoiceSum =invoiceSum.add(invoiceSplit);

            }


        }

        return mapPayInvoiceSplit;

    }

結果展示

  public static void main(String[] args) {

        Map<String,String> mapPay =new HashMap<>();
        mapPay.put("CD1","280");
        mapPay.put("CD2","20");
        mapPay.put("CD3","300");

        Map<String,String> mapInvoice =new HashMap<>();
        mapInvoice.put("FP1","100");
        mapInvoice.put("FP2","200");
        mapInvoice.put("FP3","300");

        Map<String,BigDecimal> mapPayRadio=  getPayRadio(mapPay);

        Map<String,BigDecimal> mapPayInvoiceSplit =  getSplitResult(mapPay,mapPayRadio,mapInvoice);
        System.out.println("按比例拆分");
        printlnByCode(mapPayInvoiceSplit);
        System.out.println("先進先出");
        mapPayInvoiceSplit =  getResultByOrder(mapPay,mapInvoice);
        printlnByCode(mapPayInvoiceSplit);

    }

    // 排序輸出 便於查看結果
    private static void printlnByCode(Map<String,BigDecimal> map) {

        List<Map.Entry<String, BigDecimal>> list = new ArrayList<Map.Entry<String, BigDecimal>>(map.entrySet());
        Collections.sort(list, new Comparator<Map.Entry<String, BigDecimal>>() {
            @Override
            public int compare(Map.Entry<String, BigDecimal> o1, Map.Entry<String, BigDecimal> o2) {
                return o1.getKey().compareTo(o2.getKey());
            }
        });
        list.forEach(entry->{
            System.out.println(entry.getKey()+" : "+entry.getValue());
        });


    }

目前的方案一,還可優化升級,根據規則,如何拆分出最優解,總數一致的情況,使得拆分出來的單據最少,是否可以通過排序,控制單據的先後關係。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章