这是我参与「第四届青训营 」笔记创作活动的的第4天
今天讲了好多例子,继续学习中……
写代码最应该关注什么?
- 风格?
- 效率?
- 约定?
- 使用场景?
- 设计?
当年的leftpad事件
function leftpad(str, len, ch) {
str = String(str);
var i = -1;
if (!ch && ch !== 0) ch = ' ';
len = len - str.length;
while (++i < len) {
str = ch + str;
}
return str;
}
改进:
- 代码更简洁
- 效率提升
function leftpad(str, len, ch) {
str = "" + str;
const padLen = len - str.length;
if(padLen <= 0) {
return str;
}
return (""+ch).repeat(padLen)+str;
}
- 性能更好,时间复杂度O(logn)
module.exports = function repeat(str,count) {
var n = count;
// Account for out-of-bounds indices
if (n < 0 || n == Infinity) {
throw RangeError('String.prototype.repeat argument must be greater than or equal to 0 and not be Infinity');
}
var result = '';
while (n) {
if (n % 2 == 1) {
result += string;
}
if (n > 1) {
string += string;
}
n >>= 1;
}
return result;
};
//5->101 str -> *
//左移一位:n->10 str -> ** result->*
//n->1 str->**** result->*****
- 性能更好
if (!String.prototype.repeat) {
String.prototype.repeat = function(count) {
'use strict';
if (this == null)
throw new TypeError('can't convert ' + this + ' to object');
var str = '' + this;
// To convert string to integer.
count = +count;
// Check NaN
if (count != count)
count = 0;
if (count < 0)
throw new RangeError('repeat count must be non-negative');
if (count == Infinity)
throw new RangeError('repeat count must be less than infinity');
count = Math.floor(count);
if (str.length == 0 || count == 0)
return '';
// Ensuring count is a 31-bit integer allows us to heavily optimize the
// main part. But anyway, most current (August 2014) browsers can't handle
// strings 1 << 28 chars or longer, so:
if (str.length * count >= 1 << 28)
throw new RangeError('repeat count must not overflow maximum string size');
var maxCount = str.length * count;
count = Math.floor(Math.log(count) / Math.log(2));
while (count) {
str += str;
count--;
}
str += str.substring(0, maxCount - str.length);
return str;
}
}
交通灯:实现一个切换多个交通灯状态切换的功能
版本一
<ul id="traffic" class="wait">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
const traffic = document.getElementById('traffic');
(function reset(){
traffic.className = 's1';
setTimeout(function(){
traffic.className = 's2';
setTimeout(function(){
traffic.className = 's3';
setTimeout(function(){
traffic.className = 's4';
setTimeout(function(){
traffic.className = 's5';
setTimeout(reset, 1000)
}, 1000)
}, 1000)
}, 1000)
}, 1000);
})();
版本二:数据抽象
<ul id="traffic" class="wait">
<li></li>
<li></li>
<li></li>
</ul>
const traffic = document.getElementById('traffic');
//将数据抽象出来,定义状态列表
const stateList = [
{state: 'wait', last: 1000},
{state: 'stop', last: 3000},
{state: 'pass', last: 3000},
];
//定义方法start,传入id和状态列表,使用递归方式实现切换
function start(traffic, stateList){
function applyState(stateIdx) {
const {state, last} = stateList[stateIdx];
traffic.className = state;
setTimeout(() => {
applyState((stateIdx + 1) % stateList.length);
}, last)
}
applyState(0);
}
start(traffic, stateList);
版本三:过程抽象
const traffic = document.getElementById('traffic');
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function poll(...fnList){
let stateIndex = 0;
return async function(...args){
let fn = fnList[stateIndex++ % fnList.length];
return await fn.apply(this, args);
}
}
async function setState(state, ms){
traffic.className = state;
await wait(ms);
}
let trafficStatePoll = poll(setState.bind(null, 'wait', 1000),
setState.bind(null, 'stop', 3000),
setState.bind(null, 'pass', 3000));
(async function() {
// noprotect
while(1) {
await trafficStatePoll();
}
}());
版本四:异步+函数式
const traffic = document.getElementById('traffic');
function wait(time){
return new Promise(resolve => setTimeout(resolve, time));
}
function setState(state){
traffic.className = state;
}
async function start(){
//noprotect
while(1){
setState('wait');
await wait(1000);
setState('stop');
await wait(3000);
setState('pass');
await wait(3000);
}
}
判断是否是4的幂
<input id="num" value="65536"></input>
<button id="checkBtn">判断</check>
#num {
color: black;
}
#num.yes {
color: green;
}
#num.no {
color: red;
}
num.addEventListener('input', function(){
num.className = '';
});
checkBtn.addEventListener('click', function(){
let value = num.value;
num.className = isPowerOfFour(value) ? 'yes' : 'no';
});
1.最简单的方法,但性能不够高
function isPowerOfFour(num) {
num = parseInt(num);
while(num > 1) {
if(num % 4) return false;
num /= 4;
}
return num === 1;
}
2.位操作
function isPowerOfFour(num) {
num = parseInt(num);
while(num > 1) {
if(num & 0b11) return false;//最后两位不为零时一定不是4的幂
//右移两位,继续判断
num >>>=2;
}
return num === 1;
}
3.直接转化成二进制字符串,用正则表达式去匹配,1后跟着若干个00
function isPowerOfFour(num) {
num = parseInt(num).toString(2);
return /^1(?:00)*$/.test(num);
}
4.O(1)复杂度的循环
//a&(a-1)得到1的个数少一
//x......10 & x......01
//x......1(k个0) & x......0(k个1) -> x......(k+1个0)
//一个数若为2的幂,则二进制只有一个1
//一个数若为2的幂,则二进制数零的个数为偶数,1(n个00),如100,10000,偶数位不为1(从右往左)
//num & 0101010......10 === 0,即num & 0xAAAAAAAAAAAAA
function isPowerOfFour(num){
num = parseInt(num);
return num > 0 &&
(num & (num - 1)) === 0 &&
(num & 0xAAAAAAAAAAAAA) === 0;
}
洗牌
错误写法
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);//-1和1分别表示交换和不交换位置
}
console.log(shuffle(cards));
正确写法
随机抽出每一张牌并放到最后
const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
//[a1,a2...,ak],抽到[a1,...,a(k-1)]的概率为k分之1,抽到[ak]概率(k-1)/k
//k张牌每张牌出现在任意位置的概率为:P=(k-1)/k * 1/(k-1) = 1/k
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)
分红包
切西瓜法 O(m*n)
每次分最大的那一部分
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;
}
const amountEl = document.getElementById('amount');
const countEl = document.getElementById('count');
const generateBtn = document.getElementById('generateBtn');
const resultEl = document.getElementById('result');
generateBtn.onclick = function(){
let amount = Math.round(parseFloat(amountEl.value) * 100);
let count = parseInt(countEl.value);
let output = [];
if(isNaN(amount) || isNaN(count)
|| amount <= 0 || count <= 0){
output.push('输入格式不正确!');
}else if(amount < count){
output.push('钱不够分')
}else{
output.push(...generate(amount, count));
output = output.map(m => (m / 100).toFixed(2));
}
resultEl.innerHTML = '<li>' +
output.join('</li><li>') +
'</li>';
}
抽牌法O(n)
生成总金额的随机数列,生成随机数列作为分隔,缺点是空间复杂度较高
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 = [0];
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;
}
const amountEl = document.getElementById('amount');
const countEl = document.getElementById('count');
const generateBtn = document.getElementById('generateBtn');
const resultEl = document.getElementById('result');
generateBtn.onclick = function(){
let amount = Math.round(parseFloat(amountEl.value) * 100);
let count = parseInt(countEl.value);
let output = [];
if(isNaN(amount) || isNaN(count)
|| amount <= 0 || count <= 0){
output.push('输入格式不正确!');
}else if(amount < count){
output.push('钱不够分')
}else{
output.push(...generate(amount, count));
output = output.map(m => (m / 100).toFixed(2));
}
resultEl.innerHTML = '<li>' +
output.join('</li><li>') +
'</li>';
}
总结
写好js代码:数学、算法思维解决问题