Это серия постов о том, как я решаю каждую из задач Advent of Code (AoC) с 2015 года по сегодняшний день на языке Python. Идея заключается в том, чтобы заставить себя практиковаться в кодировании и искать пути совершенствования. После решения каждой задачи я буду просматривать работы других людей, чтобы узнать, есть ли более питоничные способы решения головоломки, и какие крошечные решения могли придумать игроки в гольф.
Питонический код
Существует способ написания кода на Python, который использует язык так, как он предназначен для использования, а не просто синтаксически правильно. Я пришел в Python после многих лет кодирования на Basic в той или иной форме, поэтому многие из моих Python могут просто читаться как переведенный Basic. После многих лет увлечения мне еще многое предстоит узнать, например, об итераторах. Мне нравится находить решения адвентных головоломок на других языках Python, которые дают мне новые знания о языке.
Гольф
Какими бы сложными ни были адвентные головоломки, всегда найдется часть кодеров, которые хотят написать абсолютно короткий код. Это не всегда самый эффективный код, и часто он бесполезен в производстве, так как имеет тенденцию быть довольно непрозрачным, и поэтому его трудно поддерживать. Тем не менее, бывает очень приятно наткнуться на одну строчку кода, которая решает головоломку, для которой вы написали двадцать строк.
Спойлеры?
На Reddit есть мегапотоки решений для каждой головоломки AoC, практически на всех языках, которые вы можете себе представить. А головоломкам, с которых я начинаю, уже почти семь лет, так что я не думаю, что кому-то что-то испорчу.
День 1
Итак, приступим к головоломкам, каждая из которых состоит из двух частей. Решение первой части открывает вторую, и за решение каждой части вы получаете звезду. Цель к концу Адвента — получить 50 звезд. Обычно есть файл с тестовыми данными, которые служат исходными данными для обеих частей головоломки. Это означает, что первый бит кодирования практически в каждом решении — это загрузка данных головоломки из файла.
Часто файл представляет собой одну (очень длинную) строку, которую можно загрузить следующим образом:
with open('input.txt') as f:
data = f.read()
Или же файл будет содержать несколько строк данных, которые я обычно загружаю в список следующим образом:
with open('input.txt') as f:
data = [line for line in f]
Часть 1
Головоломки обычно написаны в стиле истории о Санте и его эльфах; вот как разворачивается сцена первого дня:
Моей первой мыслью было просто перебрать строку (for char in data
), но я вспомнил, что строковая библиотека Python может считать экземпляры одной строки внутри другой:
floor = data.count('(') - data.count(')')
И это был конец первой части, которая, справедливости ради, была заявлена как простая головоломка! Когда я рассматривал другие решения для более питонических подходов, основной альтернативой было использование функции replace()
строк для замены ‘(‘ на ‘+1’ и ‘)’ на ‘-1’ и выполнение eval()
для полученной строки:
eval(data.replace('(', '+1').data(')', '-1'))
Часть 2
Вторая часть делает практически то же самое, но нам нужно остановиться, когда мы достигнем подвала:
На мой взгляд, это означало циклический перебор символов с выходом из цикла при достижении подвала:
floor = 0
pos = 0
for char in data:
pos += 1
if char == '(':
floor += 1
else:
floor -= 1
if floor == -1:
break
print(str(pos))
Это, конечно, работало, и выполнялось достаточно быстро (<4 мс), чтобы не слишком беспокоиться о том, насколько это можно улучшить…
В гольф термины, хотя, это выделил для меня, от пользователя Reddit:
list(itertools.accumulate([1 if b == '(' else -1 for _
x in data])).index(-1) + 1
Я не видел функцию accumulate()
раньше, но похоже, что здесь она делает следующее: просматривает всю строку, сохраняет номер этажа в новом списке по мере прохождения старого списка, затем находит индекс (+1) первого раза, когда мы нажимаем -1. Это аккуратная функция, но потенциально немного расточительная, если мы попадаем в подвал на первой паре шагов в длинном наборе инструкций…