Задачки с собеседований JS, PHP, Rust, Python, Go…

Есть всего 2 типа языков программирования: те, на которые люди все время ругаются, и те, на которых никто не пишет.

Bjarne Stroustrup


Есть такая задача: определить является ли год високосным. Причем эта задача может встретиться как в работе, так и на собеседовании. На собеседовании она может быть у кого угодно. У человека пишущего на JS, PHP, Python, Ruby, Swift, Go, etc…

Конечно ответ правильный знает не каждый и это нормально. Нормально не все знать. Всезнайка наоборот вызывает подозрение. Обычно смотрят на рассуждения. Но если вы сталкивались в работе с такой задачей, то она явно будет у вас в голове и вы можете рассказать что знаете решение, потому, что… И показать его.

Решение

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

$ npm install --save leap-year
$ vim leapyear.js
const leapYear = require('leap-year');
leapYear(2014); //=> false
leapYear(2016); //=> true

Вот в данном случае такой ответ вам сыграет в минус, а не в плюс. Это не нативный метод, это отдельный npm пакет, решающий одну маленькую задачу (наподобие лефтпада). Буду называть такие пакеты LeftPad Like пакеты.

Зная как решается задача, я просто категорически против использования пакетов подобного рода. Ну это же просто нонсенс. В составе какой-то либы для работы с датами — это ок, это еще куда ни шло. Но отдельно! Серьезно?

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

Ну ок, пусть вы пользуетесь библиотекой. А покажите решение без библиотек, может прозвучать следующий вопрос.

Ликбез

В(и|ы)сокосный год— год в юлианском и григорианском календарях, продолжительность которого равна 366 дням — это на одни сутки больше продолжительности обычного, невисокосного года. В юлианском календаре високосным годом является каждый 4й год. В григорианском календаре из этого правила есть исключения.

Мы живем по григорианскому календарю (напомню на всякий случай). Високосный год по григорианскому календарю кратен 4, но при этом не кратен 100 либо кратен 400.

Python

Если вы питонист, то говорите, что воспользуетесь библиотекой:

import calendar
print(calendar.isleap("2018"))

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

def is_leap_year(year):
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

year = 2016
print(is_leap_year(year)) # true

year = 2018
print(is_leap_year(year)) # false

PHP

В PHP есть несколько способов. Пройдемся по порядку. Если знаете алгоритм, то решение ничем не отличается от вышеизложенного. В 2018 году нормальные посоны пишут затипизированный код на PHP7:

<?php declare(strict_types=1);
function is_leap_year(int $year) :bool
{
return $year%4 == 0 and (0 != $year%100 or $year%400 == 0);
}

Вы показали умение решить задачу алгоритмически — круто! Но от вас так же ждут ответы с использованием стандартных методов. Например такой:

<?php declare(strict_types=1);
function is_leap_year(int $year) :bool
{
return (bool) date('L', mktime(0, 0, 0, 1, 1, $year));
}

В данном случае функция date с параметром “L” будет возвращать “1” в случае високосного года и “0” во всех остальных случаях. Причем тип будет string. Скорее всего в работе вы этим способом и пользовались. Еще вариант с использованием встроенного класса DateTime:

function is_leap_year(int $year) :bool
{
return (bool) DateTime::createFromFormat('Y', (string) $year)
->format('L');
}

JavaScript

Я не буду повторять код с проверкой через кратность, он такой же как и в PHP. Я покажу интересные нестандартные варианты, которые так же имеют право на жизнь:

function isLeapYear(year) {
return new Date(year, 1, 29).getDate() === 29
}

Поиграем с FP стилем. Elm style, все дела.В функциональном стиле это пишется как-то так (да простят меня одепты ФП, если я тут не учел каких-то парадигм):

const multOf = (n, x) => x % n == 0;
const multOf4 = x => multOf(4, x);
const multOf100 = x => multOf(100, x);
const multOf400 = x => multOf(400, x);
const isLeapYear = y => multOf4(y) && !multOf100(y) || multOf400(y);

Этот пример мы можем “улучшить” заменив часть

multOf4(y) && !multOf100(y)

на XOR:

const isLeapYear = y =>
Boolean(
multOf4(y) ^ multOf100(y) || multOf400(y)
)
;

Операция XOR возвращает 1 если левая и правя части различны. Иначе вернет 0.

Этот же пример с XOR можем применить к первоначальному алгоритму, тогда получится как-то так:

const isLeapYear = y => !!(!!(y%4)^(!!(y%1e2)||!!(y%4e2)));

Но это уже баловство и просто неэффективно. Но выглядит забавно.

И немного магии. По сути это вариант с кратностью, но переписанный с использованием битовых операторов:

const isLeapYear = y => !(y&3||y&15&&!(y%25));

Ух, красота. Но читается сложно. Это вариант для общего развития и чтобы коллег удивить. Если сможете показать экзаменатору такое решение — есть вероятность что он будет в шоке =)

На практике лучше не использовать того, в чем не разбираетесь. Если владеете битовыми операторами и можете объяснить как это работает — отлично. Если нет — лучше не стоит зубрить, не оценят.

Как всегда с JS получились примеры круче =)

Rust

А так можно записать на Rust, ничего необычного:

fn is_leap_year(year: i32) -> bool {
year%4 == 0 && (0 != year%100 || year%400 == 0)
}
fn check_leap(year: i32) {
let answ = is_leap_year(year);
println!("{} is leap year? Answer: {}", year, answ);
}
fn main() {
let mut year = 2016;
check_leap(year);

year = 2018;
check_leap(year);
}

Почему показал пример на Rust? Потому, что в 2018 году, когда браузеры позволяют использовать WASM, Rust это наиболее популярный язык для компиляции в WebAssembly. И пора его учить всем тем, кто претендует на звание сеньора помидора веб-разработки с хорошей ЗП.

А еще сам Rust интересен своим поведением. В данном примере я показал его интересную особенность. Rust — это язык, ориентированный на выражения, а смысл точки с запятой отличается от смысла аналогичного символа в других языках с синтаксисом на основе фигурных скобок и точки с запятой. Эти две особенности связаны. Уточню про что говорю примером:

fn is_leap_year(year: i32) -> bool {
year%4 == 0 && (0 != year%100 || year%400 == 0)
}
// или
fn is_leap_year(year: i32) -> bool {
return year%4 == 0 && (0 != year%100 || year%400 == 0) ;
}

И неважно на чем вы сейчас пишите. Если вы фулстек разработчик, Rust может вам пригодиться.

Golang

Пример на этом языке покажу по двум причинам. Во-первых Go стал альтернативным языком для веб-разработки для бекендеров, кто раньше писал или до сих пор пишет на PHP, Python, Ruby, Nodejs. У нас даже есть вакансии в New.HR где все эти языки пересекаются в той или иной степени. Но не суть.

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

package main
import (
"fmt"
"time"
)
func IsLeapYear(y int) bool {
year := time.Date(y, time.December, 31, 0, 0, 0, 0, time.Local)
days := year.YearDay()
   if days > 365 {
return true
} else {
return false
}
}
func main() {
fmt.Println("2016 is leap year? ", IsLeapYear(2016)) // true
fmt.Println("2018 is leap year? ", IsLeapYear(2018)) // false
}

Дисклеймер

Напомню, что я присутствую на множестве технических собеседований совершенно разных специалистов, пишущих на разных языках. Не все задачи, что я описываю — я спрашиваю, если собеседование провожу я. Я делюсь своим опытом. И вот мой личный опыт показывает, что есть пул задач которые одинаковы для всех отраслей IT: от фронтенда и мобильщиков до… До кого угодно.

Что касается задач: любая задача может иметь право быть спрошенной. Вопрос в том, что собеседующий проверяет конкретной задачей, на что смотрит и как реагирует на ответы. А еще надо понимать кому дается задача. Разработчиков уровня сеньор нужно тестировать другими вопросами. Такие задачи хороши для джунов, максимум мидлов.

В данном случае знать как вычисляется високосный год — не обязательный параметр. Но порассуждать на эту тему важно. Как минимум дать вариант ответа, где просто проверять кратность 4м — к этому ответу можно просто придти логически. В итоге нужно рассказать принцип вычисления и дать написать алгоритм, чтобы увидеть каким способом и как человек будет это решать. Умеет ли кандидат работать с рациональными числами (всякое бывает).

Есть интересные варианты?

Поделитесь в комментариях. 🙂

Откуда взялся високосный год