背景:获取合并批销单数据,对数据进行分组合并展示,每组数据12个,可以扩展2个位置,用于业务人员筛选产品发往快递公司
规则:①相同供应商相同发货时间一起展示,每组不能超过规定数据
②相同供应商相同时间的数据如果被分到2组,那么两组数据需要相连
③必须同一发货时间的在一组中;最后每一组数据需要匀称,不能呈现出特别多与特别少。
④以发货时间为准,相同发货时间的尽量放在一起,并且以发货时间进行排序
⑤最后零碎发货时间的放在一组
⑥最后如果零碎中有与大组中发货时间相同的则放在一起,但每组数据不能超过15
思路:创建一个大list保存已经完美拍好的list
一、拿100%满足条件
①先拿出100%符合条件,拿出多少条根据算法计算出来,放到大List中
②拿出100%满足的剩余的放在一组中
二、填补
①取出大List中不满数组长度的List,对其进行填充;填充时不能把同一供应商同一时间的拆开;最大长度12+2;这块是另外一个算法,如何匹配到完美的填补
三、处理大量剩余
①剩余的处理与第二部类似,只不过,第二部是某单个数据在一个大的集合中查找,而这步则把集合中每一个小集合匹配成完美的组放入大Lst中
②同供应商同时间的必须放在一起,每组数据相加大于最大长度且小于最大长度+扩展长度 ---------算法
③由于业务数据原因有很大概率处理到最后会剩余几条数据如何处理?
四、微调、剩余归类---这里是另外一个算法
①第三步中剩下的数据中如果有大于3的则独立成一组,因为上步中已经把所有数据都匹配的近乎完美所以大于3肯定是在数组中放不下的
②把所有微小的颗粒放在同一组,装进大lst中
五、按大组中每个list的第一条数据的日期排序并按先后顺序给予组号
算法:
一、通过取整与取余对数组长度进行配比得到最优的情况
首先考虑到不满一组时的数组长度处理,这关系到数据遍历时拿数据
计算最优配比:比如25条数据,数组长度12扩展位2--取整2余1,此时如果我一组12条数据会落单一条这是非常不希望看到的结果
此时如果每组13条数据就可以得到最好的结果
1 ///2 /// 得到真实数组长度的方法 3 /// 4 /// 数组长度 5 /// 最大长度 6 /// 扩展长度 7 /// 数组个数 8 /// 数组剩余 9 /// 真实数组长度10 /// 11 private void trueArrayProperty(int arrayLength, int maxLen, int extSeat, ref int arr, ref int rem, ref int trueArrLength)12 {13 //真实组数14 if (arrayLength < maxLen)15 {16 arr = 1;17 rem = arrayLength;18 }19 else20 {21 arr = arrayLength / maxLen;22 //余数23 rem = arrayLength % maxLen;24 }25 //真实的数组长度26 trueArrLength = maxLen;27 for (int ai = 1; ai <= extSeat; ai++)28 {29 int a1 = arr * ai;30 if (rem == 0) { break; }31 if (a1 > rem)32 {33 trueArrLength = trueArrLength + ai;34 arr = arrayLength / trueArrLength;35 //余数36 rem = arrayLength % trueArrLength;37 }38 }39 }
算法二:摘数据,采用递归也是没辙中想到的辙,如果不使用递归,从大集合中遍历大集合会造成引用类型循环引用的问题
①将源数据按供应商和时间分组,拿到分组后结果集就可以拿到每组有多少条数据;
②数组长度按每组需要大于等于Maxlen且小于等MaxLen+extSeat
③while(boolean)的用意在于可能不能一次性匹配上数据,可能需要多次叠加才可以得到满意的数组
④由于业务数据原因和分组匹配规则最后一定会出现不可能全部把souceDatas吃完,所以就用了zOther存储这些另类数据,保证退出
1 ///2 /// 递归剩余数据 3 /// 4 /// 源数据 5 /// 大lst 6 /// 最大长度 7 /// 扩展位 8 /// 杂项处理 9 private void RecursionDatas(ListsourceDatas, List
> resultDatas,10 int maxLen, int extSeat, List zOther)11 {12 if (sourceDatas.Count == 0)13 {14 return;15 }16 List curItem = new List ();17 var dataGroup = sourceDatas.GroupBy(e => new { e.saleOrderDate, e.ssellerID }).OrderByDescending(e => e.Count());18 var groupFirst = dataGroup.First();19 curItem = sourceDatas.Where(e => e.saleOrderDate == groupFirst.Key.saleOrderDate && e.ssellerID == groupFirst.Key.ssellerID).ToList();20 sourceDatas = sourceDatas.Except(curItem).ToList();21 var group = sourceDatas.Where(e => e.saleOrderDate == curItem.First().saleOrderDate).GroupBy(e => new { e.saleOrderDate, e.ssellerID });22 while (curItem != null)23 {24 foreach (var item in group)25 {26 //只匹配正好的27 if (item.Count() >= (maxLen - curItem.Count) && item.Count() <= (maxLen + extSeat - curItem.Count))28 {29 curItem.AddRange(item);30 resultDatas.Add(curItem);31 sourceDatas = sourceDatas.Except(curItem).ToList();32 curItem = new List ();33 break;34 }35 }36 //一轮过后,如果没匹配上,则可能过大而剩余中没有小数37 if (curItem.Count >= maxLen)38 {39 resultDatas.Add(curItem);40 sourceDatas = sourceDatas.Except(curItem).ToList();41 curItem = new List ();42 }43 else if (curItem.Count < maxLen && curItem.Count > 0)44 {45 //一轮过后,如果没匹配上,则可能数据量过小而剩余中没有能够匹配上的46 var dataFirst = group.FirstOrDefault(e=>e.Key.saleOrderDate==curItem.First().saleOrderDate);47 if (dataFirst == null)48 { //如果没有找到同类,则有可能是与其它时间相同,也有可能与其它不同。如何处理49 zOther.AddRange(curItem);50 curItem = new List ();51 break;52 }53 curItem.AddRange(dataFirst);54 sourceDatas = sourceDatas.Except(dataFirst).ToList();55 }56 else57 {58 break;59 }60 group = sourceDatas.Where(e => e.saleOrderDate == curItem.First().saleOrderDate).GroupBy(e => new { e.saleOrderDate, e.ssellerID }).ToList();61 }62 RecursionDatas(sourceDatas, resultDatas, maxLen, extSeat,zOther);63 }
算法三:同样是集合找集合的问题,使用了降级处理思路
①在进入递归前,我先把zOther过滤了一次,按同商同时分组后,数据量大于3的独立出一组,以降低递归复杂度
②这时数据里会出现2种情况,第一种是与大list种的时间相同,这类数据是可以归并到list中的,而剩余的则是与大list中其他数组数据时间不同的,这类数据需要都放在一组中
1 private void RecursionOtherDatas(ListotherDatas, List
> resultDatas, 2 List zz) { 3 if (otherDatas.Count == 0) { 4 return; 5 } 6 var otherGroup = otherDatas.GroupBy(e => new { e.saleOrderDate, e.ssellerID }); 7 8 var groupFirst = otherGroup.First(); 9 var data = otherDatas.Where(e => e.saleOrderDate == groupFirst.Key.saleOrderDate && e.ssellerID == groupFirst.Key.ssellerID).ToList();10 foreach (var item in resultDatas.OrderBy(e=>e.Count))11 {12 if (item.Count == 15) { break; }13 if (item.Any(e => e.saleOrderDate == groupFirst.Key.saleOrderDate))14 {15 if (item.Count + data.Count <= 15)16 {17 item.AddRange(data);18 otherDatas = otherDatas.Except(data).ToList();19 groupFirst = null;20 break;21 }22 }23 }24 if (groupFirst != null) {25 zz.AddRange(data);26 otherDatas = otherDatas.Except(data).ToList();27 }28 RecursionOtherDatas(otherDatas, resultDatas, zz);29 }
后记:整个业务模块用时将近一个月,但这套代码只写了4天。前边大部分时间由于产品经理的需求一直在变每当我开发出一套算法他就变了挂,所以前边一直在改改改,索性我比较机智,并不是在代码的基础上改,
而是每次都新建一个方法。后来改烦了,索性直接越级找大boss问清楚了真正的需求,最后落定出这套算法。
这次的教训不管是大功能还是小功能都应该落实到文档上,把自己理解的东西写清楚,不管是自己忘了查看也好,未来扯皮也摆还都是有好处的