Как я наткнулся на фрагмент кода, который заставил меня переосмыслить все мое понимание программирования

Раньше я никогда серьезно не пробовал заниматься программированием на соревнованиях - да и сейчас не начал. Я начал программировать 6 лет назад, когда еще учился в старшей школе. В то время кодирование для меня было просто написанием коротких программ, которые печатали шаблоны или выполняли основную математику. Я действительно представлял свою школу на соревнованиях по программированию, где в основном полагался на свою интуицию и логику. Когда дело дошло до кодирования, я никогда не тренировался на соревнованиях.

Я писал на Java 4 года, прежде чем 2 года назад перешел на Python. Я написал сотни строк кода и прочитал несколько тысяч на этом языке. Я также недавно научил людей программировать на Python. Можно с уверенностью сказать, что я чувствовал себя очень уверенно в этом языке. Если бы вы показали мне сценарий на Python, я мог бы просмотреть его построчно и рассказать вам, что он делает.

Но затем я столкнулся с конкурентным кодированием, что заставило меня усомниться в моих знаниях Python, потому что моей первой реакцией на простую строчку кода было: «Что это за язык? Уж точно не Python ...? »

Это был простой вопрос по Codeforces (ссылка здесь) - вам даны два числа: A и B. . Вам нужно найти три числа x, y и z, чтобы

x + y = z

И что любой из них (скажем, x) должен делиться как на A, так и на B , а два других (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