淘宝组合查询算法

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

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

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

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

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

示例如下

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

主要JS代码实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
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属性值数组
         var  len = skuKeyAttrs.length;
 
 
         //对每个SKU信息key属性值进行拆分组合
         var  combArr = arrayCombine(skuKeyAttrs);
         for (j = 0; j < combArr.length; j++) {
             add2SKUResult(combArr[j], sku);
         }
 
         //结果集接放入SKUResult
         SKUResult[skuKey] = {
             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中的对象属性访问是最快的了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值