跟着月影学JavaScript (下)| 青训营笔记

89 阅读3分钟

这是我参与「第五届青训营 」笔记创作活动的第5天

重点内容

  • 代码写作关注事项
  • left-pad 事件背后的代码规范
  • 代码实践1 - 交通灯
  • 代码实践2 - 洗牌
  • 代码实践3 - 分红包

代码写作关注事项

风格效率约定使用场景设计

当年的Left-pad事件

  • 需要关注的问题
    • 模块粒度
    • 代码风格
    • 代码质量/效率

代码实践

交通灯

实现一个切换多个交通灯状态切换的功能

版本一

  • 每隔一秒钟进行切换

版本二(数据抽象)

  • 将交通灯的状态进行抽象,每个状态持续的时间不同

版本三(过程抽象)

  • 定义 wait 方法,表示等待
  • 定义 poll 方法,表示轮询

版本四(异步+函数式)

  • 定义 wait 方法,表示等待
  • 定义 setState方法,设置className
  • 通过 while 循环不断重复轮询过程

判断是否是4的幂

方法一

  • 通过 while 循环对4取模
function isPowerOfFour(num) {
   num = parseInt(num);
   while(num > 1) {
     if(num % 4) return false;
     num /= 4;
   }
   return num === 1;
}

方法二

  • 通过 while 循环进行位操作
  • 位操作:通过按位与 0b11,判断二进制数最后两位是否为0
    • 若是0:右移两位
    • 不是0:说明该数不是4的幂
function isPowerOfFour(num) {
   num = parseInt(num);
   while(num > 1) {
     if(num & 0b11) return false;
     num >>>=2;
   }
   return num === 1;
}

方法三

  • 通过三个条件进行判断:
    • 数值是否大于0
    • 二进制数num 与 num-1 相与,结果少一个1
      • 依据:2/4的幂转为二进制数后只有一个1
    • num 与 十六进制数 0xAAAAAAAAAAAAA 相与,判断偶数位是否为1
      • 依据:4的幂转为二进制数后0的个数为偶数
function isPowerOfFour(num){
  num = parseInt(num);
  
  return num > 0 &&
         (num & (num - 1)) === 0 &&
         (num & 0xAAAAAAAAAAAAA) === 0;
}

方法四

  • 通过正则表达式判断
function isPowerOfFour(num) {
  num = parseInt(num).toString(2);
  return /^1(?:00)*$/.test(num);
}

洗牌

错误方法

  • 使用 sort 方法排序导致洗牌次数不均匀
const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

function shuffle(cards) {
  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];
  }
}

console.table(result);

正确方法

  • 遍历数组
  • 随机取出一位数并放到数组末尾
  • 剩下的数循环上述步骤
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);

改进:生成器

  • 不做完循环,直接使用yield方法
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]);
  • 完整代码

分红包

切西瓜法

  • 将红包总额随机分成两份
  • 对数值较大的一份重复第一步
  • 直到红包数量足够
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;
}

抽牌法

  • 随机抽出 n-1 个数,作为数组分隔符
  • 例:抽出49和199,4.9为一份,19.9-4.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];
  }
}

// 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 = [];
  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;
}