壹 ❀ 引
在最近的工作中,有一個任務是需要修復富文本編輯器字號顯示的BUG。大概情況就是,從WPS中復制不同樣式的標題、正文到到項目編輯器中,發現沒辦法設置選中的文本為正文;而且字體字號都顯示為默認的情況下,這些字體大小還表現不同。因為該富文本編輯器是基於ckeditor二次開發,所以也是看了一天的源碼才成功定位到問題,最后發現WPS的字體單位使用的是印刷行業的單位,也就是pt,而不是我們熟知的px,這就導致了富文本編輯器無法識別該pt單位,從而產生了后續一系列的bug。
經過與程序原作者溝通討論,最終確定的修復方案是將WPS粘貼過來的文本原數據進行單位換算加工。其次還有一個問題,編輯器的字號大小選擇范圍一般是間斷的而非連續的,比如下圖:
如果編輯器能展示的字號范圍未能匹配到我們最終拿到的字號大小,那么編輯器就會展示字號為默認,因為匹配不到,那這樣就還是會造成13px與15px都顯示為默認的情況,從而給了用戶一種字號都是默認,但字體大小顯示不同的疑問。所以我們在換算之后還多了一步操作,我們需要在編輯器的字號范圍中找到與換算后的字號最接近的數字,作為最終展示字體大小,那么這就衍生出了一個問題,怎么在數組中找到與目標數字最為接近的數字。
貳 ❀ 嘗試解決
歸納上面的問題,其實就是在一個數組中找到與目標數字最近接的數字,比如:
const arr = [1,3,5,6,10];
const target = 7;
// 最終找到6,因為7和6的差最小
const arr = [1,3,5,6,10];
const target = 3;
// 最終找到3,還有比相等的數字更為接近的?
只要簡單分析上述兩個例子,我們要找的值其實就是與目標值target的差最小的一個數,考慮到存在差為負數的情況,所以這個差應該是一個絕對值,可用abs()轉換。那么我們來嘗試實現它:
const arr = [1, 2, 6, 10, 9];
const target = 3;
const findNearestNumber = (arr, target) => {
// 我們假設最近接的就是數組第第一個數字
let result = arr[0];
for (let i = 1, len = arr.length; i < len; i++) {
if (Math.abs(arr[i] - target) < Math.abs(result - target)) {
result = arr[i];
};
};
return result;
};
console.log(findNearestNumber(arr, target));//2
讓我們回顧上述代碼,一開始,我們需要提供一個初始值,並拿下一個值參與比較之后決定返回兩者中的某一個值,然后繼續參與后續遍歷,仔細一想,這不就是在做reduce的操作嗎,所以我們簡化下代碼,變成了這樣:
const findNearestNumber = (arr, target) => {
return arr.reduce((pre, curr) => {
return Math.abs(pre - target) > Math.abs(curr - target) ? curr : pre;
})
};
以上的實現適用於有序以及無序數組,考慮無序的情況,我們最差的情況就是完整遍歷一遍,最終發現最后一個數字才是我們想要的,所以站在時間復雜度上來說就是O(n)。
不過我們現在的需求有點不同,很明顯字號大小是從小到大排列的,比如這樣一個數組:
const fontSize = [8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72];
這是一個有序數組,就像肌肉記憶一樣,看到有序數組,談到遍歷我們首先想到的就應該是二分查找,其實上篇文章我也提過公司code review會比較嚴格,對於性能要求比較高。發版那天本來就夠忙了,我可不想在提測通過后,因為review沒過,又得修改代碼再次走測試流程,非常費時,所以保險起見,這里就直接考慮二分查找來做了。
我們聲明左右兩個指針,分別指向數組的第0位索引和最后一位索引,然后求出中間索引,拿中間索引所對應的數字與target對比,如果target比arr[mid]大,那說明target肯定在右邊范圍,這時候只要修改左指針為mid即可,反之修改右指針。
因為目標值不存在於數組中,所以最終我們得保證左右指針指到相鄰的兩個數字上,大致實現如下:
const findNearesttargetber = (arr, target) => {
let mid;
let l = 0;
let r = arr.length - 1;
// 保證指針最終停留在相鄰的兩個數,所以這里是判斷是否大於1
while (r - l > 1) {
mid = Math.floor((l + r) / 2);
// 如果目標數比中間小,所以范圍在左邊
if (target < arr[mid]) {
r = mid;
} else {
l = mid;
};
};
// 最后比較這兩個數字的絕對差大小即可。
return Math.abs(target - arr[l]) <= Math.abs(target - arr[r]) ? arr[l] : arr[r];
}
由於二分查找每次都能排除掉一半的可能,所以時間復雜度為O(logn),當然由於當前數組並不是很龐大,執行上其實也並不太大區別,只是盡可能去這么寫了。那么關於本文就到這里了。
