「前端每日一问(53)」如何实现字符串的 padStart 函数

1,093 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情

本题难度:

时间复杂度为 O(n):⭐ ⭐

时间复杂度为 O(logn):⭐ ⭐ ⭐

本题类型:算法、手写

padStart 函数其实平时用得不是很多,但是以前 JS 没有这个 API 的时候,很多人是引的一个叫 left-pad 的包,但是这个包发生过一些事件,感兴趣的朋友们可以看一下下面的链接。

npm left-pad包事件

NPM 与 left-pad 事件:我们是不是早已忘记该如何好好地编程?

其实很多时候,一些简单的功能我们都可以自己封装一个函数实现,根本没必要引三方包,尽可能地避免上面的事件发生。

padStart 用法

padStart()  方法用另一个字符串填充当前字符串(如果需要的话,会重复多次),以便产生的字符串达到给定的长度。从当前字符串的左侧开始填充。

const str = 'hello'
console.log(str.padStart(10, '.')) // '.....hello'

实现

方式1

一层循环搞定。

function padStart(str, length, char) {
  let res = ''
  let len = length - str.length
  for (let i = 0; i < len; i++) {
    res += char
  }
  return res + str
}

方式2

转数组再 join。

function padStart(str, length, char) {
  return Array(length - str.length + 1).join(char) + str
}

方式3

方式1和方式2时间复杂度都是 O(n),其实可以用二分法来进一步优化效率,把时间复杂度优化到 O(logn)。

/**
 * 二分的思想来拼接字符串
 * 
 * 假设需要拼接的长度为 len ,要拼接的字符 char 是 'x'
 * 
 * 对 len 二分,每次操作 len = parseInt(len / 2) 
 * 每次操作 char 翻倍,char += char,就不用一个一个拼接了
 * 
 * 'x'
 * 'xx'
 * 'xxxx'
 * 'xxxxxxxx' 
 * 
 * 拼接字符串的时间复杂度从 O(n) 降到 O(lgn)
 * 
 * 当 len = 1 时,说明已经是最小单位了,就可以返回了
 * 假设 len = 7,最终要返回的结果为 total
 * 
 * 遇到奇数的情况,也就是 len % 2 === 1 时,total += char 
 * 
 * 初始化    total ''         char x      len 7
 * 第一次操作 total x          char xx     len parseInt(7 / 2)  = 3
 * 第二次操作 total xxx        char xxxx   len parseInt(3 / 2)  = 1
 * 第三次操作 total xxxxxxx 
 * 
 * 都是偶数的情况,假设 len = 4,就是下面这种
 * 
 * 第一次操作 total ''       char xx     len parseInt(4 / 2)  = 2
 * 第二次操作 total ''       char xxxx   len parseInt(2 / 2)  = 1
 * 第三次操作 total xxxx
 * 
 */
 function padStart3(str, length, char) {
  let len = Math.floor(length) - str.length
  if (len < 1) {
    return str
  }
  let total = ''
  while(true) {
    if (len % 2 === 1) {
      total += char
    }
    if (len === 1) {
      return total + str
    }
    len = parseInt(len / 2)
    char += char
  }
}

时间复杂度: O(lgn)

最后,可以继续优化,用位运算稍微改进一下

不懂位运算的可以参考我的这篇文章:为了看懂 Vue3 diff算法,我学习了JS 位运算

/**
 * 位运算改进
 * len % 2 === 1 也就是 len & 1
 * parseInt(len / 2) 也就是 len >> 1
 */
function padStart(str, length, char) {
  let len = Math.floor(length) - str.length
  if (len < 1) {
    return str
  }
  let total = ''
  while(true) {
    if (len & 1) {
      total += char
    }
    if (len === 1) {
      return total + str
    }
    len = len >> 1
    char += char
  }
}

测试一下性能:

console.log('循环 10000 次')
console.time('直接拼接')
for(let i = 0; i< 10000; i++) {
  padStart1(str, 10000, '.')
}
console.timeEnd('直接拼接')

console.time('二分法')
for(let i = 0; i< 10000; i++) {
  padStart2(str, 10000, '.')
}
console.timeEnd('二分法')

console.time('二分法 + 位运算')
for(let i = 0; i< 10000; i++) {
  padStart3(str, 10000, '.')
}
console.timeEnd('二分法 + 位运算')

image.png

可以看出,二分法真的极大的优化了性能。

普通查找(线性时间):O(n)

二分查找(对数时间):O(logn)

在包含 100 个数字的列表中查找,二分查找最多只需 7 次。

在包含 40 亿个数字的列表中查找,二分查找最多只需 32 次。

数据量越大,二分查找带来的性能提升越大。

结尾

阿林水平有限,文中如果有错误或表达不当的地方,非常欢迎在评论区指出,感谢~

如果我的文章对你有帮助,你的👍就是对我的最大支持^_^

你也可以关注《前端每日一问》这个专栏,防止失联哦~

我是阿林,输出洞见技术,再会!

上一篇:

「前端每日一问(52)」计算斐波那契数

下一篇:

「前端每日一问(54)」什么是Tree-Shaking?Vue3 是如何支持 Tree-Shaking 的?