Оптимизация на скоростта чрез памет в JavaScript
Има ситуации, когато може да се пожертва оперативна памет за повишаване на бързодействието.
Нека разгледаме с пример. Следният код намира приятелски числа в зададен интервал:
console.log(getFriendly(9000));
function getFriendly(range) {
let res = [];
for (let i = 1; i <= range; i++) {
for (let j = 1; j < range; j++) {
if (isFriendly(i, j)) {
res.push([i, j]);
}
}
}
return res;
}
function isFriendly(num1, num2) {
let sum1 = getSum(getOwnDivisors(num1));
let sum2 = getSum(getOwnDivisors(num2));
return sum1 == num2 && sum2 == num1;
}
function getOwnDivisors(num) {
let res = [];
for (let i = 1; i < num; i++) {
if (num % i === 0) {
res.push(i);
}
}
return res;
}
function getSum(arr) {
let sum = 0;
for (let elem of arr) {
sum += elem;
}
return sum;
}
Приведеният по-горе код не е оптимален.
Той извършва голям брой операции
и при посочения интервал до 9000
страницата на браузъра просто ще замръзне.
Проблемът на този код е, че ние
за всяко число изчисляваме сумата от неговите
делители много пъти, толкова,
колкото общо числа проверяваме.
Това означава, че в нашия случай
за всяко число сумата от неговите делители
ще бъде намерена 9000 пъти.
Не е изненадващо, че всичко замръзва.
Нека оптимизираме. За начало нека направим функция, която директно изчислява сумата на делителите, без да ги запазва в масив:
function getOwnDivisorsSum(num) {
let sum = 0;
for (let i = 1; i < num; i++) {
if (num % i === 0) {
sum += i;
}
}
return sum;
}
Сега дойде времето да пожертваме оперативна памет. Нека направим функция, която предварително веднъж изчисли сумата на делителите на всички числа от зададения интервал и ги запази в масив.
Като резултат нашата функция ще връща масив, в който ключът ще бъде числото (с един по-малко), а стойността - сумата от неговите делители. Нека реализираме нашата функция:
function getAllSum(range) {
let arr = [];
for (let i = 1; i <= range; i++) {
arr.push(getOwnDivisorsSum(i));
}
return arr;
}
Сега за проверка на приятелствеността няма да изчисляваме всеки път сумата на делителите на числата, а просто ще вземаме вече изчислената от масива:
function getFriendly(range) {
let sums = getAllSum(range); // [1, 2, 6...]
let res = [];
for (let i = 0; i < sums.length; i++) {
for (let j = i; j < sums.length; j++) {
let sum1 = sums[i];
let sum2 = sums[j];
let num1 = i + 1;
let num2 = j + 1;
if (num1 == sum2 && num2 == sum1) {
res.push([num1, num2]);
}
}
}
return res;
}
Нека съберем всичко заедно и получим следния код:
console.log(getFriendly(9000));
function getFriendly(range) {
let sums = getAllSum(range);
let res = [];
for (let i = 0; i < sums.length; i++) {
for (let j = i; j < sums.length; j++) {
let sum1 = sums[i];
let sum2 = sums[j];
let num1 = i + 1;
let num2 = j + 1;
if (num1 == sum2 && num2 == sum1) {
res.push([num1, num2]);
}
}
}
return res;
}
function getAllSum(range) {
let arr = [];
for (let i = 1; i <= range; i++) {
arr.push(getOwnDivisorsSum(i));
}
return arr;
}
function getOwnDivisorsSum(num) {
let sum = 0;
for (let i = 1; i < num; i++) {
if (num % i === 0) {
sum += i;
}
}
return sum;
}
Следният код намира взаимно-прости числа от зададен интервал. Оптимизирайте го:
console.log(getRelativelyPrime(10000));
function getRelativelyPrime(range) {
let res = [];
for (let i = 2; i <= range; i++) {
for (let j = 2; j < range; j++) {
if (isRelativelyPrime(i, j)) {
res.push([i, j]);
}
}
}
return res;
}
function isRelativelyPrime(num1, num2) {
let arr1 = getDivisors(num1);
let arr2 = getDivisors(num2);
let int = getIntersect(arr1, arr2);
if (int.length === 0) {
return true;
} else {
return false;
}
}
function getIntersect(arr1, arr2) {
let result = [];
for (let elem of arr1) {
if (arr2.includes(elem)) {
result.push(elem);
}
}
return result;
}
function getDivisors(num) {
let res = [];
for (let i = 2; i <= num; i++) {
if (num % i === 0) {
res.push(i);
}
}
return res;
}