Как стать автором
Обновить

Пять задач, которые приходится решать при трудоустройстве начинающим Java-разработчикам в 2022 году

Время на прочтение6 мин
Количество просмотров41K
Всего голосов 25: ↑20 и ↓5+15
Комментарии47

Комментарии 47

Верным ответом на такую задачу будет запрос:

Зачем JOIN, если d.id IS NULL? 😵‍💫 Так себе верность

Тоже не понял зачем тут JOIN

не привязанных к отделам

достаточно запроса к таблице employee: ... Where department_id is null and ...

Подозреваю, что смысл тут в возможном отсутствии в department записи соответсвующей employee.department_id.
Отдел пропал, а сотрудник остался )

Тогда exists(), но вообще такого не должна допускать сама СУБД, т.к. внешний ключ. А если ключа нет, то проектировщик схемы даже не джун.

Вероятно тут попытка определить существует ли в таблице отделов отдел с идентификатором, который храниться в поле department_id таблицы сотрудников.

А в этом поле может быть или NULL, или 0, или ещё какое-то значение, которые показывает что у сотрудника нет отдела.

Поэтому самое надёжное чекнуть таблицу отделов.

Выше уже написали. Внешний ключ не даст вставить id несуществующего отдела.

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

Критикуя - предлагай. Как еще найти не привязанных к департаментам? e.id not in (select id from department)? Сомнительное решение..

  1. Проверка e.id not in (select id from department) не просто "сомнительна" она в принципе не корректна, так как select id from department выберет идентификаторы департаментов, тогда как e.id это идентификаторы сотрудников и они не связаны между собой и в том числе могут и пересекаться

  2. В комментариях уже предложили решение: e.department_id is null. Правда оно рассчитано на то что структура БД корректна и e.department_id имеет FK на таблицу department

  1. Да я ошибся не e.id, а e.department_id .

  2. А если department_id  = 0?

Если в БД существует нормальный FK, то в e.department_id не будет ерунды, в том числе нуля и других значений которых нет в таблице department.
Если же FK нет (что как раз и позволяет хранить в e.department_id значения навроде 0) тогда JOIN будет верятно всё же более адекватным вариантом чем проверки на уровне записей.

Я считаю что изначально нужно рассчитывать на FK, если другое не сказано явно и уже после уточнения вносить правки в запрос на основе новых сведений, а додумывать что-то дело не благодарное - иначе можно дойти до того что "не привязанных к отделам" можно интепретировать как "работник может быть приязан к нескольким отделам", а значит в department_id будет лежать список id отделов и это нужно учитывать.

P.S.
И это тоже обсуждалось.

Полностью согласен! :)
На верный ответ это точно не тянет :)

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

Это пегий дудочник)) Странно, что вы не в курсе. Сериал-то прикольный

Не смотрел сериал, надо будет исправить это упущение.

Но вторая задача же по объёму больше всех остальных вместе взятых.

В этом случае её следует вынести в отдельное design-interview (ну или в отдельную дизайн-сессию интервью).

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

А зачем в третьей задаче джойнить department, поясните?

Предложу такое решение 1й задачи.

import java.util.List;
import java.util.stream.IntStream;

public class Main {

    private static final int MIN = 0;
    private static final int MAX = 1000;
    private static final int GOOD_DIV = 3;
    private static final int BAD_DIV = 5;
    private static final int DIGIT_SUM_CONDITION = 10;


    public static void main(String[] args) {

        List<Integer> numList = IntStream.range(MIN, MAX).boxed()
            .filter(i -> (i % GOOD_DIV == 0 && i % BAD_DIV != 0 && checkSumDigits(i)))
            .toList();
        System.out.println(numList);
    }

    private static boolean checkSumDigits(int num) {
        int val = 0;
        while (num > 0) {
            val += num % 10;
            num = num / 10;
            if (val >= DIGIT_SUM_CONDITION) {
                return false;
            }
        }
        return true;
    }
}

Чтобы вывести числа, кратные 3, цикл должен быть с шагом 3, а тут, судя по первому условию .filter (в Java я не силён, поправьте, если не так) шаг цикла 1. Вместо деления вторым условием можно смотреть последнюю цифру числа, раз уж вы её потом всё равно вычисляете.

Чтобы избавиться от "дорогих" if-ов, можно сделать массив [3,6,9,12] и в цикле прибавлять его к 15*Х

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

import static java.lang.System.out;

public class Main {
    public static void main(String[] args) {
        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);
            }
        }
        out.print(1000);
    }
}

Результат такой же, но гораздо проще для понимания и поддержки, потому что программист оперирует 6-ю словами всего (for, int, i, if, out, print)

вместо 16-ти (List, Integer, numList, IntStream, range, MIN, MAX, boxed, filter, i, GOOD_DIV, BAD_DIV, toList, System, out, print)

Данное решение

  • в дальнейшем нужно будет поддерживать

  • Sonar всё равно возмутится хардкоду значений в коде - и нужно будет выносить константы

  • развёрнутое вычисление суммы цифр это быстро, но работает только для 1000 - если максимум увеличится в размере всего на пару цифр, то развёрнутая формула вычисления суммы станет совсем не читаемой

  • 1000 не делится на 3 и должно отсутствовать в выводе, а оно присутствует

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

Вы придумали несуществующие требования

  • Это решение ненадо поддерживать, это просто тестовое задание на собеседовании. Его никто не будет ревьюить, его выкинут сразу же после окончания собеседования

  • Никто не будет запускать Sonar на этом коде

  • Решение работает для конкретного заданного случая и заданного диапазона, естественно не является универсальным

  • Да, с 1000 я облажался :-(. Тут согласен.

Вы придумали несуществующие требования

Выдержка из постановки к задаче 1:

Работодателю здесь важно понять, как соискатель владеет основами синтаксиса языка и может ли писать код сразу чисто.
С помощью задач такого типа мы проверяем уровень соискателя: как много он писал код сам, а также косвенно — сталкивался ли с «грязным» кодом и рефакторил ли его. По такому заданию можно косвенно увидеть, как быстро и «чисто» (понятно и поддерживаемо) специалист будет писать собственный код.

Из этого следует что код должен быть достаточно "опрятным" и "поддерживаемым".
Да, Sonar тут не будет прогоняться и код, действительно, будет "выброшен", но именно по этому коду идёт оценка того что кандидат будет "творить" в дальнейшем - можно писать "лишь бы работало", но это явно не покажет кандидата с лучшей стороны.

Да и вы сами пишете:

Результат такой же, но гораздо проще для понимания и поддержки

, хотя проверка суммы цифр явно таковой не является даже в текущем варианте (суммирующем всего 3 позиции) и только ухудшится при изменении условий (что часто происходит с таким кодом на собесах - типа "а что будет если диапазон будет не до 1000, а до 20000?" - см. упоминание рефакторинга).

Решение работает для конкретного заданного случая и заданного диапазона, естественно не является универсальным

Вычисление суммы цифр легко превращается в функцию, которая

  1. работает для любых значений

  2. повышает читаемость и поддерживаемость

  3. не снижает производительности

  4. повышает тестируемость и модульность

Во 2-м примере метод returnNumber вообще не вызывается

Во втором примере вы забыли код дописать

public class FizzBuz1 {
    public static void main(String[] args) {
        for (int i = 3; i <= 1000; i += 3) {
            if (i % 5 != 0 && check(i)) {
                System.out.println(i);
            }
        }
    }

    private static boolean check(int i) {
        int r = 0;
        while (i > 0 && r < 10) {
            r += i % 10;
            i = i / 10;
        }
        return r < 10;
    }
}

В первой задаче не проверяется условие "сумма цифр меньше десяти"

написать код, который выводит числа от 0 до 1000, которые делятся на 3, но не делятся на 5, и сумма цифр в которых меньше десяти

все 3 условия проверяются

один товарищ изложил свою версию, чем джун отличается от мидла. первый напишет fizzbuzz через if-ы или case. а опытный кодер знает, что проект может меняться. и вместо фиксированного количества множителей мы напишем рекурсивную функцию, которая принимает от нуля аргументов и далее. поэтому шаг в цикле точно будет 1. пишу по памяти, сам не советчик здесь ) Кажется, смысл в том, чтоб программа была легко изменяемой через классы и интерфейсы.

После знакомства с ФП понимаешь, что здесь лучше всего вернуть filter (map(list)).
И потом делай с ним всё что хочешь.

Чет какие-то сложные решения в комментариях. Предлагаю следующее:

        IntStream.range(0, 1000)
            .filter(
                ((IntPredicate) e -> (e % 3) == 0)
                    .and(e -> (e % 5) != 0)
                    .and( e -> {
                        int sd =
                        Integer.valueOf(e)
                            .toString()
                            .chars()
                            .map(e1 -> Character.getNumericValue(e1))
                            .reduce(Integer::sum)
                            .orElse(0);
                        return sd < 10;
                    })
            )
            .forEach(System.out::println);

Предложу свой вариант первой задачи (если не создавать переменные по условиям задачи):

public class Task01 { 
/* 
* написать код, который выводит числа от 0 до 1000, которые делятся на 3, но не 
* делятся на 5, и сумма цифр в которых меньше десяти 
*/
public static void main(String[] args) {
	for (int i = 0; i < 1000; i++) {
		if (i % 3 == 0 & i % 5 != 0 & sumFigure(i) < 10) {
			System.out.println(i);
		}
	}
}

static int sumFigure(int num) {
	return num % 10 + num / 10 + num / 100 + num / 1000;
}

}

assertThat(sumFigure(102)).isEqualTo(3);

Код невалидный, падает на первом же трёхзначном числе, делящемся на 3.

public class main {
    public static void main(String args[]) {

        for (int i=0; i<=1000; i++)
        {
                int a=i;
                int b=0;

                while(a > 0)
                {
                    a=a%10;
                    a=a/10;
                    b=b+a;
                }
              /*  System.out.println(z);*/
            if ((b < 10) & (i % 3 == 0) & (i % 5 !=0))
                {
                System.out.println(i);
                }
            }
        }
    }

Решение первой задачи

package org.example.test2;

public class Test2 {

    public static void main(String[] args) {
        int j;
        int sum = 0;

        for (int i = 0; i < 1000; i++) {

            if ((i % 3 == 0) && (i % 5 != 0)) {
                j = i;

                while (j > 0) {
                    sum += j % 10;
                    j /= 10;
                }
                
                if (sum < 10) {
                    System.out.println(i);
                }
                
                sum = 0;
            }
        }
    }
}

Почему все циклы i++? Позабыли i+=3?
Чтобы экономить деления, цикл +=3 и вложенный в цикл счётчик 1..5.
Или даже лучше цикл от 3 с шагом 15 и внутри вывод (на проверку) чисел i+0, +3, +6, +9, +12.
Ну или от 1 и проверка чисел i+2, +5,…

Вариант с i+3 уже предложили.
А по остальным предложениям: напишите код, а не мысли вслух - давайте рассмотрим.

    public static void main(String[] args) {
        int[] list = {3, 6, 9, 12};
        for (int i = 0; i < 1000; i += 15) {
            for (int j = 0; j < 4; j++) {
                int curr = i + list[j];
                if (curr / 100 + curr / 10 % 10 + curr % 10 < 10) {
                    System.out.println(curr);
                }
            }
        }
    }

И даже проверка «curr <= 1000» лишняя, так как 10nn/100 =10 и отсечётся здесь

Я накидал бенчмарк по вариантам предложенным в комментариях (плюс мои) и получил вот такие данные:

| Benchmark | Mode  | Cnt | Score ± Error        | Units | vs `naive` | vs `simple` |
|-----------|-------|-----|----------------------|-------|------------|-------------|
| naive     | thrpt | 10  | 53348,090 ± 4429,824 | ops/s | -------    | -27.110%    |
| simple    | thrpt | 10  | 73190,139 ± 5990,700 | ops/s | +37.19%    | -------     |
| pin2t     | thrpt | 10  | 77951,880 ± 6964,085 | ops/s | +46.12%    | +06.51%     |
| igolikov  | thrpt | 10  | 79527,875 ± 5324,465 | ops/s | +49.07%    | +08.66%     |
| rombell   | thrpt | 10  | 83608,345 ± 3828,489 | ops/s | +56.72%    | +14.23%     |

Что интерпретировать можно так:

  • ваш, @rombell, вариант самый быстрый, но корректно работает строго в диапазоне до 1000 (так как проверяет только 3 знака) и при сумме цифр именно 10

  • потом идёт вариант от @igolikov, за счёт отсутствия проверки делимости на 3 и внесения проверки лимита суммы цифр в цикл подсчёта этой суммы

  • потом идёт вариант от @pin2t, за счёт проверки суммы только трёх знаков (из-за чего и работает корректно только на диапазоне до 1000)

  • и потом мой вариант

Плюс 14% это весьма хороший показатель.
А вот с учётом поддерживаемости, расширяемости и тестируемости побеждает вариант от @igolikov - он и работает на любых диапазонах и делает это довольно быстро (ваш быстрее всего на 5%) и доступен для понимания.

Мой вариант решал конкретную задачу. KISS/YAGNI. Будет задача в более общем виде — будем и решать в общем виде. С учётом допустимости не-десятичной системы счисления, которую здесь даже более общие решения хардкодят.
Кстати, есть ещё небольшие улучшения, например, добавить проверку <=900 и сэкономить несколько относительно дорогих операций за счёт одного сравнения. И вырезать промежуток 811..899. И 721..799. Даст ещё некоторый прирост. Можно даже предварительно насчитать границы, пользуясь нашими знаниями и десятичной системе счисления.
Решая конкретную задачу, разумно использовать знания об этой задаче.

Мой вариант решал конкретную задачу.

Исходная постановка звучит примерно так: Решите задачу X с помощью опрятного и поддерживаемого кода. По результирующему коду будет оцениваться то как будет писаться код для боевых задач.

Решая конкретную задачу, разумно использовать знания об этой задаче.

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

Думаю, тут мы расходимся во мнениях.
Где границы поддерживаемости? Обобщить задачу — это одно, а полностью изменить — совсем другое. Мой код вполне прозрачен и поддерживаем, изменение предельной суммы цифр либо границ делается в одной точке. Изменение пропускаемых делителей — замена шага цикла либо массива.
Что до оптимизации, так преждевременной оптимизацией было бы деление на промежутки, в предлагаемом решении нет никаких сложностей, только использование знаний о конкретной задаче.
Ещё раз обращаю внимание, все решения используют десятичную систему, не пытаясь обобщить на остальные системы счисления (в задаче, кстати, б хтом не было ни слова). Не-использование знаний о задаче — неправильно. Потом у нас для простейших действий получаются мегабайтный код. Зато фреймворк на фреймворке.
В общем, конечно, свой код лучше судить года через полтора, но пока что он мне кажется поддерживаемым не хуже прочих, и уж точно проще в анализе, чем циклы вычисления суммы цифр.
На этом предлагаю и разойтись ))
IntStream.range(1, 1000)
        .boxed()
        .filter(i -> i % 3 == 0 && i % 5 != 0 && String.valueOf(i).length() > 1
                && String.valueOf(i).chars().mapToObj(c -> Integer.valueOf(""+(char)c)).reduce(0, Integer::sum) < 10)
        .collect(Collectors.toList());

поганое задание. Не на знание алгоритмики или логики, а на зубрение методов работы со Strings. Не ходите к таким на собеседования.

  1. А причём тут "методы работы со String"? Вычисление "суммы цифр" числа через преобразование числа в строку и получение потока символов используется только у вас и у @DenisPantushev тогда как есть более "алгоритмический" вариант без, так сказать "зубрения".

  2. Из-за проверки String.valueOf(i).length() > 1 ваш код упускает часть корректных значений.

  3. Вместо String.valueOf(i).chars().mapToObj(c -> Integer.valueOf(""+(char)c)).reduce(0, Integer::sum) можно использовать гораздо более короткий, понятный и эффективный вариант: String.valueOf(i).chars().map(Character::getNumericValue).sum().

  4. А зачем IntStream боксируется в Stream<Integer>?

Всё это у вас бы спросили на собесе.

for (int i = 0; i <= 1000; i++) {
   String s = i + "";
   char[] charArr = s.toCharArray();
   int sum = 0;
   for (char c: charArr) {
       sum += Integer.parseInt(String.valueOf(c));
   }
   if (sum < 10 && i % 3 == 0 && i % 5 != 0) {
       System.out.println(i);
   }
}

Зарегистрируйтесь на Хабре, чтобы оставить комментарий