首页 > [原] 淘宝SKU组合查询算法实现

[原] 淘宝SKU组合查询算法实现

前端有多少事情可以做,能做到多好。一直在关注各大公司UED方面的知识,他们也代表了前端的力量,而且也很乐意和大家分享,把应用到项目的知识归类整理,再写成博客搬到网上来,充实这前端的内容,也是为想追寻和学习的人提供了场所,为想接触到一些前沿的知识提供了去处,感谢有这么一群人。大的科技公司基本都有自己的前端部门或团队,在网上也能看到他们的动态,像淘宝、阿里巴巴、腾讯、百度等等。

前段时间在淘宝UED官网上看到一篇SKU组合查询算法探索,当时看过以后只是感觉挺牛的,而且讲的很具体,实现步骤和代码都想说的很详细,几种算法以及算法的复杂度都很有深入的分析,挺佩服这种专研精神的,当时只是隐约的感觉到这个算法在解决电商的商品拆分属性选择中可能会用到,但是具体的实现细节也没进行尝试。

后来公司正好要做一个项目,而且用的就是淘宝商品数据结构,商品详情页是属性选择也和淘宝的很类似,当时就想到了那篇文章,于是有反复看来两三遍,试了一下上面说的第二种算法(已经给出了源码),实现起来也不麻烦,虽然例子中给出的第二种算法得到的结果只有商品数量,但是经过修改也可以得到商品的价格,本打算这样就可以直接用的项目中好了。但是在看到第二种算法的优化后(没有提供源码),就想按照这种方式来实现,也是最初萌发出来的想法一致。

第二种算法会有大量的组合,它是基于原始属性值的结果组合和递归,而不是基于结果集的。其实第二种算法的优化,基于结果集的算法实现起来也不麻烦,原理就是把结果集的SKU中key值进行更小拆分组合,把拆分和组合后的结果信息放到SKUResult里面,这样在初始化一次完成,后面的选择可以根据这个结果集使用。把组合范围减少到key里面,这样能够搜索范围避免递归,而且得到的每个小的组合属性值的结果有用信息很丰富,数量和价格都包括其中。

但是又过了一段时间以后,项目被搁浅了,也不知道以后能用上不能了,写的示例也搁了许久,再不拿出来晾晾估计都该长毛变味了。

示例如下

测试地址: http://jsfiddle.net/tianshaojie/aGggS/

下载地址:http://files.cnblogs.com/purediy/sku-20140802.rar

主要JS代码实现如下

var startTime = new Date().getTime();
//属性集
var keys = [['10'],['20','21','22','23','24'],['30','31','32','33','34','35','36','37','38'],['40']];//后台读取结果集
var data = {"10;24;31;40": {price:366,count:46},"10;24;32;40": {price:406,count:66},"10;24;33;40": {price:416,count:77},"10;24;34;40": {price:456,count:9},"10;24;35;40": {price:371,count:33},"10;24;36;40": {price:411,count:79},"10;24;37;40": {price:421,count:87},"10;24;38;40": {price:461,count:9},"10;24;30;40": {price:356,count:59},"10;23;31;40": {price:366,count:50},"10;23;32;40": {price:406,count:9},"10;23;33;40": {price:416,count:90},"10;23;34;40": {price:456,count:10},"10;23;35;40": {price:371,count:79},"10;23;36;40": {price:411,count:90},"10;23;37;40": {price:421,count:10},"10;23;38;40": {price:461,count:9},"10;23;30;40": {price:356,count:46},"10;22;31;40": {price:356,count:27},"10;22;32;40": {price:396,count:38},"10;22;33;40": {price:406,count:42},"10;22;34;40": {price:446,count:50},"10;22;35;40": {price:361,count:25},"10;22;36;40": {price:401,count:40},"10;22;37;40": {price:411,count:43},"10;22;38;40": {price:451,count:42},"10;21;31;40": {price:366,count:79},"10;21;32;40": {price:406,count:79},"10;21;33;40": {price:416,count:10},"10;21;34;40": {price:456,count:10},"10;21;35;40": {price:371,count:87},"10;21;36;40": {price:411,count:10},"10;21;37;40": {price:421,count:10},"10;21;38;40": {price:461,count:80},"10;21;30;40": {price:356,count:43},"10;20;31;40": {price:356,count:46},"10;20;32;40": {price:396,count:49},"10;20;33;40": {price:406,count:65},"10;20;34;40": {price:446,count:10},"10;20;35;40": {price:361,count:34},"10;20;36;40": {price:401,count:41},"10;20;37;40": {price:411,count:36},"10;20;38;40": {price:451,count:42},"10;20;30;40": {price:346,count: 3}
}
//保存最后的组合结果信息
var SKUResult = {};
//获得对象的key
function getObjKeys(obj) {if (obj !== Object(obj)) throw new TypeError('Invalid object');var keys = [];for (var key in obj)if (Object.prototype.hasOwnProperty.call(obj, key))keys[keys.length] = key;return keys;
}//把组合的key放入结果集SKUResult
function add2SKUResult(combArrItem, sku) {var 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]};}
}//初始化得到结果集
function initSKU() {var i, j, skuKeys = getObjKeys(data);for(i = 0; i < skuKeys.length; i++) {var skuKey = skuKeys[i];//一条SKU信息keyvar sku = data[skuKey];	//一条SKU信息valuevar skuKeyAttrs = skuKey.split(";"); //SKU信息key属性值数组skuKeyAttrs.sort(function(value1, value2) {return parseInt(value1) - parseInt(value2);});//对每个SKU信息key属性值进行拆分组合var combArr = combInArray(skuKeyAttrs);for(j = 0; j < combArr.length; j++) {add2SKUResult(combArr[j], sku);}//结果集接放入SKUResultSKUResult[skuKeyAttrs.join(";")] = {count:sku.count,prices:[sku.price]}}
}/*** 从数组中生成指定长度的组合*/
function arrayCombine(targetArr) {if(!targetArr || !targetArr.length) {return [];}var len = targetArr.length;var resultArrs = [];// 所有组合for(var n = 1; n < len; n++) {var flagArrs = getFlagArrs(len, n);while(flagArrs.length) {var flagArr = flagArrs.shift();var combArr = [];for(var i = 0; i < len; i++) {flagArr[i] && combArr.push(targetArr[i]);}resultArrs.push(combArr);}}return resultArrs;
}/*** 获得从m中取n的所有组合*/
function getFlagArrs(m, n) {if(!n || n < 1) {return [];}var resultArrs = [],flagArr = [],isEnd = false,i, j, leftCnt;for (i = 0; i < m; i++) {flagArr[i] = i < n ? 1 : 0;}resultArrs.push(flagArr.concat());while (!isEnd) {leftCnt = 0;for (i = 0; i < m - 1; i++) {if (flagArr[i] == 1 && flagArr[i+1] == 0) {for(j = 0; j < i; j++) {flagArr[j] = j < leftCnt ? 1 : 0;}flagArr[i] = 0;flagArr[i+1] = 1;var aTmp = flagArr.concat();resultArrs.push(aTmp);if(aTmp.slice(-n).join("").indexOf('0') == -1) {isEnd = true;}break;}flagArr[i] == 1 && leftCnt++;}}return resultArrs;
} //初始化用户选择事件
$(function() {initSKU();var endTime = new Date().getTime();$('#init_time').text('init sku time: ' + (endTime - startTime) + " ms");$('.sku').each(function() {var self = $(this);var attr_id = self.attr('attr_id');if(!SKUResult[attr_id]) {self.attr('disabled', 'disabled');}}).click(function() {var self = $(this);//选中自己,兄弟节点取消选中self.toggleClass('bh-sku-selected').siblings().removeClass('bh-sku-selected');//已经选择的节点var selectedObjs = $('.bh-sku-selected');if(selectedObjs.length) {//获得组合key价格var selectedIds = [];selectedObjs.each(function() {selectedIds.push($(this).attr('attr_id'));});selectedIds.sort(function(value1, value2) {return parseInt(value1) - parseInt(value2);});var len = selectedIds.length;var prices = SKUResult[selectedIds.join(';')].prices;var maxPrice = Math.max.apply(Math, prices);var minPrice = Math.min.apply(Math, prices);$('#price').text(maxPrice > minPrice ? minPrice + "-" + maxPrice : maxPrice);//用已选中的节点验证待测试节点 underTestObjs$(".sku").not(selectedObjs).not(self).each(function() {var siblingsSelectedObj = $(this).siblings('.bh-sku-selected');var testAttrIds = [];//从选中节点中去掉选中的兄弟节点if(siblingsSelectedObj.length) {var siblingsSelectedObjId = siblingsSelectedObj.attr('attr_id');for(var i = 0; i < len; i++) {(selectedIds[i] != siblingsSelectedObjId) && testAttrIds.push(selectedIds[i]);}} else {testAttrIds = selectedIds.concat();}testAttrIds = testAttrIds.concat($(this).attr('attr_id'));testAttrIds.sort(function(value1, value2) {return parseInt(value1) - parseInt(value2);});if(!SKUResult[testAttrIds.join(';')]) {$(this).attr('disabled', 'disabled').removeClass('bh-sku-selected');} else {$(this).removeAttr('disabled');}});} else {//设置默认价格$('#price').text('--');//设置属性状态$('.sku').each(function() {SKUResult[$(this).attr('attr_id')] ? $(this).removeAttr('disabled') : $(this).attr('disabled', 'disabled').removeClass('bh-sku-selected');})}});
});

收获

JavaScript中的对象属性访问是最快的了

更多相关:

  • 如何使用Python快速高效地统计出大文件的总行数, 下面是一些实现方法和性能的比较。1.readline读所有行使用readlines方法读取所有行:def readline_count(file_name):return len(open(file_name).readlines())2.依次读取每行依次读取文件每行内容进行计数:...

  • 关于逆序数的问题描述如下: 已知数组nums,求新数组count,count[i]代表了在nums[i]右侧且比 nums[i]小的元素个数。 例如: nums = [5, 2, 6, 1], count = [2, 1, 1, 0]; nums = [6, 6, 6, 1, 1, 1], count = [3, 3, 3, 0,...

  • 题目 设计一个算法,计算出n阶乘中尾部零的个数 样例 11! = 39916800,因此应该返回 2   题解 一开始就用最简单对1-n找出5的个数,然后超时了。虽然都直到是要找5,因为2肯定比5多,所以5的个数就是0的个数,只是计算方法得简单明了。既然1-n里5的个数就是0,我们就看看规律。5 10 15 。。。n 那n/...

  • EditText 限定中文8个英文16个的解决方法。 在EditText上控件提供的属性中有限定最大最小长度的方法。可是,对于输入时,限定中文8个英文16个时,怎么办?相当于一个中文的长度是两个英文的长度。 原理就不说了。自己看一下android的源代码。 以上直接上代码。 private final int maxLen =...

  • /**172. Factorial Trailing Zeroes *2016-6-4 by Mingyang* 首先别忘了什么是factorial,就是阶乘。那么很容易想到需要统计* (2,5)对的个数,因为2×5=10。但是这个条件放松一下就会发现其实只要数5的个数就好了,* 因为2实在是比5要多的多。那么这道题目就转...

  • #猜价钱 trueprice = 202 price = input("Please guess the price:") while (int(price) != trueprice):if(int(price) > trueprice):price = input("Your price is higher,Please try...

  • 第一篇博客 本文来自 自己老师 的博客 http://blog.csdn.net/lovelion/article/details/7818983 题目:某软件公司为某电影院开发了一套影院售票系统,在该系统中需要为不同类型的用户提供不同的电影票打折方式,具体打折方案如下:       (1) 学生凭学生证可享受票价8折优惠;     ...

  • ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)➤GitHub地址:https://github.com/strengthen/LeetCode➤原文地...

  • //获取某一个cookie的值 const getCookie = key => {var k = key, dc = document.cookie;if (dc.length > 0) {var s = dc.indexOf(k + "=");if (s != -1) {s = s + k.length + 1;var e = d...

  • var SGheadMapPoints = {/*obj={ maxLng: minLng: maxLat: minLat: maxCount:最大人数 minCount:最小人数 total:点位数量 }*/get: function (obj) {var arr = [];obj.maxCount || (obj.maxCount...

  • //自动搜索指定的请柬 var alertTipText = "请柬找到了,就在这个网页里面,自己仔细看吧"; var delay = 1 * 1000;//1秒后循环下一页寻找 /*获取子DOM元素在父元素里面的索引位置(是第几个元素)*/ function getNodeListIndex(childNode) {return c...

  •  获取天气情况(不支持跨域) /*json原生获取*/ function getJSON() {var XML;var url = "http://wthrcdn.etouch.cn/weather_mini?city=杭州";if (window.XMLHttpRequest) {XML = new XMLHttpRequest(...