zhuqitian 发表于 2018-12-6 10:04:21

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

电商中信息流的实时把控至关重要,在仓库中商品摆放时货架和货位的选择影响了拣货的效率,日常和活动和大促等各种场景下摆放可能会有区别,若是统一位置摆放可能会造成拣货效率低,人工成本高,甚至用户体验差。如果我们能把系统订单中已推单未配货的数据及时拿到加以提炼,找出商品的最佳组合进行排序,那种组合出现的次数最多那么,这些组合中的商品应该要优先摆放在最容易拿到的货架上面,举例说明:仓库名称   订单号      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是一个大数组,大数组中存放一个Map,key为订单号,value为订单号对应的商品明细。然后把所有的订单明细组合起来,进行排列组合。例如:1,2,3,4,55个商品可能会出现的组合:12,13,14,1523,24,2534,3545123,124,125134,1351451234,123513451    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. 实现排列组合,每一个订单中可能有多个barCode,barcode越多,子组合数也就越多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 warehouName,get 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:,ECO002:
      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);
                  orderCode = lineStr.split(FIELDSDELIMITED);
                  allSkus = lineStr.split(FIELDSDELIMITED);

                  // 历史数据中已经存在当前仓库的订单数据
                  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;
    }

    /**
   * 拿到读取到的一行数据,切分后put到Map中
   *
   * @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);
            int quantity = Integer.parseInt(barCodeQuantity.split(SINGLESKUINFODELIMITED));
            allSku.put(barCode, quantity);
      }
    }

    /**
   * 解析各仓库的所有订单
   *
   * @param warehouseOrderDetailInfo* @return map : 仓库名 --> List<List<Long>>一个订单中的多个barcode在List<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) {

      // 开关:仓库遍历到最后一个的时候,停止创建新的对象,前面所有数据已经全部put到Map中
      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;
    }
}
结果如下:武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1武汉仓: : 1临江仓: : 3临江仓: : 2临江仓: : 1临江仓: : 3临江仓: : 2临江仓: : 2临江仓: : 1临江仓: : 3临江仓: : 2临江仓: : 3临江仓: : 2临江仓: : 3临江仓: : 3临江仓: : 2临江仓: : 1临江仓: : 1临江仓: : 1郑州仓: : 3郑州仓: : 3郑州仓: : 3郑州仓: : 3郑州仓: : 3郑州仓: : 3郑州仓: : 3郑州仓: : 3郑州仓: : 3
同样一份数据可以用在不同的场合计算,类似的数据只要口径稍微调整就可以给运营给企划,给产品等各个不同部门人使用。

美丽天空 发表于 2018-12-7 22:30:11

向大佬学习了

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

美丽天空 发表于 2018-12-7 22:30
向大佬学习了

排版不好,給人看了沒啥感覺,但是内容是乾貨
页: [1]
查看完整版本: 计算订单明细中那些组合商品更受欢迎