下班坐地鐵的時候,好友忽然給我發過來一條微信: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倍……嗯。
並且我感受我多比他寫的判斷參數也耗費了很多時間,懶得改了,拿一個極端的例子再試一下:
仍是數倍的差距,你們怎麼看呢?有沒有更好的辦法實現這道題呢?