Comments 107
2012 год — РРраз habr.com/ru/sandbox/45548
2016 год — Двввааа interface31.ru/forum/index.php?topic=79.0
2019 год — Трррри! habr.com/ru/news/t/459600
Как минимум 7 лет их поливали критикой, указывая на очевидный косяк и даже этого не хватило, чтобы они признали и исправили свою ошибку.
А ведь неизвестно сколько пользователей пострадало. Представьте себе среднестатистического пользователя, который весьма бережно хранит свои данные, но по незнанию использует одинаковые пароли на различных сервисах и почте. И вот 7 лет не хватило, чтобы озон заделали дырищу, из-за которой такой пользователь оказался под серьезной угрозой.
Некоторые ВУЗы используют в своей программе задачи у которых нет решения. Это позволяет искать новые знания и придумывать новые подходы. Правда у Озона это вышло не намеренно.
Будем надеяться, что первые блины дадут Озону хорошую почву развития и успеха.
проверка задания в конце не рабочее, в итоге много людей просто в заблуждении были
С самого начала было понятно, что это такой плохо замаскированный рекрутинг. Плохо только, что в этой истории к потенциальным сотрудникам отнеслись как к дурачкам, ибо первое правило найма гласит: всегда нанимай людей умнее себя, иначе через пару лет твоя компания превратится в сборище дилетантов.
Почему "плохо замаскированный"? Наоборот — громко объявленный на главной странице проекта :)
Мне, кажется, наоборот очень круто, что компания решает ту самую проблему "курицы и яйца" для многих разработчиков — "хочу начать писать на го, но без опыта не берут".
Товарищи ex-Lazada не позорьте, а!
Надо было сразу заявлять об отборе на full time работу и, видимо, onsite. Нехорошо сделали.
Понимаю, что обидно за потраченное время и ошибки, с которыми пришлось столкнуться — без них действительно не обошлось, и в будущем мы подобного не допустим.
Принять участие в школе может любой специалист, но поскольку для нас важно пригласить разработчиков на работу по окончании курса, было ограничение по возрасту 18+ — возможно, стоило написать об этом более явно. А вот жить в Москве и области не обязательно — в школу пригласили участников и из Санкт-Петербурга, Перми, Краснодара, Пензы, Челябинска, Екатеринбурга, Уфы, Брянска. При этом, учитывая конкурс в 55 человек на место, делать решение задач единственным фактором отбора не получится, просто потому что 4 из 5 задач решили многие — по той же причине скрыли лидерборд.
И о задаче Е — в ее условиях и алгоритме проверки мы допустили сразу несколько ошибок, и чтобы наши недоработки не повлияли на результат, не учитывали решение при прохождении отбора. Возможно, стоило убрать ее раньше – но тогда оставался риск потерять полученные решения, а значит, кто-то из участников мог лишиться своих 4 из 5.
Первый отбор прошел совсем не идеально, и мы это понимаем. Мы разобрали все ошибки, том числе из этой статьи, чтобы в будущем их не повторять и сделать набор таким как нужно.
Если вы действительно представитель Озона — как насчёт прокомментировать предметно то, что освещено в статье, в т.ч. "Я не я и хата не моя"? Пока что ваш ответ выглядит как жалкая попытка оправдаться и сгладить явные косяки.
Задание было где?
пройти тестовые задания по программированию на платформе Яндекс.Контест
Виноват яндекс.
Вы о чём? Ему PR отдел не разрешит дать прямой ответ. Вот я работаю на галере <большая_company_name> — любые публичные заявление при которых я говорю что-то от имени <большая_company_name> или даже просто заявляю, что я там работаю и моё мнение такое-то — я обязан согласовать с PR отделом (не суть важно, как именно отдел наазывается). Ну а публичное признание ошибок — это то, что ни одна сколько-нибудь большая компания не допустит. Исключения из этого правила — в основном небольшие компании, либо уж ну просто очень серьёзный факап, который зарегистрирован чуть ли не гос. органами.
Нельзя ничего чинить, потому-что-
Доработать это в режиме реального времени у нас нет возможности: есть риск потерять уже существующие решения.Серьезно? 32 участника которых никто не видел, но их решение каким-то не совсем ясным образом прошло сломанные тесты. Отсюда вопрос — почему эти результаты вообще важны и должны считаться легитимными если тесты сломаны, какой вообще тогда смысл прикрываться 32-мя решениями?
Собственно что мешало исправить тесты тоже не понятно, написать что-то вроде — «да, мы тут нашли баг, наш косяк, уже исправили», аннулировать все результаты по задаче E, и попросить всех участников заново отправить свои решения на тест?
Времени было достаточно.
package main
import (
"fmt"
"time"
)
func main() {
n := 10
in1 := make(chan int)
in2 := make(chan int)
out := make(chan int)
go func(in1 chan<- int, n int) {
for i := 0; i < n; i++ {
fmt.Println("Write to in1: ", i)
in1 <- i
}
}(in1, n)
go func(in2 chan<- int, n int) {
for i := 0; i < n; i++ {
fmt.Println("Write to in2: ", i)
in2 <- i * 10
}
}(in2, n)
go func(out <-chan int) {
for i := range out {
fmt.Println("Read from Out: ", i)
}
}(out)
Merge2Channels(func(x int) int {
time.Sleep(time.Duration(x%3) * time.Second)
return x * x
}, in1, in2, out, n)
fmt.Println("Done")
fmt.Scanln()
}
type orderedNumer struct {
pos int
num int
}
//Merge2Channels merges 2 channels
func Merge2Channels(f func(int) int, in1 <-chan int, in2 <-chan int, out chan<- int, n int) {
fin1 := make(chan orderedNumer)
fin2 := make(chan orderedNumer)
go funcNum(f, in1, fin1, n)
go funcNum(f, in2, fin2, n)
go mergeFNums(fin1, fin2, out, n)
}
func funcNum(f func(int) int, in <-chan int, fin chan<- orderedNumer, n int) {
pos := 0
for i := 0; i < n; i++ {
num := <-in
// fmt.Println("Read from in: ", num)
onum := orderedNumer{pos, num}
pos++
go func(f func(int) int, onum orderedNumer, fin chan<- orderedNumer) {
onum.num = f(onum.num)
// fmt.Println("Write to fin: ", onum)
fin <- onum
}(f, onum, fin)
}
}
func mergeFNums(fin1 <-chan orderedNumer, fin2 <-chan orderedNumer, out chan<- int, n int) {
nums1 := make(map[int]int, n)
nums2 := make(map[int]int, n)
sendPos := 0
for i := 0; i < n*2; i++ {
select {
case onum1 := <-fin1:
nums1[onum1.pos] = onum1.num
case onum2 := <-fin2:
nums2[onum2.pos] = onum2.num
}
// fmt.Println("Check ", sendPos)
sendNum1, ok1 := nums1[sendPos]
sendNum2, ok2 := nums2[sendPos]
if ok1 && ok2 {
out <- sendNum1 + sendNum2
sendPos++
}
}
for i := sendPos; i < n; i++ {
sendNum1 := nums1[i]
sendNum2 := nums2[i]
out <- sendNum1 + sendNum2
}
}
решение было принято 13-го мая
Это да… пытался тут мидл программистом андроида удаленно устроится — не берут )
Возраст 40+. Опыт программирования на многих языках. В мега-супер проектах не участвовал, но есть приложения на маркете, которые живут со времен андроида 2. Есть опыт программирования на жаве со времен 1.4.
Может стоит хайповые темы и знание модных библиотек еще подтянуть? Всякие там корутины, архитектуры и архитектурные принципы, и прочее что обычно в вакансиях болтается? На гитхаб чего нибудь выложить и попросить кого нибудь проверенного отревьюить. Я конечно молодой, что мне скидку делает в глазах работодателей, но в андроид и вовсе из 1с перепрыгнуть умудрился.
Я использую rxjava и room в последних приложениях, делаю верстку экранов не на horizontal/vertical/border layout с клеем, а на ConstraintLayout, и вместо SimpleCursorAdapter использую RecyclerView.
Мне в лучшем случае присылают ответ: "вы нам не подходите". Вполне возможно hr специалисты отсекают мое резюме по ключевым словам, хотя скорей всего по возрасту. Сеньору 25 лет страшно работать с дяденькой, который программировал на ассемблере z80, когда он еще не родился.
И, да, 1с я тоже программирую. Нормальный программист выбирает язык под задачу — так меня учили в школе )
Эйджизм конечно существует, но насколько мне известно чтобы его преодолеть достаточно приложить усилий не немногим больше чем молодежи. По крайней мере если человек уже программист, а не из маркетинга, например, приходит.
Еще раз: меня просто не зовут на собеседования программиста андроид.
Программистом 1с позвали с первой рассылки (резюме другое).
Я совершенно не понял ваше описание проблем с задачей E в посте, хотя сам готовил много "классических" задач под разные системы (в том числе Я.Контест). Впрочем, пост в Telegram кое-то прояснил, процитирую:
и дальше наша теория того, что произошло:
1) они настроили задачу по умолчанию, stdout был включен, они прогнали свой тест — все ок, соответственно в run.sh они не добавляли перенаправление
2) они пошли смотреть как выглядит страничка для участников, увидели что там "стандартный вывод" в поле "Вывод" под названием задачи и сделали "рефакторинг" — отключили его, установили имя файла как "см формат вывода", не проверили авторское решение (там есть кнопка "пересудить" для него)
(есть еще вариант что они по какой-то другой причине отключили stdout не сразу, а через пару дней, но это на суть не влияет)
Вот это уже звучит реалистично. Вполне разумная схема для впихивания нетривиальных задач в олимпиадную систему: компилируем участника вместе со своими юнит-тестами, запускаем, упало — Runtime Error, не упало — всё прошло. Чекер тривиален и ничего не проверяет.
Правда, совершенно непонятно, как тут можно разумно получить Wrong Answer, если уж чекер тривиален.
Для контекста: практически все системы для олимпиад работают по одной и той же схеме, сомневаюсь, что Я.Контест сильно отличается:
- Предполагается, что решение задачи — отдельная программа, читает некоторые данные, выводит некоторые данные, всё за ограниченное время. Решение помещается в один файл. Это позволяет поддерживать сразу кучу языков, только бы компилятор был.
- При создании задачи выставляется "имя входного файла", "имя выходного файла", а также список пар (входные данные, "ответ жюри" на тест) плюс специальный "чекер".
- Каждое решение система "компилирует" в один файл. В очень творческом смысле — например, действительно, скопировав исходники в архив.
- Далее на каждом тесте система независимо "запускает" решение, подсунув входной файл. Выходной файл решение должно создать самостоятельно. Файл с "ответом жюри" в этот момент в песочнице отсутствует, чтобы участник не скатать.
- Если решение упало (ненулевой код возврата) или заняло много времени, то оно убивается. Получаем либо Runtime Error, либо Time Limit Exceeded (много процессорного времени в каком-то из смыслов), либо Idleness Limit Exceeded (процессорного мало, но много wall clock, например, решение заблокировалось на чтении). Иногда дополнительно после этого проверяется наличие созданного выходного файла (увы, не помню, как в Я.Контесте).
- Если решение не упало и прошло по времени, то в песочницу копируется файл с ответом жюри и запускается чекер. На обычных контестах обычно пишется на testlib и принимает три аргумента командной строки: входной файл, выходной файл (который должен был создать запуск участника), только что скопированный файл с ответом жюри. В целом "файл с ответом" может быть творческий и содержать не ответ, а какую-то подсказку для чекера, но это не очень хорошо.
- Если чекер вернул 0 — ответ засчитан. Иначе — Wrong Answer, Presentation Error, Jury Error ("всё сломалось, зовите жюри разбираться"), в зависимости от настроек.
Правда, совершенно непонятно, как тут можно разумно получить Wrong Answer, если уж чекер тривиален.
А все просто — чекеру не приходили данные.
Процитирую первую часть сообщения:
чтобы чекер работал надо чтобы был включен stdout или run.sh писал в файл (у них как мы видели не пишет)
дальше чекер может хитро проверять вывод, в их случае мог бы проверять время тестов или как-то по другому парсить вывод go test — но как мы видели, даже если записать пробел в файл то чекер говорит ок, соответственно никакой пользовательской логики в нем нету. если чекер возвращает 0 — платформа пишет ok (https://admin.contest.yandex.ru/docs/advanced-usage-examples/default-tests-for-language.html)
Если чекер тривиален, он не проверяет данные никак и пустой вход его тоже устроит. Если чекер нетривиален, то он не будет съедать абсолютно любой ввод. Чекер, которые проверяет, что вход непуст — это что-то очень странное.
Подозреваю, что Я.Контест проверяет, что решение участника создало выходной файл ещё перед запуском чекера. Тогда это было бы разумно сделать Presentation Error, но, возможно, пошли по пути Codeforces и убрали этот вердикт.
Presentation error мы ни разу не видели.
Что конкретно проверяет их чекер мы не видели, это недоступная информация.
Мы создали свой контест и использовали простейший пример чекера из статьи.
Я могу лишь предположить, что их чекер искал в файле слово FAIL (вывод go test) и проверял что файл не пуст (так как файл создается автоматически всегда).
Кстати, любой желающий может создать свой контест и посмотреть как он работает. Какие-то нюансы мы могли и пропустить.
После того, как я из задания E достал Makefile, shell-скрипты и их код на go, и указал им на их ошибку по почте, мне вообще больше не ответили ни да, ни нет, просто молчание.
Я программировал на обоих. Мое сугубое имхо: Go быстрее, дает более компактные докер образы, и не сложнее. Есть своя специфика — он более явный, ты больше пишешь, явно обрабатываешь ошибки, явно запускаешь горутины, вместо всей машинерии с async/await/task. Питон более лаконичный: те же comprehensions, with, и прочее, что позволяет в несколько строк написать много действий.
Не хочу скатываться в холивар — у каждого инструмента свое применение, в котором он хорош.
Думаю реально, но надо больше времениРеально (но уже с новым ограничением в 1.5 секунды.) :)
(Если прочитать всю входную строку с сделать split() — не хватает памяти
(«9: memory-limit-exceeded 189ms / 74.14Mb»).
Срабатывает, если, например, вручную по прочитанной строке пройти, находя числа по одному. (Читать сразу из входа по 1 символу было слишком долго).
Регулярные выражения не пробовал.)
№ Вердикт Ресурсы Баллы
1 ok 32ms / 2.38Mb -
2 ok 32ms / 2.38Mb -
3 ok 33ms / 2.38Mb -
4 ok 32ms / 2.38Mb -
5 ok 34ms / 2.38Mb -
6 ok 367ms / 3.29Mb -
7 ok 246ms / 4.86Mb -
8 ok 1.438s / 4.86Mb -
9 ok 333ms / 26.05Mb -
# -*- coding: utf-8 -*-
IN_FILE = 'input.txt'
OUT_FILE = 'output.txt'
seen = set() # Было
result = 0 # ничего не нашли
def get_number(st, start):
result = ''
i = start
l = len(st)
while st[i].isdigit() and i < l - 1:
result += st[i]
i += 1
# forward to next number if any
while not st[i].isdigit() and i < l - 1:
i += 1
if len(result):
return int(result), i
else:
return None, None
with open(IN_FILE) as f:
target = int(f.readline())
# for number in [int(x) for x in f.readline().split()]: # Out-of-memory
next = 0
numbers = f.readline()
while(True):
number, next = get_number(numbers, next)
if number is None:
break
complement = target - number
if complement > 0 and complement in seen:
result = 1
break
seen.add(number)
# print(target, result)
with open(OUT_FILE, "w") as f:
f.write(str(result))
Во время конкурса решилось на С++ (после попыток на других языках).
На последнюю задачу много времени потратил, после стало не интересно пробовать и некогда.
import sys
data = open('input.txt', 'r')
target, sequence = data.read().splitlines()
target = int(target)
digits: List[int] = [map(int, sequence.split()))
file_out = open('output.txt', 'w')
x = target//2
y = target -x
if(x==y):
y+=1
z = tuple(filter(lambda u: u<=x, digits))
w = tuple(filter(lambda u: u>=y, digits))
for i in z:
for j in w:
if i+j==target:
file_out.write('1')
sys.exit()
file_out.write('0')
from itertools import chain
result = "0"
trash = set()
with open("input.txt", "r") as in_file:
target = int(in_file.readline())
prev = ""
while True:
chars = in_file.read(1000)
if chars:
numbers_str = chars.split(" ")
first = numbers_str[0]
last = numbers_str[-1]
numbers = map(lambda x: int(x), numbers_str[1:-1])
if prev == "":
first = int(first)
elif first == "":
first = int(prev)
else:
first = int(prev + first)
prev = last
for number in chain([first], numbers):
if number < target:
if number in trash:
result = "1"
break
trash.add(target - number)
if result == "1":
break
else:
break
with open("output.txt", "w") as out_file:
out_file.write(result)
Не идеальное решение, но имеет место быть
Но пришлось очень аккуратно подгонять размер кусков считывания входного файла. =)
def f_sum_of_two():
has_sum = 0
diffs = set()
digits = ""
with open("input.txt") as f:
target = int(f.readline().rstrip())
while True:
char = f.read(1)
if not char:
break
if not char.isdigit():
number = int(digits)
digits = ""
if number > target:
continue
if number in diffs:
has_sum = 1
break
diff = target - number
diffs.add(diff)
else:
digits += char
with open("output.txt", "w") as f:
f.write(str(has_sum))
file, err := os.Open("input.txt")
if err != nil {
panic(err)
}
defer file.Close()
data := make([]byte, 64)
for {
n, err := file.Read(data)
if err == io.EOF {
break
}
str += string(data[:n])
}
Строки в Go иммутабельны, при выполнении конкатенации каждый раз создаётся новая строка.
Go не быстрее Python, но сложнее в написании.
Странно делать такой вывод, если знакомство хотя бы с одним из языков — поверхностное
побаловался с go
нашел душевный чатик и поучаствовал в ковыряниях палочкой в этом всем ))
в целом неплохо провел время.
главное, имхо, как и в любом деле, вовремя соскочить )
Да и сам контест был интересным, задания казались не сложными (хотя вот например задание А, если внимательно прочитать условия, можно было решить чисто алгоритмически, я вот не прочитал и когда увидел их вариант решения — порадовался) и можно было быстро попрактиковаться в алгоритмических задачах на go (не как в обычной жизни).
Весь сыр бор из-за Е начался именно из-за того, что задача выглядела простой, но не работала. Многие потратили бОльшее время на решение, чем ожидали. Я потратил 2 ночи на задание: 1ую — пытаясь переписать задание по-разному, потом написал свои тесты с нереальными угловыми случаями и в итоге так перестарался с решением, что потратил вторую ночь ловя дедлоки. И даже это задание не прошло.
Надо, конечно, было бы раньше полезть в систему и посмотреть почему так, тут сам виноват :-)
И вот когда мы нашли косяки в системе, тогда то нас и пробило на «добиться правды».
Тем, кто не участвовал, наверно все это кажется дичью, и оно, от части, так и есть :-)
В общем то, эта статья — это просто наша попытка привнести правду в случившееся и показать, что признавать ошибки — это правильно, а юлить — не очень :-)
Получил моральное удовлетворение от вашей публикации. Вы описали весь мой путь от и до. За исключением того:
- через панику и ошибки последний тест я продебажил самостоятельно за 63 подхода.
- на мои два письма о проблемах контеста мне никто не ответил
Полученный опыт заключается в большем скептицизме относительно подобных мероприятий. И да, времени жаль.
Собеседование было, на уровне пары общих вопросов. Отписка что вы не подходите тоже была.
habr.com/ru/company/ozontech/blog/507932
так и ожидает модерации.
И почему я не удивлен :)
Мой коммент тоже на модерации (писал в ответ на то что отписки получили те кто решил 3 и менее задач). Решил 4 задачи (и по задаче Е прислал решение), но получил такую же отписку: «Побольше практики: участвуйте в проектах, чемпионатах, олимпиадах.».Мне прислали такое же письмо (тоже решил 4 и Е (сейчас то же самое (плюс коммент, чтобы формально отличалось) решение, что давало «WA», даёт «OK» (говорят, недавно (уже после подведения результатов) тестирование починили)).
Причем мое решение такое же как описано в той статье. Возможно, все из-за того, что не завершал тестирование до последнего, ожидая какого-либо прояснения информации по задаче Е.Вероятно, лучше не искать закономерностей там, где их нет (и держаться подальше от болот).
Я один из требований и описания тестирования/школы понял, что для прохождения тестов необходимо знание какого то ЯП и не обязательно GO?
Прошёл все тесты на Питоне и был сильно удивлён заданию на Go. В школу Go. Которое не работает. =)
Да, прикольно получилось )
Задание на Go шло как дополнительное, если уже имеешь где-то посмотрел go, прошел go tour, то можешь показать знания.
Но в итоге так и вышло — задание необязательное :)
За 80 истраченых попыток я успел:
— Вычитать все сходные параметры.
— Составить карту верных ответов (если за 95 попыток не получится).
— Попробовать несколько вариантов оптимизации в плане обработки и хранения данных.
— Побиться головой о стену )
input_file = open("input.txt")
target = input_file.readline()
target = int(target)
numbers_trg = []
chunk_len_def = 2200000
chunk_len = chunk_len_def
tail = ""
is_exit = 0
is_double = 0
while chunk_len > 0:
chunk = input_file.read(chunk_len_def)
chunk_len = len(chunk)
if chunk_len == 0:
break
split_chunk = [int(i) for i in chunk.split()]
if chunk[0].isdigit() and tail != "":
split_chunk[0] = int(str(tail) + str(split_chunk[0]))
if chunk[-1].isdigit() and chunk_len == chunk_len_def:
tail = split_chunk[-1]
split_chunk.pop(-1)
else:
tail = ""
if len(split_chunk) == 0:
break
if target % 2 == 0:
is_double += split_chunk.count(target / 2)
if is_double > 1:
is_exit = 1
break
if len(list(filter(lambda x: target - x in numbers_trg, set(split_chunk)))) > 0:
is_exit = 1
break
numbers_trg.extend(list(filter(lambda x: x < target and not x in numbers_trg, set(split_chunk))))
split_chunk.clear()
chunk = ""
#print(numbers_trg)
#print(split_chunk)
#print(is_exit)
imin = 1
imax = target - 1
while imin < imax and is_exit != 1:
if imin in numbers_trg and imax in numbers_trg:
is_exit = 1
if is_exit == 1:
break
imin += 1
imax -= 1
input_file.close()
output_file = open("output.txt", "w")
#print(f"{imin},{imax},{is_exit},{is_double},{numbers_trg}")
if is_exit == 1:
output_file.write("1")
else:
output_file.write("0")
Именно реакция на такие ситуации и формирует репутацию технологической компании.
OZON: WA
Ozon объявляет о наборе нубов в школу Go, "нубы" прут с такой силой, что хакают платформу проведения тестов Яндекса, запускают playground правильной проверки задания, объясняют Озону как надо делать сборку, попутно х##сося его на Хабре.
Шел 5 месяц самоизоляции :)
Приходит hr к одному из лидов со словами: «Нам для pr нужно то-то.» Лид отрывает пару человек от задач и говорит: «С вас задачи на конкурс». За небольшое время появляется контест и рабочие руки возвращаются к таскам в спринте. Далее происходит то, что происходит.
Хотел принять участие, но решил, что опыта маловато. Как оказалось — не зря, убил бы время и нервы. Кстати, школа-то вообще работает? Или это просто был замаскированный набор сотрудников?
Сложности связаны также с тем, что Go — новый язык для платформы
Не понятно, зачем вообще было связываться с Яндексом. На том же CodeForces в списке поддерживаемых Go уже довольно давно.
Там в целом набрали команду не пойми кого, которые работают не пойми как. )
Да, жаль только потраченного времени. Ну может заменят криворуких программистов теперь.
Ozon go school: Как не нужно проводить отбор