Наверняка многие знают компанию Shopify — ее сеть достаточно большая и по состоянию на вчера позиционировался компанией № 8 в сети LinkedIn (по рейтингу работодателей, если не ошибаюсь).

В один прекрасный день я увидел у них позицию Staff Software Engineer в Оттаве, и памятуя, что ребята работают, в том числе и с большими данными, а соответственно и машинным обучением, по которому я, откровенно говоря, скучаю после работы в IBM, я подался на эту позицию.

Интервью достаточно стандартное — сначала 15 минутный звонок от рекрутера удостовериться живой я или нет, потом часовой рассказ «за жизнь» с другим рекрутером и потом краткая задача минут на 20, которая является своеобразным барьером, отсеивающих ну уж совсем неразумных наверное.

К слову сказать, ровно год назад, в мае 2019 года, я также интервьюировался у них, прошел все вышеуказанные этапы, но получил более вкусный оффер от другой компании, которая находится в 5 километрах от дома, в то время как в офис Shopify надо было ехать в даунтаун, а это около часа, да и парковки там не их дешевых. И именно потому, что в прошлом году мне понравился как процесс, так и адекватность сотрудников — я решил податься снова.

Но в какой-то момент что-то пошло не так.

Началось все с того, что задача, которую мне предложили решить, звучала следующим образом:
Покупатель приходит в магазин чтобы купить продукты. У него есть корзина, куда он может что-то положить, а магазин хочет дополнительно простимулировать покупателя тем, что предоставляет ему дополнительные скидки на товар:

1. Если покупатель купит больше одного яблока, то он получит скидку 20% на все яблоки в корзине;
2. Если покупатель купит пакет винограда, то второй пакет он получит бесплатно.

Итог: посчитать стоимость корзины покупателя при выходе из магазина, при то, что данные даны в следующем виде:

[["grapes", 1],["apples", 0],["peaches", 1]] => 12
[["grapes", 1],["apples", 1],["peaches", 1]] => 15
[["grapes", 1],["apples", 2],["peaches", 1]] => 16.8
...
[["grapes", 3],["apples", 1],["peaches", 1]] => 20

где первый элемент вложенного массива — это наименование продукта, а второй элемент — это количество продукта данного типа в корзине.

Цены на продукты следующие:
— яблоко ($3)
— виноград ($5)
— персики ($7 )

Я сказал ОК, интервьюер спросил какой язык я предпочту, на что я ответил «Java» и начал оценивать задачу.

Мой первый вопрос звучал невинно:
Какую структуру данных мне ожидать в качестве входа?

Ответ был еще более простым:
Arrays (массивы). Создай дополнительный метод и опиши там данные вручную.

Хорошо, подумал я, раз мы не ищем легких путей, а также учитывая, что интервью все-таки на позицию Staff Engineer, то от меня скорее всего хотят увидеть реализацию с использованием абстракции и дженериков, что-то вроде этого:
class Values<T> {
   T data;
   Values() {}
   Values(T data) {this.setData(data);}
   public void setData(T data) {this.data = data;}
   public T getData() {return this.data;}
}

и их использования:
public class Main {
   public static void main(String[] args) {
      Values[][] item = new Values[3][2];
      item[0][0] = new Values("grapes");
      item[0][1] = new Values(1);

      item[1][0] = new Values("apples");
      item[1][1] = new Values(1);

      item[2][0] = new Values("peaches");
      item[2][1] = new Values(1);
   }
}

а поскольку мы находились на расшаренном ресурсе, то я задал невинный вопрос, а как создать дополнительный класс, поскольку использовать Static сильно не хотелось, да и вроде как в данном случае не полезно.

Я немного удивился, когда услышал ответ навроде — «А зачем? Просто используй массивы и все, зачем все усложнять?». Я специально еще раз переспросил насчет массивов — точно массивы? Ответ был неизменным.

Хорошо, массивы, так массивы, пусть и со статическими классами тогда уж, поскольку начался азарт — задачка оказалась не такая уж и простая, как казалась на первый взгляд.

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

Держитесь дамы и господа, ибо ответ был классный:
Размер может варьироваться, но не меньше 3 элементов.

Простите? Я конечно могу создать массив с данными динамически, где в качестве длины массива будет параметризированная переменная, но я же должен знать сколько объектов я должен буду вставить в массив — ведь единожды создав массив, я не могу изменить его длину. Поэтому тут уж мне пришлось буквально объяснить интервьюеру, как создаются массивы внутри JVM, почему их размер фиксирован, и почему их длину невозможно переопределить после создания — только создание нового и копирование данных.

В этот момент, когда я объяснял основы «массивостроения», я упомянул List интерфейс и сказал, что вот например ArrayList, как одна из имплементаций интерфейса, хоть и основана на массивах внутри, но позволяет иметь динамический размер.

Интервьюер немного призадумался и сказал — «Отлично, тогда используй ArrayList и вставляй туда данные». К слову сказать, прошло около 15 минут, пока мы все это выясняли и я объяснял ему про массивы (всего давался час). Я спросил как вставлять-то, дженерики используем уже или нет — пока еще ничего не написано, но если и дальше будем идти такими темпами — то можно и не успеть.

Интервьюер выдал очередной перл, после которого я понял, что дело труба. Он просто написал на общей доске следующее:
«grapes» => 1st element
1 => 2nd element

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

Его наивность меня поразила — «А разве нельзя вставить строковые и числовые данные в лист?». Я говорю, да можно конечно, но либо с абстракцией и дженериками, либо Object и приведение типов, либо не в Java, а например в JavaScript. Потом я уже сам не вытерпел и предложил ну давай мы хотя бы цифры в строковые переменные преобразуем — все меньше работы, уж если ты настаиваешь на ArrayList, но продолжаю дальше — может все-таки можно использовать интерфейс Map?

Интервьюер, хотя и не сразу (не стал я выяснять причины, честно сказать — времени оставалось около 25 минут), согласился и сказал — давай на Map. Я потратил минут 10, чтобы накидать скелет кода, проверил граничные условия и сказал что закончил.

Оказалось не тут-то было и все самое интересное было впереди. Помните скидки? Процитирую условие еще раз:
If customer buys one grapes he gets another one for free. (Если покупатель купит один пакет винограда, то второй он получит бесплатно).

В данном случае можно предположить, что имея данные в формате:
["grapes", 2]

в количестве данного продукта могут быть учтены и бесплатные пакеты винограда. Тогда картина должна быть следующей:

1. Покупатель купил 1 пакет винограда и получил 1 бесплатно. Итог — в корзине 2 пакета.
2. Покупатель купил 2 пакета винограда и получил 2 бесплатно. Итог — в корзине 4 пакета.
3. Покупатель купил 3 пакета винограда и получил 3 бесплатно. Итог — в корзине 6 пакетов.

Заметили паттерн? В корзине винограда должно быть всегда только четное количество. Однако исходя из начальных данных, в корзине также бывает и нечетное количество виограда:
[["grapes", 1],["apples", 0],["peaches", 1]] => 12
[["grapes", 1],["apples", 1],["peaches", 1]] => 15
[["grapes", 1],["apples", 2],["peaches", 1]] => 16.8
...
[["grapes", 3],["apples", 1],["peaches", 1]] => 20


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

А теперь внимание — правильный ответ!
1 оплаченный пакет + 1 бесплатный + 1 оплаченный

Это феерично! Как? Ну как же так может быть? Да, я не знаю все, да у меня могут быть пробелы, но объясните же мне — как такое может быть, исходя из условия задачи!? Я попытался ему что-то объяснить, но честно — просто видел пустоту непонимания того, о чем я говорю в глазах собеседника.

Все просто — перед ним цифры, которые он придумал и посчитал с одной стороны, с другой стороны — то что дал я. Мои отличаются, значит какой ответ? Мои неправильные — он же интервьюер и его решение вроде как должно быть правильным. В общем плюнул я на это дело, уже злой был от самой ситуации, так как ожидал чего-то более интеллектуального (именно поэтому и смотрю по сторонам в поисках позиции, где будут более интересные задачи), а оказалось… то что оказалось. Я решил отказаться от этой позиции, но пока писал свой фидбэк, мне пришел отказ из Shopify.

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

Вот и вся история. Берегите себя и всего вам хорошего.