Два программиста снова спорят о работе и JavaScript:
— Я сейчас в одном проекте делаю календарь, и для него я завёл отдельный массив с днями недели. Логично же?
— Ты что, не можешь по номеру месяца посчитать количество дней в нём?
— А ты что, можешь?!
🤔 Можно ли по номеру месяца посчитать количество дней в нём, используя одну формулу?
👉 На самом деле проще и логичнее сделать массив с количеством дней в каждом месяце. Но мы хотим попрактиковаться, потренировать ум и попробовать обойтись без списка. Такой нестандартный подход к решениям может вам здорово пригодиться в будущем на более сложных задачках и в рабочих проектах.
Что делать с февралём
В високосный год в феврале 29 дней, а не 28, как обычно. Но так как мы не знаем, про какой год идёт речь, и февраль не влияет на количество дней в других месяцах, то для простоты будем считать, что в феврале у нас всегда 28 дней.
Кто-то для подсчёта дней в месяце использует костяшки на руках, другие пользуются специальными стишками и фразами, которые помогают вспомнить количество дней, но это всё не то. Нужно именно универсальное решение, например функция. На вход эта функция будет получать номер месяца, а на выходе давать нужное количество дней.
Для начала запишем в виде таблицы наши месяцы и дни:
Получается, что если на вход мы подадим 8 (август), то на выходе функция нам должна вернуть число 31 (количество дней в августе). Такую функцию мы и будем делать.
Магия математики: целочисленное деление и остаток
В нашей функции мы будем использовать сложение, вычитание и умножение — простые действия, которые не требуют специальных знаний. Но ещё нам понадобятся две операции: целочисленное деление и взятие остатка по модулю.
Целочисленное деление — это целая часть от деления одного числа на другое, в математике обозначается обратной косой чертой: \. Грубо говоря, это сколько раз одно число полностью помещается в другом.
Например, 7 \ 2 = 3
. Это значит, что если 7 разделить на 2, то получится 3,5, но так как нам нужна целая часть от деления, то остаётся только 3.
Ещё примеры:
8 \ 3 = 2
5 \ 1 = 5
12 \ 4 = 3
Остаток от деления (его ещё называют остаток по модулю) — это как раз то, что остаётся от предыдущего деления, то, что не поместилось в целую часть. Иногда обозначается символом — %.
Для примера возьмём те же самые пары чисел, что были выше: 7 % 2 = 1
. Это значит, при делении 7 на 2 у нас останется в остатке единица:
7 = (2 × 3) + 1
Эта единица, которая не влезла в деление, и называется остатком от деления.
Ещё примеры с теми же числами:
8 % 3 = 2, потому что [8 = (3 × 2) + 2]
5 % 1 = 0, потому что [5 = (1 × 5) + 0]
12 % 4 = 0, потому что [12 = (4 × 3) + 0]
Используя только простую математику и эти две операции, попробуем найти нужную формулу.
Первый подход: проверяем чётные и нечётные месяцы
Попробуем сразу подобрать такое решение, которое подходило бы к большинству месяцев. Обычное количество дней — 30 или 31. Давайте проверим, вдруг дни зависят от того, чётный номер у месяца или нечётный.
Чтобы это выяснить, используем операцию взятия остатка от деления на 2. Если месяц чётный, то число разделится нацело и в остатке будет 0. Если месяц нечётный, то в остатке будет 1.
Проверим нашу теорию, и те дни, которые совпадут с нашей формулой, покрасим в зелёный цвет, а которые не совпадут — в красный. Номер месяца в этой и во всех других формулах обозначим как икс:
f = 30 + x % 2
У нас совпала почти половина месяцев, неплохо для начала. Как мы видим, март, месяц номер 3, как раз имеет 31 день, а июнь, месяц номер 6, — 30 дней.
С февралём будем разбираться потом, а сейчас нужно что-то придумать, чтобы начиная с августа наша формула работала наоборот. Для этого надо заменить в нашей формуле чётные месяцы на нечётные, чтобы при делении они давали в остатке 1, а не 0, как раньше. Для этого достаточно прибавить единицу к иксу: (x + 1) % 2
f = 30 + (x + 1) % 2
Теперь у нас идеально вписывается вторая половина календаря, но первая предсказуемо сломалась. Надо найти способ как-то объединить эти два способа.
Второй подход: объединяем обе половины формулы
Нам нужно, чтобы сначала применялась первая формула, а как только дойдём до августа — вторая.
Август — месяц номер 8, поэтому будем проверять, дошли мы уже до восьмого месяца или нет. Мы знаем, что всего месяцев 12, и если взять целую часть от деления на 8, то до августа она будет равна нулю, а начиная с августа — единице:
1 \ 8 = 0
8 \ 8 = 1
12 \ 8 = 1
Чтобы в этом убедиться, давайте нарисуем таблицу, куда запишем все значения:
Как раз то, что нам нужно: начиная с августа наша формула помечает все нужные месяцы единицей. Теперь надо этот фильтр аккуратно встроить в формулу.
Когда мы использовали в самом начале в формуле выражение х % 2
— работала первая половина месяцев, а когда добавили единицу — (х + 1) % 2
, — то работала вторая половина. Нам нужно, чтобы единица в этой формуле появлялась начиная с августа, а для этого как раз мы использовали целочисленное деление на 8. Поэтому если мы икс целочисленно разделим на 8, то начиная с августа он будет давать единицу, а до этого — ноль.
Целая часть от деления на 8 выглядит так: х \ 8
. Используем это в нашей формуле:
f = 30 + (x + x \ 8) % 2
Всё работает, кроме февраля. Давайте это исправим.
Добавляем февраль в формулу
В последней формуле у февраля получилось 30 дней, а надо 28. Значит, нужно убрать из него два дня. Так как формула у нас общая для всех, получается, что дни уберутся для всех месяцев либо до, либо после февраля. После февраля месяцев много, а до него — только январь. Поэтому уберём два дня у первых двух месяцев, а январь подправим потом.
Чтобы убрать 2 дня, сделаем в формуле не 30 дней, а 28:
f = 28 + …
Но тогда это повлияет на все месяцы! Добавим по 2 дня к каждому месяцу начиная с марта. Для этого используем выражение 2 % х
, оно берёт остаток от деления на икс.
Для января и февраля этот остаток будет равен нулю:
2 % 1 = 0 [2 = (1 × 2) + 0]
2 % 2 = 0 [2 = (2 × 1) + 0]
А для всех остальных остаток будет равен двум, например, для июля:
2 % 7 = 2 [2 = (7 × 0) + 2]
Запишем все значения 2 % х
в таблицу:
Да, действительно, к первым двум месяцам ничего не прибавляется, а остальные получают нужные два дня. Запишем это в формулу и проверим:
f = 28 + (x + x / 8) % 2 + (2 % x)
Финал: добавляем январь
Осталось решить последнюю проблему: прибавить два дня только к январю. Скажем это на языке формул: нам нужно прибавить два дня к месяцу, номер которого равен единице. Если использовать то, что мы уже знаем, то единицу получить очень просто: 1 \ х
.
Если икс равен единице, то наша целая часть от деления тоже равна единице:
1 \ 1 = 1
А для всех остальных иксов целая часть будет равна нулю:
1 \ 2 = 0
1 \ 9 = 0
Чтобы прибавить не один, а два месяца, умножим это на 2: (1 \ х) × 2
. Теперь подставим в формулу:
f = 28 + (x + x \ 8) % 2 + 2 % x + (1 \ x) × 2
Сработало!
Чтобы перевести эту формулу на язык JavaScript, нужно знать следующее:
- целочисленного деления в JS нет, поэтому вместо него используют простое деление и отбрасывание дробной части. Поэтому
7 \ 2
в JS превращается вMath.floor(7/2);
- остаток от деления в JavaScript есть, и он обозначается теми же процентами:
7 % 2
.
Зная это, легко написать функцию на JavaScript, которая по номеру месяца выдаст количество дней в нём. Скопируйте этот код и запустите его в консоли браузера:
function f(x) {
return 28 + (x + Math.floor(x/8)) % 2 + 2 % x + Math.floor(1/x) * 2;
}
var x = Number(prompt('Введите номер месяца'));
alert('Дней в этом месяце: ' + f(x) );
Почему лучше составить справочник с числом дней?
С точки зрения быстродействий программы гораздо разумнее составить справочник числа дней, например так:
function getDaysInMonth(monthNumber){
var DaysInMonth = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
return DaysInMonth[monthNumber];
}
Ноль в начале — потому что нулевого месяца в году не бывает и их принято считать всё-таки с единицы :-)
Справочник в нашем случае — намного более элегантное и экономное решение, потому что поиск числа дней по номеру месяца в таком справочнике требует очень небольшого числа действий. А наша замороченная формула требует безумного числа вычислений ради какой-то очень простой задачи. Оно того не стоит.
Зато мы смогли.