const type = ["男裤", "女裤"] const color = ["黑色", "白色"] const size = ["S","L"]那么根据现有规格,可以得到所有的 SKU 为:
[ ["男裤", "黑色", "S"], ["男裤", "黑色", "L"], ["男裤", "白色", "S"], ["男裤", "白色", "L"], ["女裤", "黑色", "S"], ["女裤", "黑色", "L"], ["女裤", "白色", "S"], ["女裤", "白色", "L"], ]上述 SKU 是如何得到的呢,让我们一起看看实现思路,并且通过上面的🌰 来计算一遍。
/** * 堆代码 duidaima.com * 笛卡尔积组装 * @param {Array} list * @returns [] */ function descartes(list) { // parent 上一级索引;count 指针计数 let point = {}; // 准备移动指针 let result = []; // 准备返回数据 let pIndex = null; // 准备父级指针 let tempCount = 0; // 每层指针坐标 let temp = []; // 组装当个 sku 结果 // 一:根据参数列生成指针对象 for (let index in list) { if (typeof list[index] === 'object') { point[index] = { parent: pIndex, count: 0 }; pIndex = index; } } // 单维度数据结构直接返回 if (pIndex === null) { return list; } // 动态生成笛卡尔积 while (true) { // 二:生成结果 let index; for (index in list) { tempCount = point[index].count; temp.push(list[index][tempCount]); } // 压入结果数组 result.push(temp); temp = []; // 三:检查指针最大值问题,移动指针 while (true) { if (point[index].count + 1 >= list[index].length) { point[index].count = 0; pIndex = point[index].parent; if (pIndex === null) { return result; } // 赋值 parent 进行再次检查 index = pIndex; } else { point[index].count++; break; } } } }让我们看看实际的输入输出和调用结果。
const type = ["男裤", "女裤"] const color = ["黑色", "白色"] const size = ["S","L"]假设总 SKU 的库存值为下面示例,可选为有库存,不可选为某项规格无库存
[ ["男裤", "黑色", "S"], // S 无号 ["男裤", "黑色", "L"], ["男裤", "白色", "S"], // S 无号 ["男裤", "白色", "L"], ["女裤", "黑色", "S"], // S 无号 ["女裤", "黑色", "L"], ["女裤", "白色", "S"], // S 无号 ["女裤", "白色", "L"], ]那么根据邻接矩阵思想,可以得到结果图:
假设一个集合 A{a, b, c} 和另外一个集合 B{a, e},如何快速判断 B 是否是 A 的子集。这个问题比较简单的方法是用 B 中所有元素依次和 A 中的元素进行比较,对于集合中的元素,每个元素值都是唯一的。通过这样的特性,我们可以把所有字母转换为一个质数,那么 集合 A 可以表示为集合元素(质数)的积,B 同样,B 是否是 A 的子集,这个只需要将 B 除以 A,看看是否可以整除 ,如果可以那么说明,B 是 A 的子集。
那么根据邻接矩阵思路,整条 SKU 都会有一个集合值,集合值由所有涉及规格对应乘积得到的结果,在选择规格过程中,每次选择去根据集合值去反向整除规格对应值去判断是否是子集,是否为 1。
/** * 堆代码 duidaima.com * 准备质树 * @param {Int} num 质数范围 * @returns */ getPrime: function (num) { // 从第一个质数 2 开始 let i = 2; const arr = []; /** * 检查是否是质数 * @param {Int} number * @returns */ const isPrime = (number) => { for (let ii = 2; ii < number / 2; ++ii) { if (number % ii === 0) { return false; } } return true; }; // 循环判断,质数数量够完成返回 for (i; arr.length < total; ++i) { if (isPrime(i)) { arr.push(i); } } // 返回需要的质数 return arr; } // 上述动图入参以及返回结果展示: // getPrime(500) return==> // 0: (8) [2, 3, 5, 7, 11, 13, 17, 19] // 1: (8) [23, 29, 31, 37, 41, 43, 47, 53] // 2: (8) [59, 61, 67, 71, 73, 79, 83, 89] // 3: (8) [97, 101, 103, 107, 109, 113, 127, 131] // 4: (8) [137, 139, 149, 151, 157, 163, 167, 173] // 5: (8) [179, 181, 191, 193, 197, 199, 211, 223] // 6: (8) [227, 229, 233, 239, 241, 251, 257, 263]初始化处理,得到第一批邻接矩阵结果:
/** * 初始化,格式需要对比数据,并进行初始化是否可选计算 */ init: function () { this.light = util.cloneTwo(this.maps, true); var light = this.light; // 默认每个规则都可以选中,即赋值为 1 for (var i = 0; i < light.length; i++) { var l = light[i]; for (var j = 0; j < l.length; j++) { this._way[l[j]] = [i, j]; l[j] = 1; } } // 对应结果值,此处将数据处理的方法对应邻接矩阵的思维导图 // 0: (8) [1, 1, 1, 1, 1, 1, 1, 1] // 1: (8) [1, 1, 1, 1, 1, 1, 1, 1] // 2: (8) [1, 1, 1, 1, 1, 1, 1, 1] // 3: (8) [1, 1, 1, 1, 1, 1, 1, 1] // 4: (8) [1, 1, 1, 1, 1, 1, 1, 1] // 5: (8) [1, 1, 1, 1, 1, 1, 1, 1] // 6: (8) [1, 1, 1, 1, 1, 1, 1, 1] // 得到每个可操作的 SKU 质数的集合 for (i = 0; i < this.openway.length; i++) { // 计算结果单行示例: // this.openway[i].join('*') ==> eval(2*3*5*7*11*13*17*19) this.openway[i] = eval(this.openway[i].join('*')); } // return 初始化得到规格位置,规格默认可选处理,可选 SKU 的规格对应的质数合集 this._check(); }计算是否可选方法:
/** * 检查是否可以选择,更新邻接矩阵对应结果值 * @param {Boolean} isAdd 是否新增状态 * @returns */ _check: function (isAdd) { var light = this.light; var maps = this.maps; for (var i = 0; i < light.length; i++) { var li = light[i]; var selected = this._getSelected(i); for (var j = 0; j < li.length; j++) { if (li[j] !== 2) { //如果是加一个条件,只在是 light 值为 1 的点进行选择 if (isAdd) { if (li[j]) { light[i][j] = this._checkItem(maps[i][j], selected); } } else { light[i][j] = this._checkItem(maps[i][j], selected); } } } } return this.light; }, /** * 检查是否可选内容,更新邻接矩阵对应结果值 * @param {Int} item 当前规格质数 * @param {Array} selected * @returns */ _checkItem: function (item, selected) { // 拿到可以选择的 SKU 内容集合 var openway = this.openway; var val; // 拿到已经选中规格集合*此规格集合值 val = item * selected; // 可选 SKU 集合反除,查询是否可选 for (var i = 0; i < openway.length; i++) { this.count++; if (openway[i] % val === 0) { return 1; } } return 0; }添加规格方法:
/** 选择可选规格后处理 * @param {array} point [x, y] */ add: function (point) { point = point instanceof Array ? point : this._way[point]; // 得到选中规格对应的质数内容 var val = this.maps[point[0]][point[1]]; // 检查是否可选中 if (!this.light[point[0]][point[1]]) { throw new Error( 'this point [' + point + '] is no availabe, place choose an other' ); } // 判断是否选中内容已经存在已经选择内容中 if (val in this.selected) return; var isAdd = this._dealChange(point, val); this.selected.push(val); // 选择后邻接矩阵对应数据修改为 2,以做是否可选区分 this.light[point[0]][point[1]] = 2; this._check(!isAdd); }移除已选规格方法:
/** * 移除已选规格 * @param {Array} point */ remove: function (point) { point = point instanceof Array ? point : this._way[point]; // 容错处理 try { var val = this.maps[point[0]][point[1]]; } catch (e) {} if (val) { // 在选中内容中,定位取出需要移除规格质数 for (var i = 0; i < this.selected.length; i++) { if (this.selected[i] == val) { var line = this._way[this.selected[i]]; // 对应邻接矩阵内容更新为可选 this.light[line[0]][line[1]] = 1; // 从已选内容中移除 this.selected.splice(i, 1); } } // 进行重新计算 this._check(); } }总结