下班坐地铁的时候,好友忽然给我发过来一条微信:vue
这道题是这样:react
我们软件开发不是常常不是有版本号吗?
好比经典的兼容IE浏览器的jQuery1.x最后的一个版本1.12.4,但有时我们又不会精确到第三位,好比:vue3.0、react16.8…面试
而后实现一个方法,把两个版本号传进去,能够两位(如:3.0)也能够三位(如:1.12.4),若是第一个版本比第二个版本大,就返回1。算法
若是第二个版本比第一个大,就返回-1。 其余状况返回0。数组
我很快就想出了一个思路:(先省略前期的各类判断以及类型转换)用split方法将其切割成数组,而后把两个数组的第一位相比较,若是第一位就比较出结果就能够直接返回,没有必要在比较第二位了,依此类推,直到比较到最后一位。若是比较到最后一位的时候两个数组的长度不一致,就为短的那一方加个0,好比3.0就会变成3.0.0。浏览器
本觉得他也会这么想,但万万没想到他说出了一个脑回路跟我不太同样的解法:
他这么一说,我一会儿就想到了JS小数计算不许确的问题,他确定是从那得到的灵感。
不过他竟然说我是暴力解法,我怎么以为他那种才是暴力解法……
既然有争议,那我们就一不作二不休,实现一下吧!微信
function comparison (version1, version2) { // 参数的类型 const types = ['string', 'number'] // 第一个参数的类型 const type1 = typeof version1 // 第二个参数的类型 const type2 = typeof version2 // 参数不是字符串或数字的状况下返回0 if (!types.includes(type1) || !types.includes(type2)) return 0 // 若是version1是number就将其转成字符串 const ver1 = type1 === 'number' ? version1.toString() : version1 // 若是version2是number就将其转成字符串 const ver2 = type2 == 'number' ? version2.toString() : version2 // 将version1变成数组 const versionArr1 = ver1.split('.') // 将version2变成数组 const versionArr2 = ver2.split('.') // 获取长度最长的数组的length const len = Math.max(versionArr1.length, versionArr2.length) // 循环对比版本号,若是前一位比较不出大小就继续向后对比 for (let i = 0; i < len; i++) { // 若是长度不一致将自动补0 同时将字符串转为数字 const version1 = Number(versionArr1[i]) || 0 const version2 = Number(versionArr2[i]) || 0 if (version1 > version2) { // 若是version1大就返回1 return 1 } else if (version1 < version2) { // 若是version2大就返回-1 return -1 } else { // 若是比较到最后就返回0,不然继续比较 if (i + 1 === len) { return 0 } else { continue } } } }
为了方便观看这里省略了用正则判断传进来的参数是否符合xx.xx.xx形式或者去除前面的0等一些繁琐判断(面试题应该也不用写那么细,写出思路便可),因此只判断了参数是否为string或number类型,来测试一下:
因为没有写死判断,因此甚至能够实现无限版本号的比较:
好像没啥毛病哈,反正面试题说的是其他状况返回0,版本相等、传错参数应该都属于其他状况。性能
固然这个版本号过多(1.2.3.4.5.6)的状况严格意义上也算是传错参数,但为了验证一下版本号过多的状况个人方法性能与朋友的方法性能哪一个好(验证一下是否为暴力解法),因此写成了这样(这种更难写呢),并且写成这样更利于可持续化发展嘛,若是较真的朋友能够采用if(len === 2)和if(len === 3)的这种形式来解题。测试
接下来我们再来看一下我朋友的方法:spa
经测试有bug,后来又改了几版,下面是最终版:
function compareVersion(version1, version2) { let ver1 = version1.split('.') let ver2 = version2.split('.') let len1 = '' let len2 = '' ver1.forEach((item,index)=>{ let zero = '' for (let i = 0; i < index; i++) { zero += '0' } len1 += zero + item }) ver2.forEach((item,index)=>{ let zero = '' for (let i = 0; i < index; i++) { zero += '0' } len2 += zero + item }) let len = len1.length - len2.length if(len > 0){ len2 = Number(len2) * Math.pow(10,Math.abs(len)) }else{ len1 = Number(len1) * Math.pow(10,Math.abs(len)) } if(len1>len2){ return 1 }else if(len1<len2){ return -1 }else{ return 0 } }
<center>(最终版其实没有用到replace方法,他也用了split)</center>
这是他的原代码,没有写注释,后来他发微信过来讲让我把循环加0的那步去掉,只加一个0便可。
恰好我打算改造一下他的代码顺便整理下格式而后加点注释,改造后以下:
function compareVersion(version1, version2) { // 将传进的版本号切割为数组 const ver1 = version1.split('.') const ver2 = version2.split('.') // 将数组相加中间补0最后变成一个数字(字符串) let len1 = ver1.reduce((sum, item) => sum + '0' + item, '') let len2 = ver2.reduce((sum, item) => sum + '0' + item, '') // 得出两个数字(字符串)长度的差值 const len = len1.length - len2.length // 若是差值大于0 if (len > 0) { // 第二个数字就乘以十的差值次方 len2 = Number(len2) * Math.pow(10, Math.abs(len)) } else { // 不然第一个数字就乘以十的差值次方 len1 = Number(len1) * Math.pow(10, Math.abs(len)) } if (len1 > len2) { // 若是第一个数比第二个数大,返回1 return 1 } else if (len1 < len2) { // 若是第一个数比第二个数小,返回-1 return -1 } else { // 不然返回0 return 0 } }
因为没作判断,致使传入别的值会报错,不过在传参正确的状况下好像没什么毛病。
而后今早咱们又争执了一番:
我是这么想的,他这个方法不管第一位是否相同,都是要循环整个数组的。
而个人只要第一位不同瞬间就能得出结果,而后还说个人算法复杂度是O(n),我就想不明白了一个for循环遍历数组怎么就成O(n)了,我们程序里循环遍历数组多广泛的一件事啊,都成O(n)了?
并且若是不算前期判断传入的参数是否为数组中的类型的话,我只循环了一次数组,他这个循环两个数组,我说他确定不服,那我们用数听说话:
首先在方法的第一行就加入一个console.time('运行时长:'),而后再在返回值以前加入console.timeEnd('运行时长:')。
这是一个比较方便的测试程序运行时长的一种方式,注意 console.time() 和 console.timeEnd() 里面的参数必定要如出一辙才管用,你们能够去试试。
个人代码:
function comparison (version1, version2) { console.time('运行时长:') // 参数的类型 const types = ['string', 'number'] // 第一个参数的类型 const type1 = typeof version1 // 第二个参数的类型 const type2 = typeof version2 // 参数不是字符串或数字的状况下返回0 if (!types.includes(type1) || !types.includes(type2)) return 0 // 若是version1是number就将其转成字符串 const ver1 = type1 === 'number' ? version1.toString() : version1 // 若是version2是number就将其转成字符串 const ver2 = type2 == 'number' ? version2.toString() : version2 // 将version1变成数组 const versionArr1 = ver1.split('.') // 将version2变成数组 const versionArr2 = ver2.split('.') // 获取长度最长的数组的length const len = Math.max(versionArr1.length, versionArr2.length) // 循环对比版本号,若是前一位比较不出大小就继续向后对比 for (let i = 0; i < len; i++) { // 若是长度不一致将自动补0 同时将字符串转为数字 const version1 = Number(versionArr1[i]) || 0 const version2 = Number(versionArr2[i]) || 0 if (version1 > version2) { console.timeEnd('运行时长:') // 若是version1大就返回1 return 1 } else if (version1 < version2) { console.timeEnd('运行时长:') // 若是version2大就返回-1 return -1 } else { // 若是比较到最后就返回0,不然继续比较 if (i + 1 === len) { console.timeEnd('运行时长:') return 0 } else { continue } } } }
他的代码:
function compareVersion(version1, version2) { console.time('运行时长:') // 将传进的版本号切割为数组 const ver1 = version1.split('.') const ver2 = version2.split('.') // 将数组相加中间补0最后变成一个数字(字符串) let len1 = ver1.reduce((sum, item) => sum + '0' + item, '') let len2 = ver2.reduce((sum, item) => sum + '0' + item, '') // 得出两个数字(字符串)长度的差值 const len = len1.length - len2.length // 若是差值大于0 if (len > 0) { // 第二个数字就乘以十的差值次方 len2 = Number(len2) * Math.pow(10, Math.abs(len)) } else { // 不然第一个数字就乘以十的差值次方 len1 = Number(len1) * Math.pow(10, Math.abs(len)) } if (len1 > len2) { console.timeEnd('运行时长:') // 若是第一个数比第二个数大,返回1 return 1 } else if (len1 < len2) { console.timeEnd('运行时长:') // 若是第一个数比第二个数小,返回-1 return -1 } else { console.timeEnd('运行时长:') // 不然返回0 return 0 } }
测试结果:
按理说,应该是第一位就能比较出结果,而且位数很长的一个数值,运行的差距就越明显,来试一下:
差了两倍多,不过感受怎么没有本身想象中的差距那么明显呢?换下位置再试试:
这回差了6倍……嗯。
并且我感受我多比他写的判断参数也耗费了很多时间,懒得改了,拿一个极端的例子再试一下:
仍是数倍的差距,你们怎么看呢?有没有更好的办法实现这道题呢?