Два программиста и календарь
Недетская задача про детей Реально сложная задачка Задача про Катю и двух программистов Странные программисты говорят про время Задача про программистов и подбор пароля Спор двух программистов
Два программиста и календарь

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

Чтобы перевести эту формулу на язык 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];
}

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

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

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

Текст:

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

Редак­ту­ра:

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

Худож­ник:

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

Кор­рек­тор:

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

Вёрст­ка:

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

Соц­се­ти:

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