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

Адвое Код 2020: День 09 Больше векторизированного грузоподъемника с Numpy в Python

ОК, вернемся к земле. Тратить слишком длинное здание сложное и ненужное решение, как в последний раз … Помечено с AdhentOfCode, Python.

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

ОК, вернемся к земле. Тратить слишком длинное здание сложное и ненужное решение, как в последний раз занимает слишком длинное, поэтому я сохраню этот простой.

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

Задача – это еще одна, где мы должны сломать код. Довольно просто в списке номеров число может быть сделано из добавления двух из предыдущих n цифры до этого. Найдите тот, который не следует этому правилу.

Повторно использовать день 01

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

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

Этот код находит пару номеров из списка, который добавляет до 2020. Нам просто нужно отрегулировать это немного. Во-первых, нам нужно заменить данные с «ломтиком» исходного списка. Если мы называем значение, мы проверяем IDX Тогда нам нужно нарезать от 25 до этого до 1 до нее. Это можно сделать с помощью нотации нарезки Python Данные [IDX-преамбулы: IDX] Отказ Во-вторых, нам нужно сравнить числа со значением данные [IDX] , так:

for left_number, right_number in combinations(data[idx-preamble:idx], 2):
    if left_number + right_number == data[idx]:
        break

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

Это полный код для части 1

data = [int(line) for line in open("input.txt").readlines()]
preamble = 25
for idx in range(preamble, len(data)):
    for left_number, right_number in combinations(data[idx-preamble:idx], 2):
        if left_number + right_number == data[idx]:
            break
    else:
        print("Did not find it!", start, data[idx])

Часть 2 вызова, просит нас найти сумму какого-либо непрерывного диапазона чисел, которые составляют номер, найденный в части 1, который я имею наличие the_number Отказ По сути, проблема сводится к этому: у нас есть 1000 номеров в данные Давайте определим X в качестве исходного положения, а y в качестве окончательного положения, которому нужно суммировать значения. Найти x и y, который приведет к сумме, равной the_number Отказ

В отличие от предыдущих задач, в которых была вариант грубой силы, это теперь грубо принудительно вдоль двух разных осей. Поскольку у нас есть 1000 номеров, у нас есть чуть ниже 000 итераций, чтобы пройти (это менее половины, потому что одно ограничение X должно быть ниже

Векторное решение

Мы можем рассмотреть пространство поиска большой 2D-массива, со значениями от 0 до 1000 на каждой оси, давая нам чуть более миллиона записей. Около половины этих ценностей недействительна из-за ограничения, которое X

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

import numpy as np
data = np.loadtxt("input.txt")

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

def sum_range_err(start, end):
    if 0 <= start < end < data.size):
        return np.abs(the_number - np.sum(data[int(start):int(end)]))
    return np.NaN

Здесь я определяю ограничение о том, что начало должно быть ниже конца, и оба должны быть между 0 и размером данных, а в любом месте за пределами ограничения – НП. Нан Отказ

К сожалению, из-за того, как работает эта функция, она не может принимать векторы в качестве входов, как я могу с арифметикой, например, A + B. Когда оба A и B являются векторами, отлично работает, результатом является еще один вектор, где каждый элемент был добавлен вместе. Тем не менее, Данные [A: B] не приведет к векторизованному расчету просто потому, что Numpy не знает, как это сделать. Не слишком много думая об этом, я просто собираюсь бросить всю функцию в Numpy’s numpy.vectionize () Функция, которая оборачивает мою функцию для меня Поэтому мне не нужно думать об этом. Возвращаемое значение теперь является векторизированной версией моей функции, которая будет работать на каждом участне входных векторов.

sre = np.vectorize(sum_range_err)

В то время как sum_range_err () Может ли только брать целые числа для своих аргументов, эта новая функция Sre () может принимать два вектора и вернуть вектор. Это помогает ускорить вещи.

Теперь, на вычислении нашей сетки ценностей. Для этого мне просто нужно рассчитать массив входов, просто все значения между 0 и размер наших данных, а затем пройти через наши СРЕ Функция, чтобы получить сетку результатов. Это все!

xax = yax = np.arange(0, data.size)
zvals = sre(xax[:,None], yax[None,:])

Наконец, нам просто нужно найти, где ZVALS это ноль:

print(np.where(zvals == 0))

Полный код:

import numpy as np
data = np.loadtxt("input.txt")

def sum_range_err(start, end):
    if 0 <= start < end < data.size:
        return np.abs(the_number - np.sum(data[int(start):int(end)]))
    return np.NaN

sre = np.vectorize(sum_range_err)
xax = yax = np.arange(0, data.size)
zvals = sre(xax[:,None], yax[None,:])
print(np.where(zvals == 0))

Вывод

(array([554, 667]), array([571, 668]))

Этот код требуется всего несколько секунд (4 секунды на моей машине) благодаря оптимизации Numpy, и нам не нужно было слишком много думать о том, как написать или оптимизировать код.

Мы могли бы сделать это быстрее, оптимизируя доступ к данным, а также все варианты, которые я обсуждал в день 01 ( Чаш, нумба и т. Д.)

Результатом является два диапазона: 554: 571 и 667: 668. Второй диапазон на самом деле является только решением часть 1 – мы искали какой-либо диапазон, который суммирует в записи, которую мы нашли в части 1. Таким образом, естественно, диапазон, состоящий из самой записи, имеет сумму, равную себе.

ZVALS Рассчитано выше – это 2D-сетка значений данных, и мы ищем минимумы этого значения. Мы можем легко визуализировать то, что это выглядит, используя Матплотлиб путем построения карты цвета:

import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(16,12))
cplot = ax.pcolormesh(xax, yax, np.log(np.log(zvals)))
fig.colorbar(cplot)
ax.plot(resultx, resulty, 'r+')
fig.show()

Здесь я применил пару функций журнала в значения Z, чтобы цвета выглядели более четко. График на самом деле очень крутой, что затрудняет четко видеть, а также представляет проблему для любого метода оптимизации на основе градиента.

Я также понизил точку результата как красный знак, чтобы подчеркнуть, где оно есть.

Что мы видим вот то, что правильное значение находится в очень узкой долине чисел. Эта долина настолько узкая, что я фактически пробовал некоторые функции оптимизации/минимизации из Scipy Пакет, и большинство из них не смог найти решение. Единственный успех был метод Brute-Force, который занял много раз дольше, чем векторизованная версия выше. Я думаю, что эти функции оптимизации, вероятно, все равно будут работать, но потребуется некоторая настройка, чтобы правильно получить размеры шага.

Вот этот код Brute-Force:

from scipy import optimize

def sum_range_err_reflected(point):
    start = np.min(point)
    end = np.max(point)
    return np.log(np.log(np.abs(the_number - np.sum(data[int(start):int(end)]))))

ranges = (slice(0, data.size, 1), slice(0, data.size, 1))
optimize.brute(sum_range_err_reflected, ranges=ranges)

Примечание.

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

Далее!

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

Оригинал: “https://dev.to/meseta/advent-of-code-day-09-215i”