Как я наткнулся на фрагмент кода, который заставил меня переосмыслить все мое понимание программирования
Раньше я никогда серьезно не пробовал заниматься программированием на соревнованиях - да и сейчас не начал. Я начал программировать 6 лет назад, когда еще учился в старшей школе. В то время кодирование для меня было просто написанием коротких программ, которые печатали шаблоны или выполняли основную математику. Я действительно представлял свою школу на соревнованиях по программированию, где в основном полагался на свою интуицию и логику. Когда дело дошло до кодирования, я никогда не тренировался на соревнованиях.
Я писал на Java 4 года, прежде чем 2 года назад перешел на Python. Я написал сотни строк кода и прочитал несколько тысяч на этом языке. Я также недавно научил людей программировать на Python. Можно с уверенностью сказать, что я чувствовал себя очень уверенно в этом языке. Если бы вы показали мне сценарий на Python, я мог бы просмотреть его построчно и рассказать вам, что он делает.
Но затем я столкнулся с конкурентным кодированием, что заставило меня усомниться в моих знаниях Python, потому что моей первой реакцией на простую строчку кода было: «Что это за язык? Уж точно не Python ...? »
Это был простой вопрос по Codeforces (ссылка здесь) - вам даны два числа: A и B. . Вам нужно найти три числа x, y и z, чтобы
x + y = z
И что любой из них (скажем, x) должен делиться как на A, так и на B em. >, а два других (y и z в данном случае) должны делиться только на A .
Если можно найти эти числа, мы печатаем «ДА», а затем три числа, если нет, мы печатаем «НЕТ».
Наблюдение говорит нам, что это возможно, пока B не равно 1 (или меньше). Наиболее очевидный кандидат на число, кратное как A, так и B, - это AB .
Позволять,
x = A.B
y = A
Потом,
z = x + y = A.B + A = A.(B + 1)
Если вы заблудились на этом этапе, я настоятельно рекомендую посетить ссылку на проблему, потому что они могли бы объяснить ее гораздо лучше, чем я.
Обычно у меня есть привычка проверять сообщения других людей (перенял эту привычку из LeetCode), чтобы увидеть, придумал ли кто-нибудь лучшее решение или, может быть, просто другой взгляд, который мог бы меня чему-то научить.
Моим первым культурным шоком было то, что первые 15–20 заявок были одним и тем же кодом. Я до сих пор не уверен, почему это так, поскольку я знаю, что в живых соревнованиях также проводятся проверки на плагиат, и ни один из этих кодов не прошел бы их. Копируют ли люди решения других людей после завершения конкурса, чтобы увеличить количество представленных в их профиле материалов? Бьет меня.
Второй мой культурный шок был следующим:
for s in[*open(0)][1:]:a,b=map(int,s.split());c=2*a*b;print(*(['NO'],['YES',a,c-a,c])[b>1])
Это код, который я нашел для большинства материалов, сделанных на Python. Это очень принудительный однострочный код, использующий точки с запятой. Но мне потребовалось время, чтобы на самом деле расшифровать это - и как только я перевел это на более питоническую логику, я почувствовал такое чувство выполненного долга, что я создал учетную запись Medium только для документирования всего процесса.
Весь код можно переписать как:
for s in [*open(0)][1:]: a, b = map(int, s.split()) c = 2 * a * b print(*(['NO'],['YES', a , c - a, c])[b>1])
Все, что я здесь сделал, это удалил точки с запятой и раскрыл истинную форму однострочника… четырехстрочного.
Давайте посмотрим на первую строку - это для каждого цикла, где s повторяется по коллекции, представленной беспорядком, который является [* open (0)] [1:].
open () - это функция, используемая для чтения из потоков (в основном используется для обработки файлов). Переданный дескриптор файла равен 0, что указывает на стандартный ввод (1 - стандартный вывод, а 2 - стандартный поток).
На этом этапе я спросил своего друга (бывшего маэстро соревновательного кодирования), почему они просто не принимают ввод в обычном режиме, используя функцию ввода - он сказал, что нужно писать как можно меньше символов, поскольку время в конкурентном кодировании дорого и вы не хотите тратить его на написание двух целых строк для чего-то, что можно сделать за половину строки.
Формат ввода - сначала мы вводим количество тестовых примеров, скажем T. Затем для следующих строк T мы вводим каждый тестовый пример. Вот пример от Codeforces:
Мы используем функцию open () для получения данных стандартного ввода и используем *, чтобы распаковать их в список строк. Первая строка должна содержать одно число - T. Здесь нарезка используется для игнорирования этой первой строки. Итак, в этом цикле for мы перебираем s по каждому из тестовых примеров. Для лучшего понимания мы можем перевести это так:
for s in [testcase_1, testcase_2, testcase_3, ..., testcase_T]:
В этом конкретном примере это будет выглядеть так:
for s in ['5 3', '13 2', '7 11']:
Следующие две строки довольно просты. Каждый тестовый пример представляет собой строку, состоящую из двух целых чисел, разделенных одним пробелом. Мы используем функцию карты, чтобы преобразовать их в тип данных int и сохранить их в переменных a и b.
Следующая большая головная боль - это оператор печати, поэтому давайте перепишем его еще раз.
print(*(['NO'],['YES', a, c - a, c])[b>1])
В операторе печати много чего происходит. Он имеет кортеж списков и некоторую операцию индексации + распаковки. Давайте сначала уберем кортеж с дороги.
(['NO'],['YES', a, c - a, c])
Это кортеж, состоящий из двух элементов. По индексу 0 этого кортежа у нас есть то, что нужно напечатать, если невозможно найти эти три числа. Мы знаем, что это происходит тогда и только тогда, когда b ≤ 1.
В индексе 1 кортежа у нас есть то, что должно быть напечатано, если три числа были найдены (b ›1).
Затем давайте посмотрим на происходящее интересное [b ›1]. Сначала я понятия не имел, что с этим делать, пока не понял - (b ›1) вычисляется как логическое значение, что означает либо True, либо False.
Истина также может быть представлена в целочисленном формате как 1. Ложь представлена как 0.
(b ›1), хотя и является логическим значением, используется для доступа к индексу кортежа через его целочисленное значение.
Итак, для случая 1: b ≤ 1,
b ›1 оценивается как 0 (Ложь)
(['NO'],['YES', a, c - a, c])[b>1] >>> ['NO']
Для случая 2: b ›1,
b ›1 оценивается как 1 (Верно)
(['NO'],['YES', a, c - a, c])[b>1] >>> ['YES', a, c - a, c]
Подводя итог, если b ≤ 1, мы просто печатаем «NO».
В противном случае мы печатаем «ДА», за которым следуют три требуемые цифры.
Оператор печати использует распаковку, чтобы распечатать все элементы в списке по отдельности, а не просто распечатать один список.
Например,
print([1, 2, 3]) >>> [1, 2, 3] print(*[1, 2, 3]) >>> 1 2 3 print(['NO']) >>> ['NO'] print(*['NO']) >>> NO
Это подводит нас к концу этого кода. Я спросил своего друга, зачем кому-то писать такой код, и он сказал, что иногда некоторые конкурирующие программисты пытаются написать самый нечитаемый код, чтобы предотвратить «взлом».
«Взлом» - это термин, используемый для оспаривания чужого кода во время конкурса. Если вы подозреваете, что другой пользователь отправил неправильный код и по-прежнему прошел все тестовые примеры, вы можете оспорить этот код, предоставив тестовый пример, для которого этот код должен завершиться ошибкой.
Если ваш код настолько нечитабелен, насколько это возможно, кому-либо будет сложно его взломать, потому что хакерам будет трудно даже понять, что происходит, а тем более найти изъян в логике.
Конкурентное программирование - это для меня совершенно новый мир.
Это был один из самых нечитаемых фрагментов кода Python, с которыми я когда-либо сталкивался. Но когда я сказал, что напишу об этом блог, мой друг пришел в восторг и пообещал показать мне худшие примеры, так что кто знает, что еще меня ждет?
Это будет моя новая серия блогов? Зависит от того, насколько я люблю приключения. Может, оставайся с нами?
Больше контента на plainenglish.io