vue源码中,将一个字符串重复n次的算法解析

11 篇文章 0 订阅
6 篇文章 0 订阅

在浏览vue2.x的源码过程中,无意中在codeframe.js文件中看到一个repeat函数,其作用就是能够将一个字符串重复指定的次数并返回,以往看到类似的代码大多都是瞄一眼写法,然后就直接略过,然而这次我不打算轻易放过,尝试对其写法做出了逆向解释和扩展,因而有了此文。

原始代码如下:

function repeat (str, n) {
  let result = ''
  if (n > 0) {
    while (true) {
      if (n & 1) { 
        result += str
      }
      n >>>= 1
      if (n <= 0) break
      str += str
    }
  }
  return result
}

运行一下

console.log(repeat('abc',3)) // abcabcabc

这个函数的作用很简单,而且能够用更容易理解的方式写出来:

function repeat1 (str, n) {
  let result = ''
  if (n > 0) {
    for(let i=0;i<n;i++){
       result += str
    } 
  return result
}

那么,作者为什么选择那么写呢?因为那样更快,时间复杂度仅为O(logn),而现在的这种写法为O(n)。在n大到一定程度时,它们会有明显的执行时间差。

下面,我开始尝试解释源码的编码逻辑,是否存在某种理论支撑,而不是经验的归纳。

首先,我们的目的是求解一个字符串重复n的结果,例如:'a',重复11次,得到'aaaaaaaaaaa'(11个a);

然后,转换一下问题描述,那就是:如何得到11个a?

  1. 'a'+'a'+...+'a',11个a相加得到,这就是上面repeat1的实现逻辑,遍历n累加'a'
  2. 'aaaaa'+'aaaaa'+'a' =('aa'+'aa'+'a')+('aa'+'aa'+'a')+a=(('a'+'a')+('a'+'a')+'a')+(('a'+'a')+('a'+'a')+'a')+'a',分而治之
  3. 'a'+'aa'+'aaaaaaaa',这是源码的实现逻辑

通过阅读源码,我们可以得出两条关键的逻辑:

 1、用n对2取余(余数不是1就是0),为1时就把此次的str加到结果中

if (n & 1) {  // 相当于对2取余    
   result += str 
}

2、n除2取其商作为新的n,判断n是否大于0,大于0则str字符串自我重复一次

  n >>>= 1 // 对2取商
  if (n <= 0) break
  str += str // 自加

通过上面的分析,我联想到了二进制数转十进制的方法

比如:11的二进制表示是1011,二进制再转换成十进制则为: 

1*(2^0) + 1*(2^1) + 0*(2^2) + 1*(2^3)

那么,回到我们最初的问题:如何从一个'a'变成n个'a'。

此时,我们对于这个问题的分析就可以等价于分析如何从一个1变成“n个1”。因为我们可以把'a'+'a'的操作,看成是1+1的操作。例如,我们可以把代码修改一下:

function repeatNum (n) {
  let result = 0
  let num = 1 
  if (n > 0) {
    while (true) {
      if (n & 1) { 
        result += num
      }
      n >>>= 1
      if (n <= 0) break
      num += num
    }
  }
  return result
}

运行一下:

console.log(repeatNum(3)) //3
console.log(repeatNum(11)) //11

我们知道,任意一个正整数都可以用2的幂的多项式表示,也就是二进制数表示十进制数的方法,如前文所述:

11 = 1*(2^0) + 1*(2^1) + 0*(2^2) + 1*(2^3) ,其中括号前的常数就是11转成二进制1011,从低到高位的二进制数,所以代码中才有n & 1为1时才会做求和操作(n & 1相当于n对2取余,而这个余数正好是二进制数)

进一步分析:

11 = 1 +   // 首次进入循环n=11,result += 1

        (1 + 1) + // n=5, num += num,num = 2,result+=2

        0*((1+1)+(1+1)) + // n=2,num+=num,num = 4

        ((1+1+1+1)+(1+1+1+1)) // n=1,num+=num,num=8,result+=8

同理,字符串'a'重复n次也遵循相同的算法逻辑,而代码的时间复杂度也取决于n能够被2除几次,所以,时间复杂度为O(logn)。

总结:此次分析感觉并不流畅,不能算是特别有效的推演,但应该也能帮助到一些人对该源码的进一步理解,同时如果有更好的推演,也希望各位大佬不吝赐教^_^。


此外,针对源码的功能,附上一份运用分治思想的代码:

// n为奇数时,可表示为两相同偶数加基数(这里相当于str);n为偶数时,可拆分为两个相同奇数相加,继续拆分直到不能拆分为止
function myRepeat(str,n){
  if(!n || n <= 0){
    return ''
  }

  if(n === 1){
    return str
  }

  let temp = myRepeat(str,n/2)
  if(n & 1){
    return str+temp+temp
  }else{
    return temp + temp
  }

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wl_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值