JS代码质量优化
-
交通灯切换
- 每个setTimeout都是异步的,一直嵌套
- 将数据抽象出来
-
将过程抽象出来
写的代码很多,但是易扩展,更具灵活性。
-
设置状态,等待若干时间执行下一个状态
-
判断一个整数是否是4的幂 源码:code.juejin.cn/pen/7108197…
-
最简单的方法:(性能不好)
function isPowerOfFour(num) { num = parseInt(num); while(num > 1) { if(num % 4) return false; num /= 4; } return num === 1; } -
按位与,看一下最后两位是否是0,如果不是0就说明不是4的幂,如果是0的话就右移。
function isPowerOfFour(num) { num = parseInt(num); while(num > 1) { if(num & 0b11) return false; num >>>=2; } return num === 1; } -
O(1)时间复杂度的一个算法
两个相邻的数进行 按位与操作的时候,会只保留大的数中最后一个1前面的数
111110000
111101111
上面两个数进行按位与操作,会只保留111100000
如果进行与操作之后是0,说明肯定是2的倍数,意思就是所有的数都没留下,大的数中最后一个1前面没数。
100000
011111
进行按位与操作,结果是0,说明原数是只有1个1,其余都是0,现在只能保证都是2的倍数,下面来保证是4的倍数。
如果1在偶数位上,那么就都是2的奇次幂。如果1在奇数位上,那么就都是4的偶次幂。
1 0 0 0 0 2的4次幂 也就是4的2次幂
所以我们需要找一个中间量,来判断1是否位于奇数位
那么就只需要和偶数位全为1的数来进行按位与操作,如果结果是0说明1位于奇数位。
所以找一个数1010101010101010......1010偶数位全是0
那么找的这个数得是多少位呢。看一下JS有精度的整数是多少位,JS是64位的浮点数,有11位的小数位,所以精度的整数位为53位,需要13个A
function isPowerOfFour(num){ num = parseInt(num); return num > 0 && (num & (num - 1)) === 0 && (num & 0xAAAAAAAAAAAAA) === 0; } -
把num不当成数字,当成一个字符串,直接用正则表达式去匹配。
^和$分别表示字符串的开头和结尾。1表示以数字1开头。(?:00)*表示后面跟随若干对的0,即00可以重复 0 次或多次。(?: ... )是一个非捕获组,*表示重复 0 次或多次。以
1开头,后面跟随成对的0,且整个字符串从头到尾都是符合条件的。function isPowerOfFour(num) { num = parseInt(num).toString(2); return /^1(?:00)*$/.test(num); }
-
-
洗牌
-
错误写法:
const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; function shuffle(cards) { // -1 表示交换位置 1 表示不交换位置 // 这是一个分布不均匀的算法 return [...cards].sort(() => Math.random() > 0.5 ? -1 : 1); } console.log(shuffle(cards));const result = Array(10).fill(0); for(let i = 0; i < 1000000; i++) { const c = shuffle(cards); for(let j = 0; j < 10; j++) { result[j] += c[j]; // 把每次的十个数字放在result对应的十行中,然后加起来 } } console.table(result);
-
-
正确写法:
从十张牌里取一张牌,放到最后一张,再从剩下九张牌,放到倒数第二张....以此类推
第一次概率是 1/10 , 第二次抽到牌的概率是1/9,最后一次是1/1,相当于每个排列出现的概率都是1/10!
算法生成每一种可能的排列的概率都是 1/10!,从而保证了等概率洗牌。
const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; function shuffle(cards) { const c = [...cards]; for(let i = c.length; i > 0; i--) { const pIdx = Math.floor(Math.random() * i); [c[pIdx], c[i - 1]] = [c[i - 1], c[pIdx]]; } return c; } console.log(shuffle(cards));const result = Array(10).fill(0); for(let i = 0; i < 10000; i++) { const c = shuffle(cards); for(let j = 0; j < 10; j++) { result[j] += c[j]; } } console.table(result);
-
抽奖
-
生成器版本(在洗牌的基础上,只取出一次的结果,然后抽出几个数)
const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; function * draw(cards){ const c = [...cards]; for(let i = c.length; i > 0; i--) { const pIdx = Math.floor(Math.random() * i); [c[pIdx], c[i - 1]] = [c[i - 1], c[pIdx]]; yield c[i - 1]; } } const result = draw(cards); // console.log([...result]); console.log(result.next().value, result.next().value, result.next().value )
-
-
红包生成器
不能完全随机,因为还需要考虑,把钱分完的话,后面的红包还能不能继续分。发100元10个包,肯定不能第一个包就100元。
-
采用切西瓜法O(m*n)。code.juejin.cn/pen/7108203…
先把西瓜切成两半,然后再切大的,分成两半,这样就不会有不够分的情况了。
function generate(amount, count){ let ret = [amount]; while(count > 1){ //挑选出最大一块进行切分 let cake = Math.max(...ret), idx = ret.indexOf(cake), part = 1 + Math.floor((cake / 2) * Math.random()), rest = cake - part; ret.splice(idx, 1, part, rest); count--; } return ret; } -
抽牌法O(n):code.juejin.cn/pen/7108204…
想刺激一点,让大的足够大,小的足够小。
把10000份,看成是0-9999的数列,然后在数列里插入九个分隔符。
如果第一个分隔符在 50,把5块分给第一个人,如果在 200,把200-5 分给第二个人,以此类推。
function * draw(cards){ const c = [...cards]; for(let i = c.length; i > 0; i--) { const pIdx = Math.floor(Math.random() * i); [c[pIdx], c[i - 1]] = [c[i - 1], c[pIdx]]; yield c[i - 1]; } } // 0, 1, 2....9999 // 49 199 function generate(amount, count){ if(count <= 1) return [amount]; const cards = Array(amount - 1).fill(0).map((_, i) => i + 1); const pick = draw(cards); const result = []; // 只能抽count - 1 次 for(let i = 0; i < count - 1; i++) { result.push(pick.next().value); } result.sort((a, b) => a - b); result.push(amount); for(let i = result.length - 1; i > 0; i--) { result[i] = result[i] - result[i - 1]; } return result; }
-