Optimalizace rychlosti na úkor paměti v JavaScriptu
Existují situace, kdy lze obětovat operační paměť pro zvýšení výkonu.
Podívejme se na příklad. Následující kód hledá spřátelená čísla v zadaném rozsahu:
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;
}
Výše uvedený kód není optimální.
Provádí velké množství operací
a při zadaném rozsahu do 9000
stránka prohlížeče jednoduše zamrzne.
Problém tohoto kódu je v tom, že
pro každé číslo počítáme součet jeho
dělitelů velmi mnohokrát, tolikrát,
kolik čísel celkem kontrolujeme.
To znamená, že v našem případě
pro libovolné číslo bude součet jeho dělitelů
vypočítán 9000krát.
Není divu, že vše zamrzá.
Pojďme to optimalizovat. Pro začátek vytvořme funkci, která přímo vypočítává součet dělitelů, bez ukládání do pole:
function getOwnDivisorsSum(num) {
let sum = 0;
for (let i = 1; i < num; i++) {
if (num % i === 0) {
sum += i;
}
}
return sum;
}
Nyní je čas obětovat operační paměť. Vytvořme funkci, která předem jednou vypočítá součet dělitelů všech čísel z daného rozsahu a uloží je do pole.
Výsledkem naší funkce bude pole, ve kterém bude klíčem číslo (o jedna menší), a hodnotou - součet jeho dělitelů. Realizujme naši funkci:
function getAllSum(range) {
let arr = [];
for (let i = 1; i <= range; i++) {
arr.push(getOwnDivisorsSum(i));
}
return arr;
}
Nyní pro kontrolu spřátelenosti nebudeme pokaždé počítat součet dělitelů čísel, ale jednoduše vezmeme již spočítanou hodnotu z pole:
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;
}
Pojďme to všechno dát dohromady a získáme následující kód:
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;
}
Následující kód hledá nesoudělná čísla v zadaném rozsahu. Optimalizujte jej:
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;
}