分享

计算订单明细中那些组合商品更受欢迎

zhuqitian 2018-12-6 10:04:21 发表于 实践型 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 2 5357
电商中信息流的实时把控至关重要,在仓库中商品摆放时货架和货位的选择影响了拣货的效率,日常和活动和大促等各种场景下摆放可能会有区别,若是统一位置摆放可能会造成拣货效率低,人工成本高,甚至用户体验差。
如果我们能把系统订单中已推单未配货的数据及时拿到加以提炼,找出商品的最佳组合进行排序,那种组合出现的次数最多那么,这些组合中的商品应该要优先摆放在最容易拿到的货架上面,举例说明:
仓库名称   订单号      69:数量
临江仓        ECO001        69001:10,69002:3,69003:2
临江仓        ECO002        69001:1
临江仓        ECO003        69002:6,69003:3
临江仓        ECO004        69001:1,69003:2
临江仓        ECO009        69011:1,69012:2
郑州仓        ECO005        69006:8,69003:9
郑州仓        ECO006        69006:8,69003:9
郑州仓        ECO008        69006:8,69003:9
武汉仓        ECO007        69006:8,69003:9,69001:10,69002:3,69008:88
这些数据中,首先拿到这份数据,对数据进行重组合,发货是要到仓的,借助MapReduce的思想,需要把一个仓库中的所有订单号对应的明细拼在一起,组成一个Map数据结构,key是仓库名称,value是一个大数组,大数组中存放一个Mapkey为订单号,value为订单号对应的商品明细。然后把所有的订单明细组合起来,进行排列组合。例如:
1,2,3,4,5  5个商品可能会出现的组合:
12,13,14,15
23,24,25
34,35
45
123,124,125
134,135
145
1234,1235
1345
1    2   3    4    5
随机列举了一下一串数字会有n多种组合,数组长度越大,可能出现的子组合越多...
介绍了完了后,我们看下代码,写的仓促,大家发现存在优化点记得联系我
测试数据:
临江仓        ECO001        69001:10,69002:3,69003:2
临江仓        ECO002        69001:1
临江仓        ECO003        69002:6,69003:3
临江仓        ECO004        69001:1,69003:2
临江仓        ECO009        69011:1,69012:2
郑州仓        ECO005        69006:8,69003:9
郑州仓        ECO006        69006:8,69003:9
郑州仓        ECO008        69006:8,69003:9
武汉仓        ECO007        69006:8,69003:9,69001:10,69002:3,69008:88

package PermutationAndCombination;

import java.io.*;
import java.util.*;

/**
* @author 圣斗士宙斯
* @date 2018-11-14 19:08:09
* @Description: 对仓库所有订单的订单明细做排列组合,计算得出最佳组合
*/
public class Main {

    /**
     * 数据文件路径
     */
    public static final String DATAPATH = "C:\\Users\\zhuqitian\\Desktop\\仓库订单明细.txt";
    /**
     * 字段间分隔符
     */
    public static final String FIELDSDELIMITED = "\\t";
    /**
     * sku信息间的分隔符
     */
    public static final String MULTISKUINFODELIMITED = ",";
    /**
     * 69码跟数量的分隔符
     */
    public static final String SINGLESKUINFODELIMITED = ":";

    public static void main(String[] args) {

        Main main = new Main();

        //step 1. 按仓reduce,拿到一个仓库中的所有订单号,以及各个订单号对应的商品69码和quantity(主要用于校验和其他项)
        Map<String, List<Map<String, Map<String, Integer>>>> warehouseOrderDetailInfo =
                main.getWarehouseOrderDetailInfo(DATAPATH);
        // step 1.test success
//        Iterator<Map.Entry<String, List<Map<String, Map<String, Integer>>>>> entries =
//                warehouseOrderDetailInfo.entrySet().iterator();
//        while (entries.hasNext()) {
//            Map.Entry<String, List<Map<String, Map<String, Integer>>>> entry = entries.next();
//            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
//        }
        //step 2. 逐个仓库遍历,按69码排序拿到仓库中所有订单的sku的排列组合一个仓库各个订单的69码组合
        Map<String, List<List<Long>>> warehouseOrderCodes =
                main.aggWarehouseOrders(warehouseOrderDetailInfo);
        warehouseOrderDetailInfo.clear();
        // step 2. test success
//        Iterator<Map.Entry<String, List<List<Long>>>> entries2 =
//                warehouseOrderCodes.entrySet().iterator();
//        while (entries2.hasNext()) {
//            Map.Entry<String, List<List<Long>>> entry = entries2.next();
//            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
//        }
        //step 3. 实现排列组合,每一个订单中可能有多个barCodebarcode越多,子组合数也就越多2^n
        Map<String, List<List<List<Long>>>> warehouseCombinationOrders =
                main.calcWarehouseCombinationOrders(warehouseOrderCodes);
        warehouseOrderCodes.clear();
        // step 3. test success
//        Iterator<Map.Entry<String, List<List<List<Long>>>>> entries3 =
//                warehouseCombinationOrders.entrySet().iterator();
//        while (entries3.hasNext()) {
//            Map.Entry<String, List<List<List<Long>>>> entry = entries3.next();
//            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
//        }
        // step 4. 按仓拆解子组合过滤空数组,按仓跨订单计算一个子组合出现的次数
        Map<String, List<List<Long>>> warehouseAllChildCombination =
                main.getWarehouseAllChildCombination(warehouseCombinationOrders);
        warehouseCombinationOrders.clear();
        // step 4. test success
//        Iterator<Map.Entry<String, List<List<Long>>>> entries4 =
//                warehouseAllChildCombination.entrySet().iterator();
//        while (entries4.hasNext()) {
//            Map.Entry<String, List<List<Long>>> entry = entries4.next();
//            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
//        }
        // step 5. 计算每个仓库中每组商品出现的次数
        // 每个仓库所有订单的所有子组合出现的次数
        Map<String, Map<List<Long>, Integer>> warehouseAllChildCombinationNumber =
                new HashMap<String, Map<List<Long>, Integer>>();
        List<Long> singleChildCombination = new ArrayList<>(500);
        // 遍历所有仓库

        for (String warehouseName : warehouseAllChildCombination.keySet()) {
            int childCombinationQuantity = warehouseAllChildCombination.get(warehouseName).size();
            // 遍历所有子组合
            for (int i = 0; i < childCombinationQuantity; i++) {
//                System.out.println(warehouseAllChildCombination.get(warehouseName).get(i));
                int count = Collections.frequency(
                        warehouseAllChildCombination.get(warehouseName),
                        warehouseAllChildCombination.get(warehouseName).get(i));
                System.out.println(warehouseName + ":"+ warehouseAllChildCombination.get(warehouseName).get(i) + " : " + count);
            }
        }
    }


    /**
     * 计算给定仓库中所有订单的商品组合中的所有可能出现的子组合
     *
     * @param warehouseOrderCodes 仓库中的所有订单
     * @return 仓库 组合BarCodes  一个仓库对应多个订单,一个订单对应一个组合,一个组合对应多个子组合
     */
    public Map<String, List<List<List<Long>>>> calcWarehouseCombinationOrders(
            Map<String, List<List<Long>>> warehouseOrderCodes) {
        Map<String, List<List<List<Long>>>> warehouseCombinationOrders = new HashMap<String, List<List<List<Long>>>>();
        List<List<List<Long>>> allCombinationOrders = new ArrayList<List<List<Long>>>();
        List<List<Long>> singleCombinationOrders = null;
        // 遍历各个仓库下面所有订单
        for (String warehouseName : warehouseOrderCodes.keySet()) {
            // 拿到指定仓库下面的所有订单号
            List<List<Long>> allOrderCodes = warehouseOrderCodes.get(warehouseName);
            int len = allOrderCodes.size();
            // 遍历所有订单号拿到单个订单号下面的barCodes
            for (int i = 0; i < len; i++) {
                List<Long> singleOrderCodes = allOrderCodes.get(i);
                // 一组商品 可能会出现的所有子组合
                singleCombinationOrders = combinationOrders(singleOrderCodes);
                allCombinationOrders.add(singleCombinationOrders);
            }
            warehouseCombinationOrders.put(warehouseName, allCombinationOrders);
            allCombinationOrders = new ArrayList<List<List<Long>>>();
        }
        return warehouseCombinationOrders;
    }

    /**
     * group warehouNameget orderCode orderDetail
     *
     * @param dataPath* @return*/
    public Map<String, List<Map<String, Map<String, Integer>>>> getWarehouseOrderDetailInfo(String dataPath) {
        // 本地文件
        File file = new File(dataPath);
        // 读取本地文件,封装数据  临江仓 --> [{69001:1,69002:3},{69003=3, 69002=6}]   数组的长度就是单量
        Map<String, List<Map<String, Map<String, Integer>>>> warehouseOrderDetailInfo =
                new HashMap<String, List<Map<String, Map<String, Integer>>>>();
        // orderCode allSku 多个订单中的所有商品信息 ECO001:[69001:1,69002:3],ECO002:[69001:1,69002:3]
        List<Map<String, Map<String, Integer>>> multiOrderDetailInfo = null;
        List<Map<String, Map<String, Integer>>> multiOrderDetailInfo2 = null;

        InputStreamReader isr = null;
        BufferedReader br = null;
        if (file.isFile() && file.exists()) {
            try {
                isr = new InputStreamReader(new FileInputStream(file), "utf-8");
                br = new BufferedReader(isr);
                String lineStr = null;
                // all warehouseName
                Set<String> warehouse = new HashSet<String>();
                String warehouseName = null;
                String priorWarehouseOrderInfo = null;
                String orderCode = null;
                String allSkus = null;
                int warehouseNum = 0;

                // Eco001 : barcode --> quantity
                Map<String, Map<String, Integer>> singleOrderInfo = new HashMap<String, Map<String, Integer>>();
                Map<String, Integer> allSku = new HashMap<String, Integer>();
                while ((lineStr = br.readLine()) != null) {
                    warehouseName = lineStr.split(FIELDSDELIMITED)[0];
                    orderCode = lineStr.split(FIELDSDELIMITED)[1];
                    allSkus = lineStr.split(FIELDSDELIMITED)[2];

                    // 历史数据中已经存在当前仓库的订单数据
                    if (warehouse.contains(warehouseName)) {
                        allSku = new HashMap<String, Integer>();
                        singleOrderInfo = new HashMap<String, Map<String, Integer>>();
                        // 前一个warehouName跟当前warehouseName不同,说明给到的数据是乱序的
                        // 不能把前一个warehouseName订单信息放在当前warehouseName
                        // 但也要保证前一个对象的信息不能清空
//                        if (!priorWarehouseOrderInfo.equals(warehouseName)) {
//                            multiOrderDetailInfo2 = multiOrderDetailInfo;
//                        } else {
                        splitAllSkuAndPutMap(allSku, allSkus);
                        singleOrderInfo.put(orderCode, allSku);
                        splitAllSkuAndPutMap(allSku, allSkus);
                        singleOrderInfo.put(orderCode, allSku);
//                        }
                        // 如果是第一次遍历到一个仓库
                    } else {
                        allSku = new HashMap<String, Integer>();
                        singleOrderInfo = new HashMap<String, Map<String, Integer>>();
                        multiOrderDetailInfo = new ArrayList<Map<String, Map<String, Integer>>>();
                        splitAllSkuAndPutMap(allSku, allSkus);
                        singleOrderInfo.put(orderCode, allSku);

                        warehouse.add(warehouseName);
                    }

                    multiOrderDetailInfo.add(singleOrderInfo);
                    warehouseOrderDetailInfo.put(warehouseName, multiOrderDetailInfo);
                    priorWarehouseOrderInfo = warehouseName;
                }
                br.close();
                isr.close();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (br != null) {
                    try {
                        isr.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (isr != null) {
                    try {
                        isr.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } else {
            System.out.println("file is not a file or file is not existing!");
        }
        return warehouseOrderDetailInfo;
    }

    /**
     * 拿到读取到的一行数据,切分后putMap
     *
     * @param allSku* @param allSkus*/
    public void splitAllSkuAndPutMap(Map<String, Integer> allSku, String allSkus) {
        int len = allSkus.split(MULTISKUINFODELIMITED).length;
        for (int i = 0; i < len; i++) {
            String barCodeQuantity = allSkus.split(MULTISKUINFODELIMITED);
            String barCode = barCodeQuantity.split(SINGLESKUINFODELIMITED)[0];
            int quantity = Integer.parseInt(barCodeQuantity.split(SINGLESKUINFODELIMITED)[1]);
            allSku.put(barCode, quantity);
        }
    }

    /**
     * 解析各仓库的所有订单
     *
     * @param warehouseOrderDetailInfo* @return map 仓库名 --> List<List<Long>>  一个订单中的多个barcodeList<Long>
     */
    public Map<String, List<List<Long>>> aggWarehouseOrders(
            Map<String, List<Map<String, Map<String, Integer>>>> warehouseOrderDetailInfo) {
        Map<String, List<List<Long>>> warehouseOrderCodes = new HashMap<String, List<List<Long>>>();
        List<List<Long>> barCodes = new ArrayList<List<Long>>();
        List<Long> singleOrderBarCodes = new ArrayList<Long>();
        // all warehouseName
        Set<String> warehouse = new HashSet<String>();
        // step2.1 拿到所有仓库名称
        for (String warehouName : warehouseOrderDetailInfo.keySet()) {
            barCodes = new ArrayList<List<Long>>();
//            仓库中的订单数
            int warehouseOrderSize = warehouseOrderDetailInfo.get(warehouName).size();
            // 逐个仓库遍历
            for (int i = 0; i < warehouseOrderSize; i++) {
//                barCodes = new ArrayList<List<Long>>();
                Map<String, Map<String, Integer>> orderInfo = warehouseOrderDetailInfo.get(warehouName).get(i);
                // 拿到指定仓库的所有订单号
                for (Iterator<String> it = orderInfo.keySet().iterator(); it.hasNext(); ) {
                    String orderCode = it.next();
                    Map<String, Integer> barCodeQuantity = orderInfo.get(orderCode);
                    singleOrderBarCodes = new ArrayList<Long>();
                    // 拿到一个仓库中每个订单中所有barCode
                    for (String barCode : barCodeQuantity.keySet()) {
                        singleOrderBarCodes.add(Long.valueOf(barCode));
                    }
                    singleOrderBarCodes.sort(null);
                    barCodes.add(singleOrderBarCodes);
                }
            }
            warehouseOrderCodes.put(warehouName, barCodes);
        }
        return warehouseOrderCodes;
    }

    /**
     * 将给定的一组订单中的商品排列组合(全组合非全排列,abc    bca  相当于同一个组合)
     *
     * @param list* @return*/
    public List<List<Long>> combinationOrders(List<Long> list) {
        List<List<Long>> result = new ArrayList<List<Long>>();
        // 可能出现的次数为2^n
        long n = (long) Math.pow(2, list.size());
        List<Long> combine;
        for (long l = 0L; l < n; l++) {
            combine = new ArrayList<Long>();
            // 遍历给定数组
            for (int i = 0; i < list.size(); i++) {
                // 与运算后若是结果为1(同时1才是1),说明是一个新组合
                if ((l >>> i & 1) == 1) {
                    combine.add(list.get(i));
                }
            }
            result.add(combine);
        }
        return result;
    }

    /**
     * 获取每个仓库的所有子组合
     *
     * @param warehouseCombinationOrders* @return*/
    public Map<String, List<List<Long>>> getWarehouseAllChildCombination(
            Map<String, List<List<List<Long>>>> warehouseCombinationOrders) {

        // 开关:仓库遍历到最后一个的时候,停止创建新的对象,前面所有数据已经全部putMap
        int num = 1;
        // 仓库数量
        int warehouseNum = warehouseCombinationOrders.size();
        // 每个仓库中的所有订单的所有子组合
        Map<String, List<List<Long>>> warehouseAllChildCombination = new HashMap<String, List<List<Long>>>();
        // 一个订单中的所有子组合
        List<List<Long>> childCombination = new ArrayList<List<Long>>(500);
        // 遍历所有仓库
        for (String warehouseName : warehouseCombinationOrders.keySet()) {
            int ordersQuantity = warehouseCombinationOrders.get(warehouseName).size();
            // 取出指定仓库所有订单的barCode组合
            for (int i = 0; i < ordersQuantity; i++) {
                int combinationQuantity = warehouseCombinationOrders.get(warehouseName).get(i).size();
                // 拼接指定仓库指定订单的所有子组合
                for (int j = 0; j < combinationQuantity; j++) {
                    if (warehouseCombinationOrders.get(warehouseName).get(i).get(j).size() > 0) {
                        childCombination.add(warehouseCombinationOrders.get(warehouseName).get(i).get(j));
                    }
                }
            }
            warehouseAllChildCombination.put(warehouseName, childCombination);
            childCombination = new ArrayList<List<Long>>(500);
//            System.out.println(warehouseAllChildCombination);
            num++;
            if (num < warehouseNum - 1) {
                warehouseAllChildCombination = new HashMap<String, List<List<Long>>>();
            }
        }

        return warehouseAllChildCombination;
    }
}

结果如下:
武汉仓:[69001] : 1
武汉仓:[69002] : 1
武汉仓:[69001, 69002] : 1
武汉仓:[69003] : 1
武汉仓:[69001, 69003] : 1
武汉仓:[69002, 69003] : 1
武汉仓:[69001, 69002, 69003] : 1
武汉仓:[69006] : 1
武汉仓:[69001, 69006] : 1
武汉仓:[69002, 69006] : 1
武汉仓:[69001, 69002, 69006] : 1
武汉仓:[69003, 69006] : 1
武汉仓:[69001, 69003, 69006] : 1
武汉仓:[69002, 69003, 69006] : 1
武汉仓:[69001, 69002, 69003, 69006] : 1
武汉仓:[69008] : 1
武汉仓:[69001, 69008] : 1
武汉仓:[69002, 69008] : 1
武汉仓:[69001, 69002, 69008] : 1
武汉仓:[69003, 69008] : 1
武汉仓:[69001, 69003, 69008] : 1
武汉仓:[69002, 69003, 69008] : 1
武汉仓:[69001, 69002, 69003, 69008] : 1
武汉仓:[69006, 69008] : 1
武汉仓:[69001, 69006, 69008] : 1
武汉仓:[69002, 69006, 69008] : 1
武汉仓:[69001, 69002, 69006, 69008] : 1
武汉仓:[69003, 69006, 69008] : 1
武汉仓:[69001, 69003, 69006, 69008] : 1
武汉仓:[69002, 69003, 69006, 69008] : 1
武汉仓:[69001, 69002, 69003, 69006, 69008] : 1
临江仓:[69001] : 3
临江仓:[69002] : 2
临江仓:[69001, 69002] : 1
临江仓:[69003] : 3
临江仓:[69001, 69003] : 2
临江仓:[69002, 69003] : 2
临江仓:[69001, 69002, 69003] : 1
临江仓:[69001] : 3
临江仓:[69002] : 2
临江仓:[69003] : 3
临江仓:[69002, 69003] : 2
临江仓:[69001] : 3
临江仓:[69003] : 3
临江仓:[69001, 69003] : 2
临江仓:[69011] : 1
临江仓:[69012] : 1
临江仓:[69011, 69012] : 1
郑州仓:[69003] : 3
郑州仓:[69006] : 3
郑州仓:[69003, 69006] : 3
郑州仓:[69003] : 3
郑州仓:[69006] : 3
郑州仓:[69003, 69006] : 3
郑州仓:[69003] : 3
郑州仓:[69006] : 3
郑州仓:[69003, 69006] : 3

同样一份数据可以用在不同的场合计算,类似的数据只要口径稍微调整就可以给运营给企划,给产品等各个不同部门人使用。

已有(2)人评论

跳转到指定楼层
美丽天空 发表于 2018-12-7 22:30:11
向大佬学习了
回复

使用道具 举报

zhuqitian 发表于 2019-1-28 17:52:26

排版不好,給人看了沒啥感覺,但是内容是乾貨
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条