Featured image of post 算法与应用(一):笛卡尔积 (Cartesian product) 运算在电商项目中的应用-附 Demo

算法与应用(一):笛卡尔积 (Cartesian product) 运算在电商项目中的应用-附 Demo

迪卡尔积(Cartesian product)是一种数学概念,广泛应用于计算机图形学和相关的编程领域,在处理数据库、集合操作等问题时非常有用。本文主要借助电商项目应用场景,简单介绍迪卡积运算的使用。

做电商,绕不开一个叫做 SKU 的概念。SKU 全称为Stock Keeping Unit(库存量单位),在国内有时被称为单品。对一种商品(SPU)而言,当其品牌、型号、配置、等级、花色、包装容量、单位、生产日期、保质期、用途、价格、产地等属性中任一属性与其他商品存在不同时,可称为一个单品(SKU)。基于这样的概念,延伸出许多前后端需求。

在前端代码中,用户进行购买操作时,预测一个商品在不同 SKU 规格下的可选路径,是比较常见的做法。

需求

用户下单时,对商品规格作出选择,有时一个商品会有多种规格组合的属性,比如颜色可选黑、白、灰,尺寸可选大、中、小,型号可选A、B、C,每一种选择组合,对应的 SKU 的价格、库存情况可能有所不同。它们看起来像下图这样一个 3*3 的阵列,用户实际可能选择的组合多达 27 种。

img

此时,前端要跟据实际情况作一些处理,主要有四个方面:

  1. 27 个组合中,某些组合可能并没有库存,在用户选中一个规格后,需要将接下来不可选的路径置灰并不可选择;
  2. 跟据用户已选规格,预测出其他可选规格,将可选规格路径高亮出来;
  3. 一些电商应用会要求,进入商品页面时,将最优规格组合(如价格最优、性能最优)优先选中;
  4. 用户每选中一组规格组合(SKU),页面应显示出该 SKU 对应的库存、售价情况。

笛卡尔积

后端 api 接口在开发中,通常只能给出该商品已有库存的 sku ,它们看起来像这样:

    const goodsData = [
      {
        skuId: "3158054",
        price: 999,
        stock: 99,
        sale_attrs: [
          { name: "颜色", value: "黑" },
          { name: "尺码", value: "大" },
          { name: "型号", value: "A" }
        ]
      },
      {
        skuId: "3158055",
        price: 99,
        stock: 9,
        sale_attrs: [
          { name: "颜色", value: "白" },
          { name: "尺码", value: "中" },
          { name: "型号", value: "B" }
        ]
      },
      {
        skuId: "3158056",
        price: 1000,
        stock: 7,
        sale_attrs: [
          { name: "颜色", value: "灰" },
          { name: "尺码", value: "小" },
          { name: "型号", value: "C" }
        ]
      }
    ];

跟据上面的数据,我们通常需要将用户的可选规格 sale_attrs 归类、梳理出来,并作去重处理,得到一个所有备选规格的矩阵:

    const attributes = {
      颜色: ["黑", "白", "灰"],
      尺码: ["大", "中", "小"],
      型号: ["A", "B", "C"]
    };

此时,如果我们对矩阵中的值穷尽组合可能的 sku ,那么结果将远远大于 api 接口给我们的已有库存商品,这些所有可能的 sku ,是一个笛卡尔积(Cartesian product)。穷举后结果如下:

const resultArr = [
    ['黑', '大', 'A'],
    ['黑', '大', 'B'],
    ['黑', '大', 'C'],
    ['黑', '中', 'A'],
    ['黑', '中', 'B'],
    ['黑', '中', 'C'],
    ['黑', '小', 'A'],
    ['黑', '小', 'B'],
    ['黑', '小', 'C'],
    ['白', '大', 'A'],
    ['白', '大', 'B'],
    ['白', '大', 'C'],
    ['白', '中', 'A'],
    ['白', '中', 'B'],
    ['白', '中', 'C'],
    ['白', '小', 'A'],
    ['白', '小', 'B'],
    ['白', '小', 'C'],
    ['灰', '大', 'A'],
    ['灰', '大', 'B'],
    ['灰', '大', 'C'],
    ['灰', '中', 'A'],
    ['灰', '中', 'B'],
    ['灰', '中', 'C'],
    ['灰', '小', 'A'],
    ['灰', '小', 'B'],
    ['灰', '小', 'C']
]

对于一个 3*3 的阵列来说,结果多达 27 种,当这样的幂运算任何一项有增加时,结果都将大大增加。

核心方法

我们封装一种方法,专门用来推导出所有可能的组合。

    function cartesianProductOf() {
      return Array.prototype.reduce.call(
        arguments,
        function (a, b) {
          var ret = [];
          a.forEach(function (a) {
            b.forEach(function (b) {
              ret.push(a.concat([b]));
            });
          });
          return ret;
        },
        [[]]
      );
    }

此时将计算素材放进去,就能计算出所有组合:

    let arr = [
      [1, 2, 3],
      [4, 5, 6],
      [7, 8, 9],
      [10, 11, 12]
    ];

    let allArr = cartesianProductOf(...arr);

处理思路和步骤与 Demo

根据前文中的需求,拆分处理步骤,主要是加工和变造数据,计算集合,筛选数据这几步,页面根据筛选出来的数据进行判断和渲染。

由于核心原理已详细阐述,以下仅演示 Demo(基于 Vue3,iframe 嵌入):