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

Пришествие кода 2020: День 16 Использование CSV и Numpy в Python

Еще один быстрый сегодня Ссылка на Challenge Part 1 на Advent of Code 2020 Web … Tagged with AdventofCode, Python.

Еще один быстрый сегодня

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

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

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

Импорт данных

Данные, опять же, похожи на CSV, поэтому я буду использовать Python CSV библиотека. На этот раз входные данные выглядят немного по -другому, так как в документе есть некоторая вертикальная структура, а не чистые данные, которые могут быть легко импортированы в несколько строк.

Вместо того, чтобы автоматически обнаружить структуру, я буду номера строк с жестким кодом, чтобы избежать траты времени здесь:

import csv
import re

data = open("input.txt").readlines()

rules_data = data[0:20]
my_ticket = next(csv.reader(data[22:23], quoting=csv.QUOTE_NONNUMERIC))
nearby_tickets = list(csv.reader(data[25:], quoting=csv.QUOTE_NONNUMERIC))

CSV. QUOTE_Nonnumeric рассказывает CSV Библиотека для преобразования неровных значений в числа , это экономит нам необходимость сделать дополнительный шаг конверсии

Правила анализа

ravels_data содержат некоторые структурированные значения, которые служат правилами, приведенным примером является:

class: 1-3 or 5-7

Это следует интерпретировать как имя правила класс , который проверяет, если заданное значение составляет от 1 до 3 или 5 и 7 (включительно). Чтобы проанализировать их, используется небольшая регуляция, возвращая функцию, которая реализует эти правила:

rules_re = re.compile("^([a-z ]+): (\d+)-(\d+) or (\d+)-(\d+)$")
rules = []
for rule_str in rules_data:
    name, *numbers = rules_re.match(rule_str).groups()

    def make_rule(numbers):
        l1, h1, l2, h2 = map(int, numbers)
        return lambda val: (l1 <= val <= h1) or (l2 <= val <= h2)

    rules.append((name, make_rule(numbers)))

Вещество, на которую нужно отметить, это закрытие def make_rule () Анкет Хотя можно напрямую вставить лямбду, поскольку эта лямбда требует доступа к переменным вне ее, которые изменяют каждый цикл, мы попадаем в ситуацию, когда они ссылаются на неправильную переменную, что является проблематичной. Поэтому нам нужно, чтобы они были только для локальной области, и поэтому нужно использовать закрытие, которое назначает эти переменные.

Поиск ошибок

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

errors = []
for ticket in nearby_tickets:

    for value in ticket:
        if not any(func(value) for _, func in rules):
            errors.append(value)

print("errors", sum(errors))

Это дает результат, который ищет задача.

Полный код для этой части:

import csv
import re

data = open("input.txt").readlines()

rules_data = data[0:20]
my_ticket = next(csv.reader(data[22:23], quoting=csv.QUOTE_NONNUMERIC))
nearby_tickets = list(csv.reader(data[25:], quoting=csv.QUOTE_NONNUMERIC))

rules_re = re.compile("^([a-z ]+): (\d+)-(\d+) or (\d+)-(\d+)$")
rules = []
for rule_str in rules_data:
    name, *numbers = rules_re.match(rule_str).groups()

    def make_rule(numbers):
        l1, h1, l2, h2 = map(int, numbers)
        return lambda val: (l1 <= val <= h1) or (l2 <= val <= h2)

    rules.append((name, make_rule(numbers)))

errors = []
for ticket in nearby_tickets:

    for value in ticket:
        if not any(func(value) for _, func in rules):
            errors.append(value)

print("errors", sum(errors))

Во второй части задачи нас попросят выяснить, какое правило поступает в каком столбце, найдя порядок правил, для которых все значения в таблице действительны.

Поиск действительных билетов

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

valid = []
for ticket in nearby_tickets:
    for value in ticket:
        if not any(func(value) for _, func in rules):
            break
    else:
        valid.append(ticket)

Наивный подход

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

def check_next(rules, data):
    column, *the_rest = data

    for idx, (name, rule) in enumerate(rules):
        if all(rule(value) for value in column):
            new_rules = rules[:idx] + rules[idx+1:]

            if not new_rules:
                return [name]

            if rule_string := check_next(new_rules, the_rest):
                return [name] + rule_string
    return None

valid_np = np.array(valid)
check_next(rules, valid_np.T)

К сожалению, это действительно медленно. Слишком много итераций, и этот расчет может занять часы.

Можем ли мы сделать лучше?

Процесс устранения

Давайте рассмотрим, может ли быть, что данные структурированы, чтобы мы могли найти столбцы в процессе устранения? Более ранняя формулировка вопроса, кажется, говорит об этом. Посмотрим, сколько правил действительны для каждого столбца:

for column in valid_np.T:
    print(len([rule_name for rule_name, rule in rules if all(rule(value) for value in column)]))

Выход

8
19
20
12
4
18
10
5
6
1
9
15
14
13
3
16
7
2
11
17

Бинго, ясно, что один из этих столбцов, 10 -й, соответствует точно одному правилу, что означает, что мы можем устранить как этот столбец, так и это правило с следующего шага поиска. Фактически, ясно, что в другом столбце есть 2 совпадения, еще 3, поэтому данные, скорее всего, будут доступны для поиска, используя простой процесс устранения.

Давайте кодируем это:

columns = np.insert(valid_np.T, 0, np.arange(valid_np.shape[1]), axis=1)

def eliminate(rules, columns):

    if len(columns) == 1:
        return [(columns[0][0], rules[0][0])]

    for idx, (col_idx, *column) in enumerate(columns):
        valid_rules = [rule_name for rule_name, func in rules if all(func(value) for value in column)]

        if len(valid_rules) == 1:
            new_rules = [rule for rule in rules if rule[0] != valid_rules[0]]
            remaining_columns = np.vstack([columns[:idx], columns[idx+1:]])
            return [(col_idx, valid_rules[0])] + eliminate(new_rules, remaining_columns)

result = eliminate(rules, columns)

Выход

[(9.0, 'wagon'),
 (17.0, 'arrival station'),
 (14.0, 'row'),
 (4.0, 'train'),
 (7.0, 'route'),
 (8.0, 'seat'),
 (16.0, 'type'),
 (0.0, 'zone'),
 (10.0, 'arrival platform'),
 (6.0, 'departure time'),
 (18.0, 'departure platform'),
 (3.0, 'departure location'),
 (13.0, 'departure date'),
 (12.0, 'departure track'),
 (11.0, 'departure station'),
 (15.0, 'arrival track'),
 (19.0, 'arrival location'),
 (5.0, 'duration'),
 (1.0, 'price'),
 (2.0, 'class')]

Этот код завершился мгновенно (он не должен был выполнять никакого поиска) и заказал правила!

Задача просит нас использовать это сопоставление с правилом на колонну и умножить шесть столбцов из наших данных о наших билетах, имя правила которого начинается с «отъезда». Мы можем использовать многоиндексирование/вещание Numpy, чтобы напрямую вытащить эти столбцы из наших билетных данных:

six_rules = [int(idx) for idx, rule_name in result if rule_name.startswith("departure")]
print("product", np.prod(np.array(my_ticket)[six_rules]))

Это напрямую создает выход. Полный код для этой части:

import csv
import re

data = open("input.txt").readlines()

rules_data = data[0:20]
my_ticket = next(csv.reader(data[22:23], quoting=csv.QUOTE_NONNUMERIC))
nearby_tickets = list(csv.reader(data[25:], quoting=csv.QUOTE_NONNUMERIC))

rules_re = re.compile("^([a-z ]+): (\d+)-(\d+) or (\d+)-(\d+)$")
rules = []
for rule_str in rules_data:
    name, *numbers = rules_re.match(rule_str).groups()

    def make_rule(numbers):
        l1, h1, l2, h2 = map(int, numbers)
        return lambda val: (l1 <= val <= h1) or (l2 <= val <= h2)

    rules.append((name, make_rule(numbers)))

valid = []
for ticket in nearby_tickets:

    for value in ticket:
        if not any(func(value) for _, func in rules):
            break
    else:
        valid.append(ticket)

columns = np.insert(valid_np.T, 0, np.arange(valid_np.shape[1]), axis=1)

def eliminate(rules, columns):

    if len(columns) == 1:
        return [(columns[0][0], rules[0][0])]

    for idx, (col_idx, *column) in enumerate(columns):
        valid_rules = [rule_name for rule_name, func in rules if all(func(value) for value in column)]

        if len(valid_rules) == 1:
            new_rules = [rule for rule in rules if rule[0] != valid_rules[0]]
            remaining_columns = np.vstack([columns[:idx], columns[idx+1:]])
            return [(col_idx, valid_rules[0])] + eliminate(new_rules, remaining_columns)

result = eliminate(rules, columns)

six_rules = [int(idx) for idx, rule_name in result if rule_name.startswith("departure")]

print("product", np.prod(np.array(my_ticket)[six_rules]))

Впереди!

Оригинал: “https://dev.to/meseta/advent-of-code-day-16-in-python-b4n”