高性能凑发票方案实现

需求

公司有一个比较坑爹的报销方案,需要根据一堆碎的发票中,凑出一个目标金额,要求误差在1块钱以内
缺点:每次人肉去对比吧,浪费大量的时间

解决

下面贴出golang实现的方案

package main

import (
    "fmt"
    "strconv"
    "time"
)

type InvoiceCounter struct {
    maxValue int   //期望值(单元为分)
    items    []int //发票金额(单元为分)
    overflow int   //允许的误差值(单元为分)
}

//items:所有发票 maxValue:目标金额 overflow:允许误差金额
func NewInvoiceCounter(items []float64, maxValue float64, overflow float64) *InvoiceCounter {
    obj := &InvoiceCounter{}
    obj.maxValue = obj.dollarToCent(maxValue)
    obj.overflow = obj.dollarToCent(overflow)
    centItems := make([]int, len(items))
    for i, v := range items {
        centItems[i] = obj.dollarToCent(v)
    }
    obj.items = centItems
    return obj
}

//元转分
func (this *InvoiceCounter) dollarToCent(value float64) int {
    value, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", value), 64)
    return int(value * 100)
}

//分转元
func (this *InvoiceCounter) centToDollar(v int) float64 {
    value := float64(v)
    value, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", value/100), 64)
    return value
}

//执行计算,返回所有方案
func (this *InvoiceCounter) Run() [][]float64 {
    items := this.items
    n := len(this.items)
    max := this.maxValue + this.overflow
    states := this.createStates(len(this.items), max+1)
    states[0][0] = true
    if items[0] <= max {
        states[0][items[0]] = true
    }
    for i := 1; i < n; i++ {
        //不选
        for j := 0; j <= max; j++ {
            if states[i-1][j] {
                states[i][j] = states[i-1][j]
            }
        }
        //选中
        for j := 0; j <= max-items[i]; j++ {
            if states[i-1][j] {
                states[i][j+items[i]] = true
            }
        }
    }
    //获取最终所有满足的方案
    res := make([][]float64, 0)
    for j := this.maxValue; j <= max; j++ {
        for i := 0; i < n; i++ {
            if states[i][j] {
                //判断必须最后一个选中才算,要不区间有重合 比如前5个元素已经满足目标金额了,state[5][w]=true,然后state[6][w]也是true,存在重复的方案
                if j-items[i] >= 0 && states[i-1][j-items[i]] == true {
                    res = append(res, this.getSelected(states, items, i, j))
                }
            }
        }
    }
    return res
}

//获取所有选中的元素(倒推)
func (this *InvoiceCounter) getSelected(states [][]bool, items []int, n, max int) []float64 {
    var selected = make([]int, 0)
    for i := n; i >= 1; i-- {
        //元素被选中
        if max-items[i] >= 0 && states[i-1][max-items[i]] == true {
            selected = append([]int{items[i]}, selected...)
            max = max - items[i]
        } else {
            //没选,max重量不变,直接进入下一次
        }
    }
    if max != 0 {
        selected = append([]int{items[0]}, selected...)
    }
    dollarItems := make([]float64, len(selected))
    for i, v := range selected {
        dollarItems[i] = this.centToDollar(v)
    }
    return dollarItems
}

//初始化所有状态
func (this *InvoiceCounter) createStates(n, max int) [][]bool {
    states := make([][]bool, n)
    for i, _ := range states {
        states[i] = make([]bool, max)
    }
    return states
}

使用示例

func main() {
    //所有发票金额
    items := []float64{100, 101, 103, 105, 106, 132, 129, 292, 182, 188, 224.3, 40.5, 35.9, 32.5, 39, 12, 17.5, 28, 35, 34, 26.32, 28, 35, 39, 25, 1, 24, 35, 45, 47, 32.11, 45, 32, 38.88, 44, 36.5, 35.8, 45, 26.5, 33, 25, 364, 27.3, 39.2, 180, 279, 282, 281, 285, 275, 277, 278, 200, 201, 1959.12, 929.53, 1037.03, 1033.9}

    //目标金额5000钱,允许差额1块钱
    obj := NewInvoiceCounter(items, 5000, 1)

    startTime := time.Now()

    //执行计算,返回所有结果方案
    res := obj.Run()

    //打印所有方案
    for _, v := range res {
        fmt.Println(v)
    }
    fmt.Printf("total:%d used time:%s\n", len(res), time.Now().Sub(startTime))

}

运行结果

[100 101 103 105 106 132 129 292 182 188 224.3 40.5 12 17.5 35 34 26.32 28 35 39 25 1 24 35 45 47 45 32 38.88 44 36.5 45 26.5 33 25 364 27.3 39.2 180 279 282 281 285 275 277 278]
[100 101 103 105 132 129 292 182 188 35.9 39 12 17.5 28 35 34 26.32 28 35 39 25 1 24 35 45 47 32.11 45 32 38.88 44 36.5 35.79 45 26.5 33 25 364 27.3 39.2 180 279 282 281 285 275 277 278 200]
...
[35.9 25 24 38.88 36.5 35.79 45 26.5 33 25 27.3 39.2 180 279 282 281 285 275 277 278 200 201 1037.03 1033.9]
total:577 used time:97.048224ms

耗时97毫秒,共计算出577种方案,就是这么爽!

你可能感兴趣的:(高性能凑发票方案实现)