title: 商品 SKU 组合查询算法实现 date: 2019-09-25 08:44:01 categories: Web tags:
前言
在系统学习了98集的数据结构和算法之后,以为自己的水平已经达到了一个新的高度了。但是在做一个APP的时候,发现自己竟然处理不了加入购物车的多级SKU对应的库存问题。着实是一个不小的打击。在解决问题之后,记录下来,牢记耻辱。
什么是SKU
维基百科解释:
存货单位(英语:stock keeping unit,SKU/ˌɛsˌkeɪˈjuː/)[1],也翻译为库存单元,是一个会计学名词,定义为库存管理中的最小可用单元,例如纺织品中一个SKU通常表示规格、颜色、款式,而在连锁零售门店中有时称单品为一个SKU。最小库存管理单元可以区分不同商品销售的最小单元,是科学管理商品的采购、销售、物流和财务管理以及POS和MIS系统的数据统计的需求,通常对应一个管理信息系统的编码。
举个例子: 如图所示
该商品有四个属性单位 公母、重量、数量、颜色分类 四个属性又有对应的 单位
{
'公母' : ['公的', '母的'],
'重量' : ['100克', '200克', '300克', '400克']
'数量' : ['一对', '两对', '三对', '四对']
'颜色分类' : ['花色', '原色', '黑色', '金色']
}
如图选中的的 ['200克', '三对'] 是一个SKU,
['公的', '花色']
['300克', '三对']
['三对']
['母的','300克', '三对']
['公的','100克', '三对','原色']
这些组合都是一个SKU
也就是说明,公母、重量、数量、颜色分类对应的属性,单独或者两个属性或者以上的组合都是一个SKU
业务场景
在电商项目很几率在遇上,商品 SKU 组合查询的实现。如图:
以 公母、重量组合成对应的 SKU如下表格:
| 公母 | 重量 | 库存 |
|---|---|---|
| 公 | 100克 | 10 |
| 公 | 200克 | 10 |
| 公 | 300克 | 10 |
| 公 | 400克 | 10 |
| 母 | 100克 | 10 |
| 母 | 200克 | 10 |
| 母 | 300克 | 0 |
| 母 | 400克 | 10 |
图中只是选中的 ['母'] 有库存,然后查找对照上面的表格 ['母', '300克']。当用户选择['母'] 的时候,那么对应的['母的', '300克'], 300克的选项应该显示为不可选择状态
(平常用淘宝的觉得这个功能很简单,但...实际开发起来自己却不会(尴尬)。)
解决方案
场景还原
// 属性集
var key = [
{name: '公母', item: ['公的', '母的']},
{name: '重量', item: ['100克', '200克', '300克']},
{name: '数量', item: ['一对', '两对', '三对']}
];
// 数据集
var sku = {
'公的;100克;一对': {price: 100, count: 10},
'公的;200克;一对': {price: 101, count: 0},
'公的;300克;一对': {price: 102, count: 12},
'母的;100克;一对': {price: 103, count: 13},
'母的;200克;一对': {price: 104, count: 14},
'母的;300克;一对': {price: 105, count: 3},
'公的;100克;两对': {price: 100, count: 0},
'公的;200克;两对': {price: 101, count: 11},
'公的;300克;两对': {price: 102, count: 9},
'母的;100克;两对': {price: 100, count: 0},
'母的;200克;两对': {price: 101, count: 11},
'母的;300克;两对': {price: 102, count: 0},
'公的;100克;三对': {price: 100, count: 10},
'公的;200克;三对': {price: 101, count: 11},
'公的;300克;三对': {price: 102, count: 3},
'母的;100克;三对': {price: 100, count: 10},
'母的;200克;三对': {price: 101, count: 11},
'母的;300克;三对': {price: 102, count: 2}
};
有了这两组数据,就可以渲染 DOM 结构,然后用户点击的时候,获取对应的 key 拼接,再根据key从 数据集 里面 取得对应的信息
See the Pen 商品组合SKU算法实现(基础版) by bamboo (@celverbamboo) on CodePen.上面这个演示有个最大的问题就是,必须把每个属性都选择后,才能获取到对应的库存和价格,如果没有选择完整,就无法获取对应的数据。
原因也很简单,因为 数据集 里没有提供嘛。比如只选择了['公的'],拼接出来之后,就变成了公的;;,再看看数据集,里面没有所以需要对数据集进行处理。
对数据集进行处理
首先,我们希望得到如下数据
当前sku为 ['公的', '100克', '一对']
['公的', '', '']
['', '100克', '']
['', '', '一对']
['公的', '100克', '']
['公的', '', '一对']
['', '100克', '一对']
['公的', '100克', '一对']
如果sku['公的', '100克', '一对', '花色']
['公的', '', '', '']
['', '100克', '', '']
['', '', '一对', '']
['', '', '', '花色']
['黑', '100克', '', '']
['黑', '', '一对', '']
['黑', '', '', '花色']
['', '100克', '一对', '']
['', '100克', '', '花色']
['', '', '一对', '花色']
['黑', '100克', '一对', '']
['黑', '100克', '', '花色']
['黑', '', '一对', '花色']
['', '100克', '一对', '花色']
['黑', '100克', '一对', '花色']
由此可以得出,我们需要 从 m 个不同元素中取出 n 个元素的组合算法,然后根据该组合,并且获取对应的sku的key,把重复sku的key库存加上。
算法代码(JS版)
// 属性集
const keys = [
['10', '11'],
['20', '21'],
['30', '31', '32']
];
// 数据集, 一般由后台返回
const data = {
'10;20;30': { price: 1, count: 0 },
'10;20;31': { price: 2, count: 2 },
'10;20;32': { price: 3, count: 3 },
'11;20;30': { price: 4, count: 4 },
'11;20;31': { price: 5, count: 5 },
'11;20;32': { price: 6, count: 6 },
'10;21;30': { price: 7, count: 0 },
'10;21;31': { price: 8, count: 8 },
'10;21;32': { price: 9, count: 0 },
'11;21;30': { price: 10, count: 10 },
'11;21;31': { price: 11, count: 11 },
'11;21;32': { price: 12, count: 0 },
};
//保存最后的组合结果信息
const SKUResult = {};
// 调用函数
initSKU();
console.log(SKUResult);
//初始化得到结果集
function initSKU() {
let i, j, skuKeys = getObjKeys(data); // 过滤获取所有对应的sku-key数组
for (i = 0; i < skuKeys.length; i++) {
const skuKey = skuKeys[i];//一条SKU信息key
const sku = data[skuKey]; //一条SKU信息value
const skuKeyAttrs = skuKey.split(';'); //SKU信息key属性值数组
skuKeyAttrs.sort(function(value1, value2) {
return parseInt(value1) - parseInt(value2);
});
//对每个SKU信息key属性值进行拆分组合
const combArr = combInArray(skuKeyAttrs);
for (j = 0; j < combArr.length; j++) {
addSKUResult(combArr[j], sku);
}
//结果集接放入SKUResult
SKUResult[skuKeyAttrs.join(';')] = {
count: sku.count,
prices: [sku.price]
}
}
}
//获得对象的key
function getObjKeys(obj) {
if (obj !== Object(obj)) throw new TypeError('Invalid object');
const keys = [];
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
keys[keys.length] = key;
}
}
return keys;
}
//把组合的key放入结果集SKUResult
function addSKUResult(combArrItem, sku) {
const key = combArrItem.join(';');
if (SKUResult[key]) {//SKU信息key属性·
SKUResult[key].count += sku.count;
SKUResult[key].prices.push(sku.price);
} else {
SKUResult[key] = {
count: sku.count,
prices: [sku.price]
};
}
}
/**
* 得到从 m 元素中取 n 元素的所有组合
* 结果为[0,1...]形式的数组, 1表示选中,0表示不选
*/
function getCombFlags(m, n) {
if (!n || n < 1) {
return [];
}
const aResult = [];
const aFlag = [];
let bNext = true;
let i, j, iCnt1;
for (i = 0; i < m; i++) {
aFlag[i] = i < n ? 1 : 0;
}
aResult.push(aFlag.concat());
while (bNext) {
iCnt1 = 0;
for (i = 0; i < m - 1; i++) {
if (aFlag[i] === 1 && aFlag[i + 1] === 0) {
for (j = 0; j < i; j++) {
aFlag[j] = j < iCnt1 ? 1 : 0;
}
aFlag[i] = 0;
aFlag[i + 1] = 1;
const aTmp = aFlag.concat();
aResult.push(aTmp);
if (aTmp.slice(-n).join('').indexOf('0') === -1) {
bNext = false;
}
break;
}
aFlag[i] === 1 && iCnt1++;
}
}
return aResult;
}
/**
* 从数组中生成指定长度的组合
* 方法: 先生成[0,1...]形式的数组, 然后根据0,1从原数组取元素,得到组合数组
*/
function combInArray(aData) {
if (!aData || !aData.length) {
return [];
}
const len = aData.length;
const aResult = [];
for (let n = 1; n < len; n++) {
const aaFlags = getCombFlags(len, n);
// console.log(JSON.stringify(aaFlags))
while (aaFlags.length) {
const aFlag = aaFlags.shift();
const aComb = [];
for (let i = 0; i < len; i++) {
aFlag[i] && aComb.push(aData[i]);
}
aResult.push(aComb);
}
}
return aResult;
}
在调用initSKU之后,打印 SKUResult则获得以下结果。
完整版
在获得数据集之后,在点击对应的SKU属性之后,应该获取当前的SKU,然后和其它的属性对应的SKU进行遍历拼接。即可获得从 SKUResult的商品信息。效果如图:
See the Pen 商品组合SKU实现算法(完整版) by bamboo (@celverbamboo) on CodePen.