大厂前端面试常问的数据处理,必会系列(三)

41 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第20天,点击查看活动详情

  • 将数字每千分位用逗号隔开
  • 实现非负大整数相加和相乘
  • 实现 add(1)(2)(3)
  • 实现类数组转化为数组
  • 使用 reduce 求和

1. 将数字每千分位用逗号隔开

数字有小数版本:

 let format = n => {
     let num = n.toString() // 转成字符串
     let decimals = ''
         // 判断是否有小数
     num.indexOf('.') > -1 ? decimals = num.split('.')[1] : decimals
     let len = num.length
     if (len <= 3) {
         return num
     } else {
         let temp = ''
         let remainder = len % 3
         decimals ? temp = '.' + decimals : temp
         if (remainder > 0) { // 不是3的整数倍
             return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + temp
         } else { // 是3的整数倍
             return num.slice(0, len).match(/\d{3}/g).join(',') + temp 
         }
     }
 }
 format(12323.33)  // '12,323.33'

数字无小数版本:

 let format = n => {
     let num = n.toString() 
     let len = num.length
     if (len <= 3) {
         return num
     } else {
         let remainder = len % 3
         if (remainder > 0) { // 不是3的整数倍
             return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') 
         } else { // 是3的整数倍
             return num.slice(0, len).match(/\d{3}/g).join(',') 
         }
     }
 }
 format(1232323)  // '1,232,323'

2. 实现非负大整数相加和相乘

JavaScript对数值有范围的限制,限制如下:

 Number.MAX_VALUE // 1.7976931348623157e+308
 Number.MAX_SAFE_INTEGER // 9007199254740991
 Number.MIN_VALUE // 5e-324
 Number.MIN_SAFE_INTEGER // -9007199254740991

(1)大数相加

如果想要对一个超大的整数(> Number.MAX_SAFE_INTEGER)进行加法运算,但是又想输出一般形式,那么使用 + 是无法达到的,一旦数字超过 Number.MAX_SAFE_INTEGER 数字会被立即转换为科学计数法,并且数字精度相比以前将会有误差。

实现一个算法进行大数的相加:

 function sumBigNumber(a, b) {
   let res = '';
   let temp = 0;
   
   a = a.split('');
   b = b.split('');
   
   while (a.length || b.length || temp) {
     temp += ~~a.pop() + ~~b.pop();
     res = (temp % 10) + res;
     temp  = temp > 9
   }
   return res.replace(/^0+/, '');
 }

其主要的思路如下:

  • 首先用字符串的方式来保存大数,这样数字在数学表示上就不会发生变化
  • 初始化res,temp来保存中间的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算
  • 将两个数组的对应的位进行相加,两个数相加的结果可能大于10,所以可能要仅为,对10进行取余操作,将结果保存在当前位
  • 判断当前位是否大于9,也就是是否会进位,若是则将temp赋值为true,因为在加法运算中,true会自动隐式转化为1,以便于下一次相加
  • 重复上述操作,直至计算结束

(2)大数相乘

 function multiplyBigNum(num1, num2) {
     //判断输入是不是数字
     if (isNaN(num1) || isNaN(num2)) return "";
     num1 = num1 + ""
     num2 = num2 + ""
     let len1 = num1.length,
         len2 = num2.length;
     let pos = [];
 ​
     //j放外面,先固定被乘数的一位,分别去乘乘数的每一位,更符合竖式演算法
     for (let j = len2 - 1; j >= 0; j--) {
         for (let i = len1 - 1; i >= 0; i--) {
             //两个个位数相乘,最多产生两位数,index1代表十位,index2代表个位
             let index1 = i + j,
                 index2 = i + j + 1;
             //两个个位数乘积加上当前位置个位已累积的数字,会产生进位,比如08 + 7 = 15,产生了进位1
             let mul = num1[i] * num2[j] + (pos[index2] || 0);
             //mul包含新计算的十位,加上原有的十位就是最新的十位
             pos[index1] = Math.floor(mul / 10) + (pos[index1] || 0);
             //mul的个位就是最新的个位
             pos[index2] = mul % 10;
         }
     }
 ​
     //去掉前置0
     let result = pos.join("").replace(/^0+/, "");
 ​
     return result - 0 || '0';
 }

3. 实现 add(1)(2)(3)

函数柯里化概念: 柯里化(Currying)是把接受多个参数的函数转变为接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。

1)粗暴版

 function add (a) {
 return function (b) {
     return function (c) {
       return a + b + c;
     }
 }
 }
 console.log(add(1)(2)(3)); // 6

2)柯里化解决方案

  • 参数长度固定
 var add = function (m) {
   var temp = function (n) {
     return add(m + n);
   }
   temp.toString = function () {
     return m;
   }
   return temp;
 };
 console.log(add(3)(4)(5)); // 12
 console.log(add(3)(6)(9)(25)); // 43

对于add(3)(4)(5),其执行过程如下:

  1. 先执行add(3),此时m=3,并且返回temp函数;
  2. 执行temp(4),这个函数内执行add(m+n),n是此次传进来的数值4,m值还是上一步中的3,所以add(m+n)=add(3+4)=add(7),此时m=7,并且返回temp函数
  3. 执行temp(5),这个函数内执行add(m+n),n是此次传进来的数值5,m值还是上一步中的7,所以add(m+n)=add(7+5)=add(12),此时m=12,并且返回temp函数
  4. 由于后面没有传入参数,等于返回的temp函数不被执行而是打印,了解JS的朋友都知道对象的toString是修改对象转换字符串的方法,因此代码中temp函数的toString函数return m值,而m值是最后一步执行函数时的值m=12,所以返回值是12。
  • 参数长度不固定
 function add (...args) {
     //求和
     return args.reduce((a, b) => a + b)
 }
 function currying (fn) {
     let args = []
     return function temp (...newArgs) {
         if (newArgs.length) {
             args = [
                 ...args,
                 ...newArgs
             ]
             return temp
         } else {
             let val = fn.apply(this, args)
             args = [] //保证再次调用时清空
             return val
         }
     }
 }
 let addCurry = currying(add)
 console.log(addCurry(1)(2)(3)(4, 5)())  //15
 console.log(addCurry(1)(2)(3, 4, 5)())  //15
 console.log(addCurry(1)(2, 3, 4, 5)())  //15

4. 实现类数组转化为数组

类数组转换为数组的方法有这样几种:

  • 通过 call 调用数组的 slice 方法来实现转换
 Array.prototype.slice.call(arrayLike);
  • 通过 call 调用数组的 splice 方法来实现转换
 Array.prototype.splice.call(arrayLike, 0);
  • 通过 apply 调用数组的 concat 方法来实现转换
 Array.prototype.concat.apply([], arrayLike);
  • 通过 Array.from 方法来实现转换
 Array.from(arrayLike);

5. 使用 reduce 求和

arr = [1,2,3,4,5,6,7,8,9,10],求和

 let arr = [1,2,3,4,5,6,7,8,9,10]
 arr.reduce((prev, cur) => { return prev + cur }, 0)

arr = [1,2,3,[[4,5],6],7,8,9],求和

 let arr = [1,2,3,4,5,6,7,8,9,10]
 arr.flat(Infinity).reduce((prev, cur) => { return prev + cur }, 0)

arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}],求和

 let arr = [{a:9, b:3, c:4}, {a:1, b:3}, {a:3}] 
 ​
 arr.reduce((prev, cur) => {
     return prev + cur["a"];
 }, 0)