先简单聊聊
一提到算法,一些小伙伴们听着“打脑壳”,一些小伙伴会觉得天天搞什么排序,二叉树迭代...有啥意思,工作中用不到。哈哈,“老子”说的好,不是用不到,而是你还没到那个level。今天主要是聊聊学习简单的算法在实际工作中的利用,没有什么太高深的东西(白嫖怪请走开),只是跟大家分享我当初的思考过程,从最low的代码一步一步思考,最后到基本还看的过去代码。文章较长!小妹儿,先上鸳鸯锅吃起!
业务场景
添加商品 这个简常见务场景我相信很多小伙伴们都遇到过,大家都知道(SPU)加上一些属性才组成(SKU),比如最常见的“门”(彩蛋--其实我是卖门的小二,会有些小伙伴没有这个生活经验,哈哈!正好给大家普及一下,毕竟将来都是要买房子装修的人,如果小伙伴正好遇到选门这方面的问题留言咨询哈!),门是SPU,门包含了颜色、材质、开口方向等一堆属性,直接上代码!
let directions = ["左锁内开", "左锁外开","右边内开","右锁外开"] //后面给大家配图
let colors = ["象牙白", "胡桃木","苹果木"]
let materials = ["原木", "实木","钛合金"]
那么一个门SKU就应该是["左锁内开","象牙白","实木"]、["右边内开","胡桃木","实木"]等等...大白话就是需要把上面的属性挨个组合起来。下面正式开始代码(每个后台返回的数据解构不一样,实际代码会有差异)
1. 嵌套循环
这种方式是最容易想到了,就是第一个属性数组迭代,里面再放入第二个属性数组,再放第三个...
let skus = []
for (let i = 0; i < directions.length; i++) {
for (let j = 0; j < colors.length; j++) {
for (var k = 0; k < materials.length; k++) {
let sku = [];
sku.push(directions[i],colors[j],materials[k]);
skus.push(sku)
}
}
}
我的天啦!O(N^3)的复杂度。哈哈,机智的你是不是早就看出了!难道我的属性只有3个?不可能啊,比如你选了钛合金门,还有70料,83料,85料,90料等等,不同的料价格不同也就是不同的SKU啊。所以这个操作肯定是不行的。
2. 两两组合
上面的操作当额外增加属性的时候就不行了,那么我们先解决这个问题。我们采用属性两两组合,也就是先让directions和colors组合成二维数组返回,再和materials组合。上代码!
let props = [directions, colors, materials,returns],skus = props[0];
for (var k = 1; k < props.length; k++) {
skus = matching(skus, props[k]);
}
function matching(arr, arr1) {
let sku = []
if (!!arr.length && !!arr1.length) {
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr1.length; j++) {
sku.push(`${arr[i]}-${arr1[j]}`)
}
}
return sku
}
}
结果如下:
再遍历一次分开就搞定。这种操作从时间复杂度可以看成O(N^2),似乎解决了未知属性集合数量的拼装,但是始终不够优雅。我们继续往下思考!
2. 排列组合(递归回溯)
本来我打算自己贴代码,但是前几天看到一篇博文写的很好,这里大家可以看一下 @晨曦时梦见兮 老师的一篇博文,他写的已经非常仔细了。https://juejin.im/post/6844904191379374087。 如果晨曦的代码看着晕可以看下面每一行加注释的代码。
这里我把思路再整理一下:最终我们需要拼装成二维数组[["左锁内开","象牙白","实木"]、["右边内开","胡桃木","实木"]...],那么我们的递归体就是去完成二维数组里面的每一项的拼装。 为了方便大家读我把每一行代码加了注释。大家在看代码的时候,不要硬看,要学会使用debug,同时也要有良好的js基础,高阶函数、闭包、递归等等。
function createGoods(...goodsProps){
// goodsProps 为 已经选择好的属性数组
let results = [] //输出的二维数组
function helper(goodsPropsIndex,prevArr){ //定义递归处理函数
//拿到当前迭代的哪一个属性数组,默认传递的0,拿第一个属性数组
let props = goodsProps[goodsPropsIndex]
//获取是不是最后一组属性,递归的结束条件。
let isLast = goodsPropsIndex == goodsProps.length - 1
for(let prop of props){ //遍历其中某一个属性数组
//把当前迭代的属性数组中取一个与上次传入的合并。
let cur = prevArr.concat(prop)
//判断是不是最后一个迭代的属性数组
if (isLast) {
//如果是,就把合并好的一次放入输出数组中
results.push(cur)
}else{
//如果不是,就继续递归下一个属性数组
helper(goodsPropsIndex+1,cur)
}
}
}
helper(0,[]) //参数一为属性数组的索引,[]为拼装当前这次合并数组
return results
}
总结
大家都是从小白慢慢的学习到能独挡一面的,只有非常厉害的人才能一下想到 递归回溯,借用 晨曦老师的话:我们可以先记住一些固定的模式,再从实际工作中去慢慢领会,同时夯实JS基础,多去尝试,萌新终究会变成别人眼中的大佬!再次感谢 @晨曦时梦见兮 老师的博文,帮我省了很多篇幅。
附录
现在主流的家装门是实木贴面,也就是使用实木条加上门皮拼装的,实木条的多少决定了门的质量和强度,千万不要相信销售给你看的小样,在挑选的要从门的中间网上一掌的位置,从左到右敲,如果声音基本一致,那就比较厚道的。同时西南地区市场价包安装大概在1400左右,太低的标注为实木门的就不要买了。