Недостающие математические методы в JavaScript

Недостающие математические методы в JavaScript

Содержание
  1. Sum
  2. Товар
  3. Четные и нечетные
  4. triangleNumber
  5. Факториал
  6. Факторы
  7. isPrime
  8. lcm (наименьшее общее кратное)
  9. Заключение

В этой статье мы рассмотрим некоторые из недостающих математических методов в JavaScript и то, как мы можем написать функции для них.

Объект JavaScript Math содержит несколько действительно полезных и мощных математических операций, которые можно использовать в веб-разработке, но ему не хватает многих важных операций, которые предоставляет большинство других языков (например, Haskell, в котором их огромное количество).

Sum

Возможно, вы помните из школы, что слово ”сумма” - это синоним слова ”прибавить”. Например, если мы складываем числа 1, 2 и 3, то на самом деле это означает 1 + 2 + 3.

Наша функция `sum будет суммировать все значения в массиве.

Есть два способа написать эту функцию: можно использовать цикл for, а можно воспользоваться функцией reduce. Если вы хотите заново познакомиться с функцией reduce, вы можете прочитать об использовании map() и reduce() в JavaScript.

Использование цикла for:

function sum(array) {
	let total = 0;
	for (let count = 0; count < array.length; count++) {
		total = total + array[count];
	}
	return total;
}

Использование функции reduce:

function sum(array) {
	return array.reduce((sum, number) => sum + number, 0);
}

Обе функции работают одинаково (функция reduce - это просто встроенный цикл for), и вернут одно и то же число (при том же массиве). Но функция reduce намного аккуратнее.

Например:

sum([1,2,3,4]) === 10 sum([2,4,6,8]) === 20.

Возможность суммировать список чисел - это, пожалуй, самая полезная и самая необходимая математическая операция, которой не хватает в объекте JavaScript Math. Опять же, функция sum работает как отличный инструмент проверки. Например, в судоку мы можем проверить, нет ли у пользователя повторов в данном столбце или строке, проверив, что столбец/строка складываются в 45 (1 + 2 + 3 + 4 +…+ 9). Эта функция также хорошо работает в приложении для интернет-магазинов, если мы хотим вычислить общую сумму счета - при условии, что все цены хранятся в массиве.

Следуя примеру приложения для покупок, вот пример того, как мы можем использовать ее в нашем коде:

const prices = [2.8, 6.1, 1.5, 1.0, 8.99, 2.99];

function totalCost(prices) {
	return prices.reduce((sum, item) => sum + item, 0);
}

Товар

Наша функция product будет работать аналогично функции sum, за исключением того, что вместо сложения всех чисел в списке мы будем их перемножать.

И снова мы можем использовать цикл for почти так же, как и в первой функции sum:

function product(array) {
	let total = 1;
	for (let count = 0; count < array.length; count++) {
		total = total * array[count];
	}
	return total;
}

Обратите внимание, что мы инициализируем переменную total значением 1, а не 0, так как в противном случае total всегда будет равна 0.

Но функция reduce все равно работает в этом случае и является более удобным способом написания функции:

function product(array) {
	return array.reduce((total, num) => total * num, 1);
}

Вот несколько примеров:

product([2,5,8,6]) === 480 product([3,7,10,2]) === 420.

Применение этой функции может показаться неочевидным, но я обнаружил, что она очень полезна при попытке выполнить несколько преобразований в рамках одного расчета. Например, если вам нужно найти цену в долларах десяти упаковок яблок (каждая килограммовая упаковка стоит 1,50 доллара), то вместо того, чтобы перемножать огромные суммы, эффективнее было бы хранить все значения в массиве и использовать функцию product, которую мы только что написали.

Пример массива будет иметь такой формат:

const pricePerKg = 1.5;
const numberOfKg = 10;
const conversionRate = 1.16;
const conversion = [1.5, 10, 1.16];
const USprice = product([pricePerKg, numberOfKg, conversionRate]);

Четные и нечетные

Эти функции принимают число, которое может быть в виде длины массива, и возвращают true или false в зависимости от того, является ли это число четным или нечетным.

Чтобы число было четным, оно должно быть кратным двум, а чтобы число было нечетным, наоборот, оно не должно быть кратным двум. Это будет ключевой частью функций.

В Haskell, например, эти функции встроены, что значительно упрощает работу, тем более что вы можете просто написать это:

четные 29 << false нечетные 29 << true.

Ruby, с другой стороны, предоставляет эти функции в виде методов. Но это все равно намного проще:

29.even? << false 29.odd? << true

Самый простой способ написать эти функции на JavaScript - использовать оператор остатка, %. Он возвращает остаток при делении одного числа на другое. Например:

11 % 3 === 2

Вот пример того, как может выглядеть наша функция even:

function even(number){ return number % 2 === 0 }.

Как мы видим, у нас есть функция even, которая принимает в качестве параметра число и возвращает булево значение, основанное на условии:

number % 2 === 0.

Когда число делится на два, если остаток равен нулю, мы знаем, что оно делится на два, и вернем true. Например:

even(6) === true even (9) === false.

Вот пример whв нашей функции odd может выглядеть следующим образом:

function odd(number){ return number % 2 !== 0 }.

Эти две функции очень похожи: в качестве параметра берется число, а в зависимости от условия возвращается булево значение:

number % 2 !== 0.

Если остаток от деления числа на два не равен нулю, то число нечетное и будет возвращено true. Например:

odd(7) === true odd(114) === false.

Возможность проверить, является ли число четным или нечетным, крайне важна, и это удивительно просто. Поначалу это может показаться не столь важным, но это может работать как отличная техника проверки ввода - например, с длиной массива или просто при проверке победителя в игре для двух игроков. Вы можете отслеживать, сколько раундов было сыграно, и если число нечетное, то побеждает игрок 1, а если четное, то побеждает игрок 2 - при условии, что первый раунд считается 1.

Эти функции взаимозаменяемы, и, скорее всего, нам понадобится только одна. Однако наличие двух функций значительно облегчает отслеживание логики true или false, особенно в больших кусках кода.

Вот как мы можем написать пример выше:

function checkWinner(gamesPlayed) {
	let winner;
	if (odd(gamesPlayed)) {
		winner = 'player1';
	} else {
		winner = 'player2';
	}
	return winner;
}

triangleNumber

Треугольные числа звучат гораздо причудливее, чем они есть на самом деле. Это просто сумма всех целых чисел до определенного числа.

Например, это пятое треугольное число: 5 + 4 + 3 + 2 + 1 = 15.

Это возвращает нас к предыдущему примеру с судоку. Мы хотим убедиться, что все цифры уникальны, и можем сделать это, проверив, что они совпадают с результатом 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9. Это, конечно же, девятое треугольное число!

Конечно, мы можем написать функцию, используя цикл for, примерно так:

function triangleNumber(number) {
	let sum = 0;
	for (let i = 1; i < number + 1; i++) {
		sum = sum + i;
	}
	return sum;
}

Однако это было бы очень неэффективным решением, потому что существует очень простая формула для вычисления треугольных чисел: 0,5 x (число) x (число + 1).

Таким образом, наиболее эффективная версия нашей функции должна выглядеть следующим образом:

function triangleNumber(number) {
	return 0.5 * number * (number + 1);
}

Вот несколько примеров того, как мы будем ее использовать:

triangleNumber(7) === 28 triangleNumber(123) === 7626

Факториал

Факториал натурального числа (любое целое число, строго большее 0) - это произведение всех чисел, меньших или равных этому числу. Например: Факториал числа 3 (обозначается 3!) равен 3 x 2 x 1 = 6.

Как и в случае с функциями сумма и продукт, существует два способа создания функции факториал: с помощью цикла for и с помощью рекурсии. Если вы еще не знакомы с рекурсивными алгоритмами, то это, по сути, функции, которые вызывают сами себя несколько раз, пока не достигнут ”базового случая”. Подробнее о них вы можете прочитать в статье ”Рекурсия в функциональном JavaScript”.

Вот как мы можем создать нашу функцию factorial с помощью цикла for:

function factorial(number) {
	let total = 1;
	for (let i = 1; i < number + 1; i++) {
		total = total * i;
	}
	return total;
}

Эта функция перебирает все числа от 1 до number (увеличивая их при каждом проходе) и умножает total на каждое число, а затем возвращает итоговый результат (факториал числа).

Вот как мы можем создать нашу функцию factorial, используя рекурсию:

function factorial(number) {
	if (number <= 0) {
		return 1;
	} else {
		return number * factorial(number - 1);
	}
}

В этой функции базовым случаем является ноль, так как 0!, на удивление, равно единице (доказательство этого на самом деле очень интересно). Это означает, что при прохождении числа через функцию, пока оно не равно нулю, оно будет умножаться на факториал(число - 1).

Чтобы понять, что именно делает эта функция при каждом проходе, можно проследить алгоритм. Вот алгоритм, отслеженный с помощью 3:

def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

result = factorial(3)
print(result)  # Output: 6

В любом случае обе функции вернут одно и то же значение. Например:

factorial(5) === 120

Факторы

Факторы образуют пары, и каждая пара умножается вместе, образуя исходное число. Например:

Факторами 10 являются: 1 и 10; 2 и 5. Факторами 18 являются: 1 и 18; 2 и 9; 3 и 6.

Мы хотим, чтобы наша функция factors принимала число и возвращала массив всех его факторов. Существует множество способов написать эту функцию, но самый простой - использовать императивный подход, например, такой:

function factors(number) {
	let factorsList = [];
	for (let count = 1; count < number + 1; count++) {
		if (number % count === 0) {
			factorsList.push(count);
		}
	}
	return factorsList;
}

Во-первых, мы создаем наш массив, оставляя его пустым для начала. Затем с помощью цикла for перебираем все целые числа от 1 до самого числа, и при каждом переходе проверяем, делится ли число на целое число (или count в данном случае).

Как вы понимаетеВидите, чтобы проверить делимость, мы снова используем знак mod. И если число делится на целое число, то оно является множителем и может быть помещено в наш массив.

Затем этот массив возвращается, и каждый раз, когда мы запускаем функцию, будет возвращаться массив факторов в порядке возрастания. Например:

factors(50) === [1,2,5,10,25,50].

Поиск факторов числа может быть невероятно полезен, особенно когда вам нужно сформировать группы - например, в онлайн-играх, когда вам нужно равное количество пользователей в каждой команде. Например, если у вас 20 пользователей и каждой команде нужно 10 игроков, вы сможете использовать функцию факторы, чтобы подобрать 10 игроков для двух команд. Аналогично, если бы каждой команде требовалось по четыре игрока, вы могли бы использовать функцию factors для объединения четырех в пять команд.

На практике это может выглядеть следующим образом:

function createTeams(numberOfPlayers, numberOfTeams) {
	let playersInEachTeam;
	if (factors(numberOfPlayers).includes(numberOfTeams)) {
		playersInEachTeam = numberOfPlayers / numberOfTeams;
	} else {
		playersInEachTeam = 'ждем еще игроков';
	}
	return playersInEachTeam;
}

isPrime

Это одно из самых ранних условий, которое вы изучаете в школе, но в повседневной жизни оно используется нечасто. В двух словах, число является простым, если оно имеет два различных коэффициента, которые всегда равны единице и самому себе. К простым числам относятся: 2, 3, 5, 7, 11, 13, 17, 19… и так далее до бесконечности.

Поначалу может показаться, что это сложная функция - и это действительно так, если бы мы только что не написали очень полезную функцию факторы. Как уже говорилось, число является простым, если оно имеет два различных фактора, поэтому наша функция проста:

function isPrime(number){ return factors(number).length === 2 }.

Это вернет булево значение, основанное на том, равна ли длина списка факторов числа двум - другими словами, имеет ли оно два фактора.

На практике это будет выглядеть следующим образом:

isPrime(3) === true isPrime(76) === false isPrime(57) === true.

Продолжая пример с ”группировкой пользователей”, приведенный выше, если число пользователей простое, мы не можем сгруппировать их поровну (если только у нас не одна группа, но это противоречит цели примера), что означает, что нам придется ждать, пока еще один пользователь присоединится. Таким образом, мы можем использовать его в такой функции, как эта:

function addUsers(users){ if(isPrime(users)){ wait = true } else{ wait = false } } Недостающие математические методы в JavaScript: gcd (Наибольший общий делитель)

Операция наибольшего общего делителя, иногда известная как ”наибольший общий коэффициент”, позволяет найти наибольший коэффициент, который имеют два числа.

Например:

GCD чисел 12 и 15 равно 3. GCD чисел 8 и 4 равно 4.

Простой способ выяснить это - перечислить все коэффициенты каждого числа (используя нашу невероятную функцию выше) и сравнить эти списки. Однако сравнение списков требует довольно удобных, но неэффективных манипуляций с массивами.

Но вот пример:

function gcd(number1, number2) {
	let inCommon = [];
	for (let i of factors(number1)) {
		if (factors(number2).includes(i)) {
			inCommon.push(i);
		}
	}
	return inCommon.sort((a, b) => b - a)[0];
}

Здесь мы присваиваем переменной inCommon пустой массив и в цикле просматриваем массив факторов number1 (используя нашу функцию из предыдущей части). Если массив факторов number2 содержит элемент в текущем проходе, мы помещаем его в наш массив inCommon.

Когда у нас есть массив всех общих факторов двух чисел, мы возвращаем первое значение массива, отсортированное в порядке убывания. Другими словами, мы возвращаем наибольший общий делитель.

Как вы можете себе представить, если бы мы еще не создали функцию factors, код для этого был бы огромным.

Более лаконичный, но более сложный способ сделать это - использовать рекурсию. Это довольно известный алгоритм, который называется Евклидовым алгоритмом:

function gcd(number1, number2) {
	if (number2 === 0) {
		return number1;
	} else {
		return gcd(number2, number1 % number2);
	}
}

В нашем базовом случае число2 равно 0, и тогда число1 является наибольшим общим делителем. В противном случае GCD - это GCD числа2 и остатка числа1, деленного на число2.

Опять же, обе функции вернут одно и то же. Например:

gcd(24, 16) === 8 gcd(75, 1) === 1

lcm (наименьшее общее кратное)

Наименьшее общее кратное работает по аналогии с наибольшим общим делителем, но вместо этого находит наименьшее целое число, от которого оба числа являются коэффициентами.

Например:

LCM чисел 2 и 6 равно 6. LCM чисел 4 и 15 равно 60.

К сожалению, для этой функции мы не можем просто создать массив всех кратных каждого числа, так как это будет бесконечный список.

Однако есть очень полезная формула, которую мы можем использовать для вычисления наименьшего общего кратного:

(число1 x число2) / наибольший общий делитель двух чисел.

Чтобы проверить формулу, вы можете попробовать ее на примере выше. LCM из 2 и 6:

(2 x 6)/gcd(2,6) = 12/2 = 6.

К счастью для нас, мы только что создали функцию gcd, поэтому создать эту функцию на удивление просто:

function lcm(number1, number2) {
	return (number1 * number2) / gcd(number1, number2);
}

Вот и все! Осталось только вернуть формулу, приведенную выше, и все заработает:

lcm(12, 9) === 36.

Возможно, эта функция не имеет очевидного применения, но я часто обнаруживал, что она отлично подходит для ситуаций, когда два события происходят через разные промежутки времени, что означает, что мы можем использовать LCM, чтобы узнать, когда эти два события происходят в одно и то же время.

Например, если изображение запрограммировано на появление каждые шесть секунд, а абзац текста - на появление каждые восемь секунд, то изображение и абзац впервые появятся вместе на 24-й секунде.

Заключение

Все описанные выше функции можно найти в следующей демонстрации CodePen, где вы можете взаимодействовать с функциями и увидеть их работу на практике.

Однако если вы хотите избавить себя от необходимости копировать эти функции каждый раз, когда они вам понадобятся, я собрал их (а также несколько других) в мини-библиотеку под названием JOG-Maths.

Надеюсь, это дало вам несколько идей о том, какие математические операции вы можете использовать помимо встроенного объекта JavaScript Math, и о силе математики в коде! Источник