Excel催化剂开源第28波-调用Google规划求解库

在Excel催化剂的自定义函数中,有规划求解的函数,用于在一些凑数的场景,某财务工作网友向我提出的需求,例如用于凑发票额使用。

一般开发票的场景是多次采购合在一起开具,即多个订单产生后开,同时发票一般有限额不是想开多少就开多少,而且发票的张数每月都是有限的,也不是随便可以一个零头开一张发票。

对这些凑数的场景,有个算法叫背包算法,是规范求解方面的,当然笔者也没有深入研究过,只是在我师傅的帮助下,找到了Google有一个开源库专门干这些事,性能也是棒棒的,甩开原生Excel的规范求解几个月球距离。

因为这个Google库比较大,而且好像是C++内核的,有区分32位和64位,所以最终没有直接放到ExcelDna项目中,而是采用WebService的方式来部署这个功能,放到服务器上,避开32位、64位问题,同时也不必让客户端发布文件时携带那么大的类库。

关于WebService的问题,可自行百度学习,现只是给出此类库和用这个类库实现了凑数的场景。

Excel催化剂开源第28波-调用Google规划求解库

Google.OrTools类库

WebSevice源码如下:

 public List GetGroupIdsByKnapsacks(long[] values, long[] capacities,int scaleNum)
 {
 KnapsackSolver solver = new KnapsackSolver(
 KnapsackSolver.KNAPSACK_DYNAMIC_PROGRAMMING_SOLVER, "test");
 long[,] weights = new long[1, values.Length];
 for (int i = 0; i < values.Length; i++)
 {
 weights[0, i] = values[i];
 }
 Dictionary dicResult = new Dictionary();
 int iLoop = 0;
 long computedProfit;
 do
 {
 long capacity = capacities[iLoop];
 solver.Init(values, weights, new long[] { capacity });
 computedProfit = solver.Solve();
 //因为有0值的存在,所有一定会有解,只是目标值为0
 if (computedProfit == 0)
 {
 break;
 }
 for (int i = 0; i < values.Length; i++)
 {
 if (solver.BestSolutionContains(i))
 {
 if (!dicResult.ContainsKey(i))
 {
 dicResult.Add(i, $"{(iLoop + 1).ToString("00")}_{capacity*1.0/Math.Pow(10,scaleNum)}_{capacity * 1.0 / Math.Pow(10, scaleNum) - computedProfit * 1.0 / Math.Pow(10, scaleNum)}");//存入序号和组大小、组差异等信息
 values[i] = 0;
 weights[0, i] = 0;
 }
 }
 }
 //Console.WriteLine(computedProfit);
 iLoop++;
 } while (iLoop < capacities.Length);
 List groupIds = new List();
 for (int i = 0; i < values.Length; i++)
 {
 if (dicResult.ContainsKey(i))
 {
 groupIds.Add(dicResult[i]);
 }
 else
 {
 groupIds.Add(null);
 }
 }
 return groupIds;
 }

在ExcelDna上再进行封装

 [ExcelFunction(Category = "规划求解类", Description = "分组凑数,从源数据列中,抽取出指定的项目组合,使其求和数最大限度接近分组的大小。Excel催化剂出品,必属精品!")]
 public static object CouShuWithGroupFromOrTools(
 [ExcelArgument(Description = "需要分组的原始数据单元格区域,精度为最多4位小数点,多于4位将截断")] object[] srcRange,
 [ExcelArgument(Description = "限定组的上限的单元格区域,可选多个单元格代表分多个组,组的大小可不相同,尽量较难组合的放最上面优先对其组合")] object[] groupeRange
 )
 {
 int scaleNum = GetScaleNum(srcRange);
 KnapsacksService.KnapsacksServiceSoapClient client = new KnapsacksService.KnapsacksServiceSoapClient();
 KnapsacksService.ArrayOfLong values = new KnapsacksService.ArrayOfLong();
 values.AddRange(srcRange.Select(s => Convert.ToDouble(s)).Select(t => Convert.ToInt64(t * Math.Pow(10, scaleNum))));
 KnapsacksService.ArrayOfLong capacities = new KnapsacksService.ArrayOfLong();
 capacities.AddRange(groupeRange.Where(s => s != ExcelEmpty.Value).Select(t => Convert.ToDouble(t)).Select(r => Convert.ToInt64(r * Math.Pow(10, scaleNum))));
 KnapsacksService.ArrayOfAnyType results = client.GetGroupIdsByKnapsacks(values, capacities,scaleNum);
 return Common.ReturnDataArray(results.Select(s => s).ToArray(), "L");
 }

结语

此篇介绍的Google.OrTools类库,远不止用于一个简单的凑数功能,若对其他功能有兴趣,可自行去查阅文档学习,此处仅仅作引路,这些著名的类库,通常来说,帮助文档、示例代码都是十分详细的。

再一次见证了VSTO借助外部的轮子力量,给Excel提供了源源不断地能力,让Excel用户在Excel环境可以完成许多不可思议的事情。

同样此篇也开拓了思路,一些复杂的功能需求,不止是借助外部WebAPI的接口调用,甚至自己也可以封装一些API接口供自己调用,在服务器上开发功能,不必考虑客户端的复杂环境,更为稳定地进行开发,而不必考虑兼容性等问题。

页面更新:2024-05-29

标签:票额   零头   结语   轮子   限额   月球   催化剂   算法   发票   函数   客户端   场景   接口   需求   功能   环境   数码

1 2 3 4 5

上滑加载更多 ↓
Top