Месяца два назад в комментариях вот к этой статье участники сообщества привели с десяток решений несложной задачи. Решения интересные, в чём-то красивые, но они напоминают мне сферического коня в вакууме.
Поясню. Джуниор разработчик проходит собеседование. Ему дали следующее задание.
Одна из задач, которую мы даём соискателям: написать код, который выводит числа от 0 до 1000, которые делятся на 3, но не делятся на 5, и сумма цифр в которых меньше десяти
Интервьюер хочет убедиться что:
Кандидат знает синтаксис языка программирования.
Он умеет решать алгоритмические задачи в рамках имеющихся ресурсов.
Его не придётся переучивать на стандарты использующиеся в компании.
Разработчик решает следующие проблемы:
Ему нужно выставить себя в лучшем свете
Ему нужно обойти других кандидатов
И в конечном итоге получить вакансию
Давайте попробуем максимизировать пользу для обоих участников процесса.
Решение #1 от @Filex
List<Integer> numList = IntStream.range(MIN, MAX).boxed()
.filter(i -> (i % GOOD_DIV == 0 && i % BAD_DIV != 0 && checkSumDigits(i)))
.toList();
Интервьюер:
Начинающий программист демонстрирует навыки функционального программирования.Ну ок, начитанный. Но сможет ли он писать код без ошибок в реальной жизни? ФП сравнительно новая концепция, тем более в применении к языку ООП. Вряд ли в нашем legacy это пригодится.
Кандидат:
Я вижу что интервьюер хмурится. Я быстренько говорю что это моё хобби, для роста. И накидываю более традиционное решение
Решение #2 от @pin2t
for (int i = 0; i < 1000; i++) {
if (i % 3 == 0 && i % 5 != 0 && (i / 100 + (i / 10) % 10 + i % 10) < 10) {
out.println(i);
}
}
Интервьер:
Решение вполне себе рабочее, но уж слишком примитивное. Сможет ли начинающий программист расти? Понимает ли он что всякое число в коде это потенциальная проблема?
Кандидат:
Это я вам набрасал интуитивное решение. В реальной жизни я бы так поступил когда нужно срочно сделать прототип и показать заказчику. Потом разумеется будут написаны тесты и код будет приведён в соответствии со стандартами.
Решение #3 от @igolikov
for (int i = 3; i <= 1000; i += 3) {
if (i % 5 != 0 && check(i)) {
System.out.println(i);
}
}
Интервьер:
Молодец, отсеял 2/3 заведомо неправильных решений, оптимизировать может. Но циклы в Java вполне себе оптимизированы, а вот деление операция долгая. Тут оно не нужно в принципе. Начал с тройки, но почему упустил 1000, оно же не делится на три. Кандидат думает, но не вникает в детали.
Кандидат:
Тут либо всё делаешь аккуратно, либо не лезешь в предварительную оптимизацию которая есть зло.
Ну и моё решение
for (int i = 0; i < 10; ++i)
for (int j = 0; j + i < 10; ++j)
for (int k = 0; i + j + k < 9; ++k)
if (k == 5 || k ==0)
continue;
if (i + j + k != 3 && i + j + k != 6)
continue;
print(i * 100 + j * 10 + k)
Интервьюер:
Кандидат знает, как работают процессоры. Он вникает в суть задачи. Может быть немного overqualified, но есть потенциал роста. Но сможет ли он писать поддерживаемый код?
Кандидат:
Обязательно упомянуть, что это лучшее решение с его точки зрения. Но если производительность не важна, лучше конечно написать более простое решение, которое понятно другим членам команды.
Комментарии (25)
igolikov
03.11.2022 11:18+3Начал с тройки, но почему упустил 1000, оно же не делится на три. Кандидат думает, но не вникает в детали.
Тут либо всё делаешь аккуратно, либо не лезешь в предварительную оптимизацию которая есть зло.
for (int i = 3; i <= 1000; i += 3) {
Этот цикл заканчивается на 999
Samhuawei Автор
03.11.2022 11:43Да, на месте кандидата я бы так и ответил, но тогда он скажет - а зачем тогда <= когда можно обойтись <
Случаи разные бывают, иногда компании нужны аккуратные, внимательные к деталям люди. Или те кто может обосновать то что он делает.YDR
04.11.2022 09:24+2но ведь в условии явно записана 1000? как объяснить потом, что она должна превратиться в 999? И мы ведь не экономим "=".
eleoleeye
03.11.2022 13:02+4Интервьюер:
Начинающий программист демонстрирует навыки функционального программирования.Ну ок, начитанный. Но сможет ли он писать код без ошибок в реальной жизни? ФП сравнительно новая концепция, тем более в применении к языку ООП. Вряд ли в нашем legacy это пригодится.
Кандидат:
**уходит с интерьвю**
MinimumLaw
03.11.2022 14:05+1Одна из задач, которую мы даём соискателям: написать код, который выводит числа от 0 до 1000, которые делятся на 3, но не делятся на 5, и сумма цифр в которых меньше десяти
...
Ну и моё решение
А оно точно выведет 9? 54? 117?
qw1
04.11.2022 21:40+1Да, автор заигрался в оптимизацию и посадил ошибку.
Его решение выдаёт 25 чисел, правильное решение «в лоб» — 65.
Но в принципе, ход мыслей верный, решение довольно легко исправить.MinimumLaw
05.11.2022 16:49Вопрос в том, почему не исправлено в статье. Нехорошо... Как минимум коммент под решением не помешал бы.
В целом есть замечательный признак делимости на три которых отлично ложится на условия задачи в части "сумма цифр меньше 10". Но уж больно он неявно в коде сделан. Или наоборот - намеренно явно и ошибочно.
Автора бы в студию.
valery1707
03.11.2022 14:28Обязательно упомянуть, что это лучшее решение с его точки зрения.
Наиболее производительное на данный момент. Более чем в два раза чем "простой" и вариант@igolikov.
Но если производительность не важна, лучше конечно написать более простое решение, которое понятно другим членам команды.
Производительность почти всегда важна, но не в ущерб читаемости и поддерживаемости.
Ваше же решение получилось хоть и производительным, но, на мой взгляд, практически не поддерживаемым - любое минимальное изменение параметров из задания будет требовать картинальной переработки всего кода.
И чаще всего такойwrite only
код на проекте, в действительности, не нужен.YDR
04.11.2022 09:33+1оно реально получится самым быстрым? Три вложенных цикла, условия - не константы, на один разряд числа тратится целый int, логика работы не очевидна на первый взгляд, ...
Я, правда, в Java не писец, у меня деформация от ассемблера и С, вот второе и третье решение в ассемблер хорошо ложатся. Я бы еще даже сделал
for (int i = 3; 1 <= 1000/3; i ++) { if (i % 5 != 0) { j = i*3; if (check(j)) { System.out.println(j); } } }
qw1
04.11.2022 21:50Тут вся сложность будет внутри функции check, которая должна разложить число
j
на цифры. Программист напишет это через операции div/mod 10/100, а компилятор превратит деления на 10/100 в сдвиги и умножения на «магические константы» типа 0x0CCCCCCCD, 0x66666667 и 0x51EB851F. Вроде как итераций цикла в три раза меньше, зато тело цикла раздувается. Плюс ещё проверка на кратность 5, тоже тяжелее, чем у автора. Впрочем, не исключено, что ваша версия будет быстрее.
Код автораfor (int k = 0; i + j + k < 9; ++k) if (k == 5 || k == 0) continue;
можно ещё упроститьfor (int k = 1; i + j + k < 9; ++k) if (k == 5) continue;
suburg
03.11.2022 14:54Я бы вообще для начала спросил, как будет использоваться этот код, предъявляются ли требования к производительности и насколько вероятно изменение критериев в будущем. И только после этого думал про решение.
Хотя это конечно аналитическая профдеформация уже.
Maccimo
03.11.2022 19:10+2Каждый раз, когда я вижу
continue
, рука тянется к киянке.ФП сравнительно новая концепция
Гхм, Лиспу более 60 лет, Хаскелю — более 30.
qw1
04.11.2022 21:55+1Каждый раз, когда я вижу continue, рука тянется к киянке.
И return в середине функции тоже не нравится? Мы бы с вами не сработались )))Maccimo
05.11.2022 00:01-1Возврат в середине функции иногда может быть оправдан,
continue
же — code smell в чистом виде,goto
на минималках. Его обожают начинающие ждуниоры с кашей в голове.Мы бы с вами не сработались )))
Открывающую фигурную скобку вы где ставите, заветам Кернигана и Ритчи следуете?
virtustilus
05.11.2022 02:25Думается мне continue настолько же goto, насколько while является return'ом.
Пример кода из свежей Symfony:foreach ($transitions as $transition) { if ($transition->getName() !== $transitionName) { continue; } $transitionBlockerList = $this->buildTransitionBlockerListForTransition($subject, $marking, $transition); if ($transitionBlockerList->isEmpty()) { return true; } }
Там далеко не джуниоры пишут проект, как мне кажется.
qw1
05.11.2022 17:30continue же — code smell в чистом виде, goto на минималках. Его обожают начинающие ждуниоры с кашей в голове
Как-то я себе слабо представляю, что человек писал-писал break и continue, и тут его осенило: всё, хватит! Я теперь буду создавать новый блок вложенности, чтобы всё было явно прописано.
В моей же картине мира, противники continue — это либо такие старые бабки, которые учились на turbo pascal 2.0, где не было этой конструкции, и которые ворчат «не нужны нам ваши лямбды, и continue ваш сраный нам тоже не нужон!».
Либо такие математики-теоретики, адепты Вирта, которые любой код приводят к стандартным блокам «следования, ветвления, итерации». У них всё должно быть по строгим теоретическим схемам. Если уж пишем императивный код, то никакой декларативности, и наоборот.Maccimo
05.11.2022 18:48Против
break
никто не возражал, речь шла исключительно проcontinue
.не нужны нам ваши лямбды
Если уж доводить до абсурда, то берите сразу машинное обучение и квантовые вычисления.
Если уж пишем императивный код, то никакой декларативности, и наоборот.
Где
continue
, а где декларативность, не сравнивайте тёплое с мягким.qw1
05.11.2022 19:41Против break никто не возражал, речь шла исключительно про continue.
И то, и другое — скрытый goto. Есть рациональная причина, почему одно можно, а другое нельзя?Если уж доводить до абсурда
Почему же? Лямбды — синтаксический сахар, как и continue, их можно выразить в старых конструкциях, но громоздким и избыточным кодом.
s_f1
Samhuawei Автор
Да, это ещё один пункт который можно обсудить с интервьюёром. В условии задачи написано
который выводит числа от 0 до 1000, которые делятся на 3
Способ вывода не написан, возможно это сериализация для которой важен тип данных. Но слово "числа" намекает что нужен int.