前端有多少事情可以做,能做到多好。一直在關注各大公司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信息key var sku = data[skuKey]; //一條SKU信息value var 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); } //結果集接放入SKUResult SKUResult[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中的對象屬性訪問是最快的了