Backend Development 10 min read

Recursive Order Merging Algorithm for JD Logistics Inbound Service

This article describes the background, problem definition, recursive algorithm design, Java implementation, deduplication logic, performance considerations, and business impact of a SKU‑level order merging solution for JD Logistics' inbound-to-warehouse process.

JD Tech Talk
JD Tech Talk
JD Tech Talk
Recursive Order Merging Algorithm for JD Logistics Inbound Service

Background: JD Logistics' inbound‑to‑warehouse service ("to‑warehouse for merchants") aims to reduce merchants' need to split orders according to JD purchase orders, align industry flow, improve merchant satisfaction, and increase JD's pick‑up app efficiency; the order‑merging feature was created to meet these goals.

Problem: A batch of 50‑100 purchase orders must be merged according to multiple rules. Each order may have different source types (self‑operated vs. non‑self‑operated), receipt types, multiple SKUs, and each SKU can have multiple levels; a batch can contain many SKUs with many levels.

Merge Rules

Self‑operated and non‑self‑operated orders cannot be merged.

Physical receipt and document receipt orders cannot be merged.

Orders with the same receiving warehouse and distribution center can be merged.

If merging would cause the same SKU to have multiple levels, the merge is prohibited.

Approach (A – Idea): Assume the batch can be merged, then apply the merge rules to split non‑compliant orders. Use rules 1‑3 to create lists that need rule 4 processing. Identify the SKU with the most levels (e.g., skuA with 7 levels) and partition orders by those levels, recursively merging and pruning already‑processed sets to avoid exponential time complexity. After recursion, sort and deduplicate the resulting merge sets.

Algorithm (B – Recursive Method): A recursive algorithm repeatedly breaks the problem into similar sub‑problems, calling itself until base conditions are met (e.g., each SKU has only one level).

Solution – Code

1. Recursive Merge Code

/**
 * 指定不同等级不能合单
 *
 * @param poNoSet       采购单号Set
 * @param poMainInfoMap 采购单详情
 * @param calculatedSet 计算过的采购单据列表的集合
 * @return
 */
private List
> doMergeClassDifferent(Set
poNoSet, Map
poMainInfoMap, Set
calculatedSet) {
    // 如果该set已经计算过则不重复计算
    List
> resultList = new ArrayList<>();
    String calculatedPoNoKey = buildCalculatedPoNoKey(poNoSet);
    if (calculatedSet.contains(calculatedPoNoKey)) {
        return resultList;
    } else {
        calculatedSet.add(calculatedPoNoKey);
        resultValue.incrementAndGet();
    }

    // 以sku为key的集合
    Set
skuSet = new HashSet<>();
    // 以sku 为key, 值为poNos
    Map
> skuMap = new HashMap<>();
    // 存放同一个sku下有多少个不同等级的集合
    Map
> skuToskuLevelMap = new HashMap<>();

    // 以sku+level 为key的集合
    Set
skuLevelSet = new HashSet<>();
    // 以sku+level 为key, 值为poNos
    Map
> skuLevelMap = new HashMap<>();

    for (String poNo : poNoSet) {
        PoOrderFacadeResponse.PoMainInfo poMainInfo = poMainInfoMap.get(poNo);
        // 采购单条目
        List
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
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
strings = skuToskuLevelMap.get(sku);
            if (strings.size() > high) {
                high = strings.size();
                maxLevelSku = sku;
            }
        }
        if (high > MagicCommonConstants.NUM_1) {
            // 获取该sku下的poNos
            Set
strings = skuMap.get(maxLevelSku);
            // 差集
            Set
chaJiSet = poNoSet;
            chaJiSet.removeAll(strings);

            Set
skuLevels = skuToskuLevelMap.get(maxLevelSku);
            for (String skuLevel : skuLevels) {
                Set
poNoTempSet = skuLevelMap.get(skuLevel);
                poNoTempSet.addAll(chaJiSet);
                // 递归计算
                List
> clist = doMergeClassDifferent(poNoTempSet, poMainInfoMap, calculatedSet);
                if (CollectionUtils.isNotEmpty(clist)) {
                    resultList.addAll(clist);
                }
            }
        }
    }
    return resultList;
}

2. Deduplication Code

/**
 * 去重 合单之后的采购单号
 *
 * @param sets
 * @param dooModel
 */
private List
> uniqueRepeatPoNo(List
> sets, DooModel dooModel) {
    sets.sort(new Comparator
>() {
        @Override
        public int compare(Set
o1, Set
o2) {
            return o2.size() - o1.size();
        }
    });

    List
> resultList = new ArrayList<>();
    Set
allMergedSet = new HashSet<>();

    Set
allSet = new HashSet<>();
    for (Set
set : sets) {
        Set
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;
}

Value: After 45 days of launch, the feature is used by 22 customers across multiple provinces, generating an additional 5 million CNY in revenue, improving merchant satisfaction, increasing JD pick‑up app efficiency by 30 %, and boosting overall transportation efficiency.

Summary of Challenges: After SKU‑based partitioning, remaining orders must be added to different partitions, a solution discovered after extensive reasoning and implemented via recursion.

Performance: The recursive algorithm avoids exponential growth by recording intermediate results and pruning already‑computed order sets, remaining robust as order volume, SKU count, and level variety increase.

Scan to join the technical discussion group

BackendJavaAlgorithmlogisticsrecursionorder merging
JD Tech Talk
Written by

JD Tech Talk

Official JD Tech public account delivering best practices and technology innovation.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.