Рубрики
Без рубрики

Advent of Code 2020: День 01 Использование Numpy и векторизированных расчетов

Я собираюсь приступить к Advent of Code 2020 в Python, но каждый вызов я постараюсь исследовать различие … с меченым с AdhentOfCode, Python.

Advent of Code 2020 (26 части серии)

Я собираюсь делать Advent of Code 2020 В Python, но каждый вызов я постараюсь исследовать другой способ сделать это, может быть, выучить несколько новых библиотек в процессе.

Все упомянутые в этом посте: Контекстные менеджеры, генераторы, список пометки, IterTools, теория набора, Numpy, Chapy

Ссылка на вызов на Advent of Code 2020 сайта 2020

Выпарайте проблему, вы получаете список номеров, содержащих два запися, что сумма до 2020 года, проблема – просто найти эти два числа. (А затем умножить их вместе, чтобы отправить результат)

Приведенный пример:

1721
979
366
299
675
1456

Где в правильном ответе 1721 и 299. Фактический список, данный вам, намного дольше, чем это, но все же имеет только два числа, которые добавляют до 2020.

Advent of Code предоставит вам файл для загрузки, вы можете сохранить этот файл в виде текстового файла. Я спас его как input.txt в моем рабочем пространстве.

Чтение файла

Данные вступают в качестве файла, содержащего одно число в строке. К сожалению, если мы прочитаем этот файл напрямую, мы получим кучу строк, поэтому мы должны преобразовать их в номера, чтобы сделать математику. Вот довольно типичный подход к этому в Python.

data = []
with open("input.txt", "r") as fp:
    for value_str in fp:
        data.append(int(value_str))

Это простое, не выиграет вам никаких наград. Но, на самом деле есть что-то интересное, о котором здесь стоит говорить.

Контекстные менеджеры

Почему мы используем с нравится? В Python, с используется как часть «менеджера контекста». Контекстные менеджеры – это умные мелочи, которые автоматически управляют некоторым кодом при входе и при выходе в контексте. Это очень хорошо для файловых операций, операций с базой данных и вещей, где вы хотите автоматически закрыть свой файл/подключение, даже если ваши программы сбиваются из-за исключения.

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

Файловые операции одинаковы, хотя это полностью можно записать этот код без менеджера контекста (см. Ниже). Единственное отличие в том, если исключение происходит где-то между Открыть () и Закрыть () Затем рукопожатие файлов остается открытым. В этом случае это не проблема, потому что никто другой не использует файл, и эта программа закрывается довольно быстро. Но в продукции используются, возможно, вы открываете файлы, которые другие процессы ждут, чтобы написать. Лучше всего использовать контекстные менеджеры для файловых операций, чтобы вещи аккуратно закрыты.

data = []
fp = open("input.txt")
for value_str in fp:
    data.append(int(value_str))
fp.close()

Генераторы

Другое, что происходит вот то, что мы прямо говорим …| для Value_str в FP Отказ FP Это наш открытый файл, как следует, мы напрямую используем его в для … как Петля, как это? Это связано с тем, что файловые объекты Python на самом деле имеют кучу дополнительных функций, которые позволяют им использовать в нескольких разных способах, один из них является генератором, что позволяет вам вытащить одну строку в момент времени из файла, просто используя его в для … как петля. Если вы заинтересованы в технической реализации, под капотом Возвращенный файл объект FP имеет __Next __ () метод, который просто делает Self.Readline ()

Если вы не хотели использовать генератор, то, возможно, он будет выглядеть что-то подобное:

data = []
with open("input.txt") as fp:
    while True:
        value_str = fp.readline():
        if not value_str:
            break
        data.append(int(value_str))

Это не весело. Мы должны вручную читать каждую строку в петле и выйти из цикла, если эта строка была пустой.

Хотя … Если вы здесь, чтобы повеселиться, Python 3.8 вводит оператор Walrus : = Что на самом деле вводит немного удовольствия в этот плохой код:

data = []
with open("input.txt") as fp:
    while value_str := fp.readline():
        data.append(int(value_str))

Посмотри на это! Это снова весело.

И если вам было интересно, да, можно получить более компактным, используя понимание списка и Handy .readlines () Функция, которая возвращает массив строк из объекта файла

data = [int(value_str) for value_str in open("input.txt").readlines()]

Есть еще лучшие способы сделать это, чем это, я покажу вам более поздний кусок кода.

Наивеное решение

Там нет позора в начале с очевидного, но неэффективного решения. В реальном мире это достаточно часто!

Таким образом, у нас есть список номеров. Решение «Brute-Force» здесь просто принять каждый номер. Добавьте его в другой номер и посмотрите, является ли общий 2020. Давайте пойдем вперед и сделаем это, посмотрим, что мы учимся.

count = len(data)
# outer loop
for i in range(count):

    # inner loop
    for j in range(i, count):

        # check numbers
        left_number = data[i]
        right_number = data[j]
        if left_number + right_number == 2020:
            print("Found it!", left_number, right_number)

Выход:

Found it! 201 1819

Так что внешние петли петли Я между 0, и однако длинные данные есть. А затем для каждого из них, внутренние петли петлей J Между я, однако длинными данными, тем самым прикрывая каждую комбинацию. Мы просто проверяем, добавляем ли пару из них до 2020 года и распечатайте ее.

С помощью этого кода есть довольно много вещей. Это не отличный код. Мы можем сделать лучше.

Лучшее наивное решение

Во-первых, мы должны использовать Python’s для ... как лучше. Нам не нужно использовать i а также J Счетчик переменных Python не такого языка, и делать это довольно редко. Вместо этого мы можем напрямую повторить данные:

# outer loop
for left_number in data:

    # inner loop
    for right_number in data:

        if left_number + right_number == 2020:
            print("Found it!", left_number, right_number)

Выход:

Found it! 201 1819
Found it! 1819 201

Что за! У нас есть два решения. Да, к сожалению, использование двух петель, подобных таким образом, мы проигрываем на небольшую оптимизацию, которую мы сделали ранее, где мы запускаем внутреннюю петлю на индексе внешней петли; И поскольку мы не выходим из цикла после того, как решение найдено, код прошел впереди взаимную пару. Давайте исправим это.

# outer loop
for idx, left_number in enumerate(data):

    # inner loop
    for right_number in data[idx:]:

        if left_number + right_number == 2020:
            print("Found it!", left_number, right_number)

Встроенный перечислять () Функция теперь вызывает для ... как Чтобы вернуть бот исходное значение, а также его индекс (в основном эквивалент Я от раньше). Затем мы можем использовать это во внутренней цикле для «среза» данные и начать с части стороны вверх. Оба эти вещи – это то, что некоторые люди называют «питон».

Дать библиотеки иметь дело с этим

Одной из важных идей в программировании является то, что определенные вещи «решены проблемы». Что это значит это значит, это то, что настолько хорошо, что в нее почти не повторно изобретают. Бесчисленные люди написали этот код, и поэтому существует библиотека, которая ухожена и достаточно хорошо документирована, чтобы решить эту проблему. Мы стараемся не писать наш собственный код, чтобы решить «решенные проблемы». Итализация по всем комбинациям набора вещей – «решенная проблема». Так что должна быть библиотека, которую мы можем использовать, верно?

Да, это такая общая проблема, которую Python Ships с библиотекой под названием Itertools который содержит различные инструменты для выполнения итераций. В нашем случае мы хотим использовать Комбинации () Функция, которая, если дана наше данные будет просто выплавить каждое сочетание двух (или больше, если мы хотели) ценностей из него:

from itertools import combinations

for left_number, right_number in combinations(data, 2):
    if left_number + right_number == 2020:
        print("Found it!", left_number, right_number)
        break

Многоочиститель. Нет более вложенных петель, мы просто спрашиваем Комбинации () Чтобы дать нам один из наборов уникальных комбинаций 2 элементов из данные каждая итерация для петля, оставляя нас, чтобы просто проверить, если они суммируют.

Несмотря на то, что мы позволяем библиотеке ITERTOOLS сделать тяжелый подъем, это все еще метод грубой силы, где проверяется каждая комбинация. Есть ли другой способ сделать это?

Получить математическое

С маленькой математикой, да, мы можем. Поскольку мы ищем два числа, которые добавляют до 2020, для любого указанного номера в списке, мы знаем, что выглядит его соответствующая пара, это всего лишь 2020 минус номер!

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

for left_number in data:
    # calculate match
    right_number = 2020-left_number

    # does it exist?
    if right_number in data:
        print("Found it!", left_number, right_number)
        break

Вот еще одна питон: Если ... как Можно очень легко проверить, будет ли значение в списке.

Расстраиваться о вещах

Что мы делаем здесь, проходит через каждый номер в данные , расчет дополнительного значения, а затем видение, если это дополнительное значение было внутри данные Отказ Что если я сказал, что мы могли бы сделать это все в одном.

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

Это теория установлена! Мы хотим найти пересечение между данные и Дополнение Значения, в нем лежит наш ответ. У Python есть мощные установленные функции.

Сначала мы должны создать свой список комплиментов:

complement = []
for number in data:
    complement.append(2020-number)

Не так быстро, это не питон достаточно. Мы должны переключиться на понимание списка:

complement = [2020-number for number in data]

Намного лучше. Понимание списка Python очень мощно и позволяет написать компактный код. Вышеуказанные фрагменты с двумя кодами в значительной степени идентичны, однако определенная причина, по которой я хочу использовать понимание списка, что станет ясным в сек.

Теперь, когда у нас есть наш список дополнения, мы можем пойти дальше и создавать объекты Python Set из них:

complement = [2020-number for number in data]
c_set = set(complement)
d_set = set(data)
print("Found it!", c_set.intersection(d_set))

Выход:

Found it! {201, 1819}

Как видите, с установленными наборами Python и немного математики, очень легко найти ответ.

Теперь … Почему я перешел на понимание списка? Поскольку Python Looves понимание, а в дополнение к пониманию списка также есть и установить понимание. Мы можем генерировать комплект дополнения напрямую:

complement = {2020-number for number in data}

Просто переключение на фигурные скобки Дополнение теперь набор, а не список. Более того, Пересечение () Метод будет автоматически преобразовывать списки, приведенные в качестве аргументов, что означает весь весь код, если мы использовали строки ввода данных короткоформа, на самом деле выглядят так:

data = [int(value_str) for value_str in open("input.txt").readlines()]
complement = {2020-number for number in data}
print("Found it!", complement.intersection(data))

Короче, быстрее

Итак, я упомянул «решенные проблемы». Оказывается, читающие числа из файлов также являются решенной проблемой. Как вычитает номера из большого списка чисел. Это все общие задачи в научных вычислениях, а Python имеет одну очень важной библиотеку для этого: numpy Отказ Numpy – это числовая вычислительная/многомерная вычисляющая библиотека массива. Я бы пошел так далеко, как сказать, что если вы узнаете Python, вы должны знать Numpy, он много приходит.

Numpy удобно имеет функции, чтобы сделать все это, включая вычитание номеров из всего, и выполнять пересечения. Так что код просто выглядит так:

import numpy as np
data = np.loadtxt("input.txt")
complement = 2020 - data
print("Found it!", np.intersect1d(data, complement))

Выход

Found it! [ 201 1819]

Вот и все! Четыре линии, без петель. Загрузить данные, рассчитать дополнение, запускать пересечение между этими двумя списками. Выполнено.

Хотя линия 2020 - данные выглядит как обычное вычитание, потому что данные теперь является Numpy Object, достаточно умно, чтобы знать, что это означает, что это значит сделать это вычитание для каждого члена данные Создание нового Ompy Object Дополнение с таким количеством элементов, как в данные Отказ Много кода с использованием Numpy выглядит как обычные математические операции, но на самом деле относится к большим массивам/матрицам все сразу.

Кроме того, с момента появления кодовой проблемы хочет, чтобы вы собрали два числа вместе, вы можете получить это напрямую с .Prod () Способ тоже:

np.intersect1d(data, complement).prod()

Я сказал быстрее

Python не самый быстрый язык там. Это на самом деле хорошо известно, что это немного медленно. Numpy обходит вокруг этого, будучи написанным в C, что означает, что при использовании Numpy вы используете пылающий быстрый оптимизированный код Crunching номер. Это отлично подходит для ученых данных, где, хотя Python медленно, тяжелый подъем делается Numpy.

Многие люди считают, насколько легко писать Python, чтобы быть важным преимуществом, перевешиваем его недостатки. Для многих задач, таких как наука о данных, где код может запускать несколько раз, чтобы обработать некоторые данные, а затем отложить в сторону, эта скорость написания Python имеет реальные преимущества. В конце концов, это быстрее и, вероятно, дешевле, чтобы просто написать какой-то код и оставить его номер в течение ночи, чем проводить несколько дней, оптимизирующих код в C/C ++, чтобы он мог завершить через несколько минут.

Итак, Python доминировал на сцене науки о данных, и поскольку она по своей природе не является быстрым языком, а не принудительным данным ученым для изучения C/C ++ и Refactor их код, экосистема Python имеет другой подход к улучшению производительности: параллелизма.

Подход Python – да, в основном «бросить больше вычислительной мощности», пока все еще легко написать. Python имеет множество библиотек и инструментов (многие встроены), которые разблокируют мульти-обработку или обработку по нескольким машинам или даже движущейся обработке в GPU.

Чтобы проиллюстрировать этот вид подхода, приведенный выше Numpy Code может быть легко преобразован для прогона в GPU, просто подключая numpy Для совместимой библиотеки Купье Отказ Там, где реализация Numpy под капотом находится в C, реализация Кушки использует CUDA для NVIDIA GPUS. Он поддерживает те же функции, что и Numpy, что означает в большинстве случаев, является простой заменой.

import cupy as np
data = np.loadtxt("input.txt")
complement = 2020 - data
print("Found it!", np.intersect1d(data, complement))

Посмотрите, что я там делал? Python позволяет вам изменить имя импорта при импорте их, так как я сделал Импортировать Numpy как NP Раньше я могу сейчас просто Импорт Кухни как NP И иметь чашку маскаринга как Numpy. Поскольку у него есть те же функции, все все еще работает, и теперь я переместил свою обработку на GPU с двухбуквенным изменением моего кода.

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

Вторая часть вопроса – небольшое расширение более ранней проблемы, но до 3 чисел. В этом случае, безупречно, что он больше не проходит для использования наборов или Numpy пересечений с 3 номером. Но, к счастью, более раннее решение на основе IterTools может очень легко простираться до 3 чисел, поэтому это простой случай:

from itertools import combinations
for a, b, c in combinations(data, 3):
    if a + b + c == 2020:
        print("Found it!", a, b, c)
        break

Далее!

Advent of Code 2020 (26 части серии)

Оригинал: “https://dev.to/meseta/advent-of-code-day-01-23hi”