当前位置: 首页 > news >正文

递归算法实践--到仓合单助力京东物流提效增收

一、背景

京东物流到仓业务「对商家」为了减少商家按照京东采购单分货备货过程,对齐行业直接按照流向交接,提升商家满意度;「对京东」揽收操作APP提效;到仓合单功能应运而生;

二、问题

一次批量采购单(一次50或者100个采购单)需要根据不同的规则合并成多个订单;

每一个采购单可以是不同的来源类型(自营和非自营)、不同的收货类型,每一个采购单会有多个SKU,同一个SKU只有一个等级,一批采购单会有多个SKU,同一个SKU会有多个等级;

合单规则:

1.自营和非自营不能合;

2.实物收货和单据收货的采购单不能合并;

3.相同收获仓和配送中心的采购单可以合并;

4.两个采购单如果合并之后同一个SKU拥有多个等级,则不可以合单;

三、打法

A、思路

1.首先认为这一批单子可以合单,后续就是根据合单规则将不符合规则转换成拆单的过程;

2.根据合单规则1、2、3可以将这一批单子拆成多个需要执行规则4的待合单集合List;

3.举个极端例子,规则1、2、3这些采购单都是相同的,则该List数量为1,这100个单子进行后续根据SKU+等级维度的合单;

4.由于相同SKU不同等级不可以合单,我们可以先找出这100个采购单中包含最多等级的SKU,比如skuA 包含最多的7个等级, 根据skuA进行按等级进行分堆,分成7堆之后,由于并不是所有的采购单都包含skuA, 则这100个采购单可能还会剩下一些单子不在这7堆之内,也就是剩下的这些单子如果只是基于skuA维度进行分堆,可以跟这7堆任何一堆进行合单,这时候需要将这些剩下的单子分别加入到这7堆里面,得到第一次合单后的结果,这里很重要,也是纳入递归算法的基础;

5.得到的7堆再分别进行第四步的操作,直到当前这一堆的sku不包含不同等级为止(这里是递归结束的条件);

6.由于分堆里面包含了重复的订单,所以有些单子组合会被重复计算,这时候需要维护一个列表将计算过的单据进行保存,这样可以将重复的列表进行剪枝,这样可以保证整个算法的时间复杂度不是指数级增长;

7.针对最终全部递归之后的结果将合单的列表进行由多到少进行排序,然后进行排重,这里如果排重之后只有一个采购单了可以先释放,但不要加到排重列表里面,因为后面可能还会出现可合并的集合,很重要,不然得到的合单结果会变少,得到最终的合单后的结果;

B、算法

‌‌递归算法是一种通过重复将问题分解为同类的子问题来解决问题的方法‌; 特点是函数或子程序在运行过程中直接或间接调用自身;常见的递归算法包括‌Fibonacci函数、‌Hanoi问题和‌阶乘计算等;

C、解决方案

1. 递归代码块

/*** 指定不同等级不能合单** @param poNoSet       采购单号Set* @param poMainInfoMap 采购单详情* @param calculatedSet 计算过的采购单据列表的集合* @return*/
private List<Set<String>> doMergeClassDifferent(Set<String> poNoSet, Map<String, PoOrderFacadeResponse.PoMainInfo> poMainInfoMap, Set<String> calculatedSet) {// 如果该set已经计算过则不重复计算List<Set<String>> resultList = new ArrayList<>();String calculatedPoNoKey = buildCalculatedPoNoKey(poNoSet);if (calculatedSet.contains(calculatedPoNoKey)) {return resultList;} else {calculatedSet.add(calculatedPoNoKey);resultValue.incrementAndGet();}// 以sku为key的集合Set<String> skuSet = new HashSet<>();// 以sku 为key, 值为poNosMap<String, Set<String>> skuMap = new HashMap<>();// 存放同一个sku下有多少个不同等级的集合Map<String, Set<String>> skuToskuLevelMap = new HashMap<>();// 以sku+level 为key的集合Set<String> skuLevelSet = new HashSet<>();// 以sku+level 为key, 值为poNosMap<String, Set<String>> skuLevelMap = new HashMap<>();for (String poNo : poNoSet) {PoOrderFacadeResponse.PoMainInfo poMainInfo = poMainInfoMap.get(poNo);// 采购单条目List<PoOrderFacadeResponse.PoItemInfo> poItemInfos = poMainInfo.getPoItemInfos();for (PoOrderFacadeResponse.PoItemInfo poItemInfo : poItemInfos) {String skuKey = poItemInfo.getGoodsNo();String skuLevelKey = buildSkuLevelKey(poItemInfo);skuSet.add(skuKey);setKeyMap(skuKey, skuMap, poNo);// 存放同一个sku下有多少个不同等级的集合Set<String> stringSet = skuToskuLevelMap.get(skuKey);if (CollectionUtils.isEmpty(stringSet)) {stringSet = new HashSet<>();skuToskuLevelMap.put(skuKey, stringSet);}stringSet.add(skuLevelKey);skuLevelSet.add(skuLevelKey);setKeyMap(skuLevelKey, skuLevelMap, poNo);}}if (skuSet.size() == skuLevelSet.size()) {// 此处sku的数量和sku+level的数量相同,不需要再进行递归运算// 方法结束的出口resultList.add(poNoSet);return resultList;} else {// 同一个sku下最多等级个数int high = MagicCommonConstants.NUM_1;// 最多等级个数的对应skuString maxLevelSku = "";for (String sku : skuToskuLevelMap.keySet()) {Set<String> strings = skuToskuLevelMap.get(sku);if (strings.size() > high) {high = strings.size();maxLevelSku = sku;}}if (high > MagicCommonConstants.NUM_1) {// 获取该sku下的poNosSet<String> strings = skuMap.get(maxLevelSku);// 差集Set<String> chaJiSet = poNoSet;chaJiSet.removeAll(strings);Set<String> skuLevels = skuToskuLevelMap.get(maxLevelSku);for (String skuLevel : skuLevels) {Set<String> poNoTempSet = skuLevelMap.get(skuLevel);poNoTempSet.addAll(chaJiSet);// 递归计算List<Set<String>> clist = doMergeClassDifferent(poNoTempSet, poMainInfoMap, calculatedSet);if (CollectionUtils.isNotEmpty(clist)) {resultList.addAll(clist);}}}}return resultList;
}

2. 去重代码块

/*** 去重 合单之后的采购单号** @param sets* @param dooModel*/
private List<Set<String>> uniqueRepeatPoNo(List<Set<String>> sets, DooModel dooModel) {sets.sort(new Comparator<Set<String>>() {@Overridepublic int compare(Set<String> o1, Set<String> o2) {return o2.size() - o1.size();}});List<Set<String>> resultList = new ArrayList<>();Set<String> allMergedSet = new HashSet<>();Set<String> allSet = new HashSet<>();for (Set<String> set : sets) {Set<String> tempSet = new HashSet<>();for (String poNo : set) {if (!allSet.contains(poNo)) {tempSet.add(poNo);allMergedSet.add(poNo);}}if (!tempSet.isEmpty()) {if (tempSet.size() > 1) {allSet.addAll(tempSet);resultList.add(tempSet);}// 此处的单条后面不一定不能合单}}// 差集allMergedSet.removeAll(allSet);if (allMergedSet.size() > 0) {for (String poNo: allMergedSet) {putPoNoToSet(dooModel, poNo);}}return resultList;
}
http://www.hskmm.com/?act=detail&tid=20193

相关文章:

  • 计算机视觉(opencv)练习——抠图(图像裁剪与轮廓提取) - 详解
  • 深入解析:@scqilin/phone-ui 手机外观组件库
  • Tita项目与绩效一体化管理:驱动企业效能跃升的数字化引擎
  • 第七篇
  • labview打包应用
  • Day23抽象类
  • ES 是否有类似mysql explain的语句诊断用法
  • 让每次语音唤醒都可靠,公牛沐光重构可观测体系
  • 【2025-09-27】连岳摘抄
  • Python 爬虫 HTTPS 实战,requests httpx aiohttp 抓取技巧、证书问题与抓包调试全流程 - 教程
  • Codeforces 补题笔记
  • 使用 Python 基于Ollama构建个人私有知识库(AI生成)
  • Codeforces Round 1048 (Div. 2) 补题笔记
  • 【RabbitMQ】消息可靠性保障
  • React学习笔记(一)
  • Day23static详解
  • 11.prometheus监控之黑盒(blackbox)监控
  • Python虚拟环境及创建和使用虚拟环境(Python3)
  • 团队协作必备:16款在线协同编辑文档方案对比
  • 变电站、开闭所、环网柜、配电站
  • 为AI注入灵魂:一种面向人机黑箱的元人文治理新范
  • clickhouse
  • 2025.9.28——1黄
  • 聚焦 AI 应用基础设施,云栖大会 Serverless AI 全回顾
  • 删除 Ubuntu Nautilus 资源管理器侧栏的默认目录
  • 完整教程:整合与超越:论“开源AI智能名片链动2+1模式S2B2C商城小程序”对传统红人直播带货模式的升维
  • 2025 最新隔音板厂家权威推荐排行榜:聚焦实力厂商,阻尼 / 聚酯纤维等全品类适配与标杆案例解析室外/KTV/隔音板安装/沈阳/ 厂房隔音板厂家推荐
  • 2025 年医疗器械注册咨询公司最新权威推荐排行榜:TOP 级服务商全流程能力解析,助力企业高效合规拿证医疗器械注册咨询//二类医疗器械注册咨询/三类医疗器械注册咨询公司推荐
  • git合并最近的多个提交
  • Altium Designer 25.8.1 最新版安装指南