记得之前看到过一个JS超大正整数相加的题目,今天想了下自己会怎么实现,于是便进行了尝试,代码如下:
function bigAdd(a, b){
let lenA = a.length,
lenB = b.length,
curry = 0,
res = [];
let loopLen = Math.max(lenA, lenB );
for(let i = 0 ; i < loopLen ; i++){
const indexA = ( lenA - i - 1 ) > -1 ? lenA - i -1 : -1;
const indexB = ( lenB - i - 1) > -1 ? lenB - i -1 : -1;
const valA = indexA > -1 ? a[ indexA ] : 0 ;
const valB = indexB > -1 ? b[ indexB ] : 0 ;
res[i] = ( Number(valA) + Number(valB) + curry) % 10;
curry = Math.floor( ( Number(valA) + Number(valB) + curry ) / 10 );
}
return res.reverse().join('');
}
想法很简单,就是按照加法的规律,从末尾开始一项一项的相加。
然后看了网上关于该题目的算法,真是惊叹别人算法的简洁以及对于JS中类型转换相关知识的巧妙应用。
function addBig(a, b){
let temp = 0,
res = '';
a = a.split('');
b = b.split('');
while( a.lenght || b.length || temp){
temp += ~~a.pop() + ~~b.pop();
res = (temp % 10 ) + res;
temp = temp > 9;
}
return res.replace(/^0+/g, '');
}
首先,这个代码给人的感觉真的很精简。但是其中其实隐藏了很多关于JS 中类型转换的相关知识, 下面我们来一一分析。
1.为什么将a,b转换为数组
转换为数组的目的是为了在while
循环体中使用 pop
方法来获取数组中的最后一项。如果你不转换为数组,我们能想到的获取字符串的最后一项的方法是sutstr(-1,1)
。但是这种方法只能获取最后一项,无法动态的改变字符串本身。
2. 为什么while
的判断条件是 a.lenght || b.length || temp
而不是a || b || temp
因为循环体中每次的循环会使数组长度减少1,当数组为空时,假设极端的条件下 a, b
都为[]
,但是这个时候 [] || [] || 0
的结果是 true
, 循环永不结束。只因为Boolean([]) = true
。
3. 为什么使用~~a.pop()
,如果只是 为了将值转换为数字 ,为何不使用Number
进行强制转换不是更加易懂?
这个就十分精髓了,首先使用~
操作符进行取反是会将字符串转换为数字的,在进行一个取反操作,就转换为了字符串本身代表的数字,但不仅如此。考虑下我们数组循环的最后为空时,[].pop()
的结果为undefined
, 而 Number(undefined)
的结果为NaN
,~NaN
的结果为-1
, ~~NaN
的结果 为0,当你使用NaN
进行加法运算时,一切结果都没有了意义。所以这个地方必须为~~a.pop()
, 而不能使用Number
进行强制转换。
4. 为什么temp = temp > 9
,这是什么鬼?
这一点其实也是用了JS中的隐式转换,我们换个写法你就明白了temp = temp > 9 ? 1 : 0
,temp
实际上表示的是进位的结果,在循环的开始我们使用temp += ~~a.pop() + ~~b.pop();
进行temp
的再赋值,实际等价于temp = (true | false) + Number
。 而Boolean
在进行数字运算时,true
会转换为1,false
会转换为0,此处巧妙的应用了该隐式转换。
一个小小的算法,其中隐含了这么多的基础知识,所以还是需要夯实基础啊!!!