Два программиста и календарь
hard

Два программиста и календарь

Как узнать количество дней в месяце только по его номеру?

Java — это не будущее, это уже настоящее
Изучите программирование на Java.
Начать бесплатно
Java — это не будущее, это уже настоящее

Два программиста снова спорят о работе и 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];
}

Ноль в начале — потому что нулевого месяца в году не бывает и их принято считать всё-таки с единицы :-)

Справочник в нашем случае — намного более элегантное и экономное решение, потому что поиск числа дней по номеру месяца в таком справочнике требует очень небольшого числа действий. А наша замороченная формула требует безумного числа вычислений ради какой-то очень простой задачи. Оно того не стоит.

Зато мы смогли.

Текст:

Михаил Полянин

Редактура:

Максим Ильяхов

Художник:

Даня Берковский

Корректор:

Ирина Михеева

Вёрстка:

Мария Дронова

Соцсети:

Олег Вешкурцев

Java — это не будущее, это уже настоящее
Изучите программирование на Java и станьте тем, кто поддерживает современную ИТ-инфраструктуру во всём мире
Начать бесплатно
Java — это не будущее, это уже настоящее Java — это не будущее, это уже настоящее Java — это не будущее, это уже настоящее Java — это не будущее, это уже настоящее
Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
Инженерная задачка для программистов

Как запрограммировать датчик для вращающегося диска?

medium
Задача на логику
Задача про сторожа и фонарик

Ваша любимая задача на перебор и логику.

easy
Задача про бейсбольную биту

Эта задача решается не так просто, как кажется.

easy
Сложная логическая задача про сумму перестановок
Сложная логическая задача про сумму перестановок

Как сложить 120 чисел за пару минут

medium
Задача про команду программистов, тимлида и таск-трекер
Задача про команду программистов, тимлида и таск-трекер

Описание простое, а задачка — нет

easy
Задача на импортозамещение
Задача на импортозамещение

Немного логики, капля математики и много любви к российским фермерам.

medium
Задача про секс и математику
Задача про секс и математику

Могут ли логика, математика и статистика привести к созданию новой крепкой семьи?

medium
Как поделить премии?
Как поделить премии?

Помогите начальнику отдела.

easy
Странная задача про выполнение задач и продуктивность
Странная задача про выполнение задач и продуктивность

Сколько успевает делать помощник?

easy
hard