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

京东云开发者
• 阅读 16

作者:京东物流 李硕

一、背景

京东物流到仓业务「对商家」为了减少商家按照京东采购单分货备货过程,对齐行业直接按照流向交接,提升商家满意度;「对京东」揽收操作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和等级的种类增多依然健壮;

点赞
收藏
评论区
推荐文章
VOP 消息仓库演进之路|如何设计一个亿级企业消息平台
VOP作为京东企业业务对外的API对接采购供应链解决方案平台,一直致力于从企业采购数字化领域出发,发挥京东数智化供应链能力,通过产业链上下游耦合与链接,有效助力企业客户的成本优化与资产效能提升。本文将介绍VOP如何通过亿级消息仓库系统来保障上千家企业KA客户与京东的数据交互。
随机高并发查询结果一致性设计实践
物流合约中心是京东物流合同管理的唯一入口。为商家提供合同的创建,盖章等能力,为不同业务条线提供合同的定制,归档,查询等功能。由于各个业务条线众多,为各个业务条线提供高可用查询能力是物流合约中心重中之重。同时计费系统在每个物流单结算时,都需要查询合约中心,确保商家签署的合同内容来保证计费的准确性。
【最佳实践】京东小程序-LBS业务场景的性能提升 | 京东云技术团队
一、前言1.1京东LBS门详业务介绍京东LBS门详目前已经支持了仓网、药急送、天选、小时达POP多种业务,并且具备了多端的能力,一套代码可以在京东app、健康app、微信小程序中运行,一定程度上研发效率的提升能够更加快速的支持业务迭代。随着业务需求猛增、各
京东云开发者 京东云开发者
9个月前
对号入座,快看看你的应用系统用了哪些高并发技术?
一系统简介百舸流量运营平台承接着京东金融APP核心资源位和京东APP部分重要资源位,大促单接口QPS达到10w,压测单接口到20w,典型的c端读链路高并发场景。接下来,聊聊我们的系统都有哪些应对高并发的“武功秘籍”。二“武功秘籍”1缓存(redis缓存
京东云开发者 京东云开发者
3个月前
大模型时代下的新一代广告系统
京东零售广告部承担着京东全站流量变现及营销效果提升的重要职责,广告研发部是京东最核心的技术部门,也是京东最主要的盈利来源之一。作为京东广告部的核心方向,我们基于京东海量的用户和商家数据,探索最前沿的深度学习等算法技术,创新并应用到业务实践中,赋能千万商家和
京东云开发者 京东云开发者
2个月前
一起单测引起的项目加载失败惨案
作者:京东科技宋慧超一、前言最近在开发一个功能模块时,在功能自测阶段,通过使用单测测试功能的完整性,在测试单测联通性使用到静态方法测试时,发现单测报错,通过查阅解决方案发现需要对Javaassist包进行排包或者升版本处理。通过排包解决掉单测报错,在部署项
京东云开发者 京东云开发者
1个月前
EXCEL导入—设计与思考
作者:京东物流叶方伟EXCEL导入—设计与思考一、案例信息与设计1.1、案例需求与背景B2BTC同城二期有一个Excel导入的功能,单次数据量小于一千,使用频次不高。但涉及到多个字段组成唯一约束,即每条数据操作时要根据唯一性组合字段来操作,要确保数据表中的
京东云开发者 京东云开发者
3星期前
从MySQL JOIN 算法角度看如何优化SQL
作者:京东物流京东物流一、前言在做MySQL的SQL优化时,如果只涉及到单表查询,那么大部分慢SQL都只需从索引上入手优化即可,通过添加合适的索引来消除全表扫描或者排序操作,执行效果,大概率能实现质的飞跃。然而,在实际生产中,除了单表查询,更多的是多个表的
京东云开发者 京东云开发者
3星期前
十亿级订单系统的数据库查询性能优化之路
作者:京东零售崔健0.前言•系统概要:BIP采购系统用于京东采销部门向供应商采购商品,并且提供了多种创建采购单的方式以及采购单审批、回告、下传回传等业务功能•系统价值:向供应商采购商品增加库存,满足库存周转及客户订单的销售,供应链最重要的第一环节1.背景采
万字好文:大报文问题实战 | 京东物流技术团队
大报文问题,在京东物流内较少出现,但每次出现往往是大事故,甚至导致上下游多个系统故障。大报文的背后,是不同商家业务体量不同,特别是B端业务的采购及销售出库单,一些头部商家对京东系统支持业务复杂度及容量能力的要求越来越高。因此我们有必要把这个问题重视起来,从组织上根本上解决。