首页 > 编程语言 >递归算法实践--到仓合单助力京东物流提效增收

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

时间:2025-01-09 15:11:44浏览次数:1  
标签:sku SKU Set 仓合单 -- 合单 new 提效 采购

作者:京东物流 李硕

一、背景

京东物流到仓业务「对商家」为了减少商家按照京东采购单分货备货过程,对齐行业直接按照流向交接,提升商家满意度;「对京东」揽收操作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, 值为poNos
    Map<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, 值为poNos
    Map<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;
        // 最多等级个数的对应sku
        String 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下的poNos
            Set<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>>() {
        @Override
        public 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;
}

四、价值

目前上线之后刚推广,功能上线45天,已经在浙江、 河南、上海、江苏、安徽、天津、四川、北京22个客户使用,增收500万整体运营平稳,且在大促期间合单收货功能优势更加凸显:「对商家」减少商家按照京东采购单分货备货过程,对齐行业直接按照流向交接,商家满意度提升。「对京东」 揽收操作APP提效30%,分货、入库交仓效率提升10%,整体TC转运效率更快;

五、总结

难点:将根据SKU分堆之后剩下的采购单分别加到不同的分堆中,这个方案也是思考了好久之后想到的,然后构造成递归进行计算,最终进行去重;

性能:递归算法中大部分计算都是重复的,但是经过记录中间计算结果,将计算过的采购单集合直接剪枝,计算时间就不会随着采购单的数量增长而指数增长,真实情况也是随着单据数量的增加、SKU和等级的种类增多依然健壮;

标签:sku,SKU,Set,仓合单,--,合单,new,提效,采购
From: https://www.cnblogs.com/Jcloud/p/18662186

相关文章

  • 代码之美-代码整洁之道
    作者:京东零售刘仲伟一、开篇引言京东零售从9月开始对技术风险系统性地跟踪汇报,以故障数、监控发现率、故障发现时间、故障恢复时间等多个指标进行统计和跟踪汇报,相比于之前线上小故障杖责二十、大故障发配宁古塔,有了向惩前毖后、治病救人方向的转变。我也有机会参与到其中部分......
  • 【Cobalt Strike】UDRL简单配置
    cs4.9的下载、解压和连接server不写了,网上有的。以下kit的链接:https://pan.baidu.com/s/1mu6rpmHoGQ-lTPmSll14tg?pwd=test提取码:test下载好visualstudio2022和C++所需要的相关插件网上也都有,简单的就不写了。将cs启动好把下载的套件解压出来,找到UDRL-VS,找到udrl-vs......
  • BFS
    BFS(广度优先搜索,Breadth-FirstSearch)是一种用于遍历或搜索树或图的算法。它的核心思想是从起始节点开始,逐层向外扩展,先访问离起始节点最近的节点,再访问更远的节点。BFS通常使用队列(Queue)来实现。BFS的核心思想逐层扩展:从起始节点开始,先访问所有与起始节点直接相连的节点(第......
  • ☘️☘️☘️React和Vue底层机制相关优秀文章
    ReactFiber相关讲解React技术揭秘完全理解ReactFiber[译]深入Reactfiber架构及源码看家本领来了:全面了解ReactSuspense和Hooks走进ReactFiber的世界ReactFiber是什么reactfiber到底有多细你不知道的ReactVirtualDOM我对React实现原理的理解......
  • 生产环境部署apollo
    生产环境部署apollo1总体设计Apollo(阿波罗)是一款可靠的分布式配置管理中心,诞生于携程框架研发部,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。官方文档地址:https://www.apolloco......
  • 域内权限
    域内权限前言前面一节讲了工作组的权限,今天介绍一下域内权限的相关知识域内用户组介绍当一个电脑加入到域之后,使用域内用户进行登录,域内用户的信息存放在域控(DC)上,添加用户或者修改密码登操作都在域控上执行管理员组(Administrators)的成员可以不受限制地存取计算机/域的资......
  • 聊一聊 C#异步 任务延续的三种底层玩法
    一:背景1.讲故事最近聊了不少和异步相关的话题,有点疲倦了,今天再写最后一篇作为近期这类话题的一个封笔吧,下篇继续写我熟悉的生产故障系列,突然亲切感油然而生,哈哈,免费给别人看程序故障,是一种积阴德阳善的事情,欲知前世因,今生受者是。欲知来世果,今生做者是。在任务延续方面,我个......
  • 2025年必备开源免费项目管理软件,9款工具全面解析,提升工作效率
    高效的项目管理对于团队和企业的成功至关重要。无论是小型创业团队还是大型企业,都需要合适的工具来协调资源、跟踪进度、管理任务。开源免费的项目管理软件因其灵活性、可定制性以及无需高昂成本的特点,成为了众多团队的首选。本文将为您详细介绍9款2025年必备的开源免费项目管理......
  • CDS标准视图:银行对账单行项目 I_BankStatementItem
    视图名称:银行对账单行项目I_BankStatementItem视图类型:基础视图视图代码:点击查看代码@AbapCatalog.sqlViewName:'IBANKSTATMENTITM'@AbapCatalog.compiler.compareFilter:true@AbapCatalog.preserveKey:true@AccessControl.authorizationCheck:#CHECK@EndUserText.l......
  • java入门与基础语法
    java入门三高问题:高可用,高性能,高并发Java特性与优势:简单性,面向对象,可移植性,高性能,分布式,动态性,多线程,安全性,健壮性Java三大版本Javase(标准版),javame(嵌入式开发),javaee(企业级开发)jdk:java开发者工具包jre:java运行环境jvm:java虚拟机安装java环境(自行搜索网上其他博......