Приход кода 2017, день 8 - Выполнение простых инструкций

В настоящее время я использую Advent of Code проблемы программирования как возможность изучить некоторые python , Следующий код - мое решение для задачи «День 8», где задача состоит в том, чтобы написать программу, которая считывает набор инструкций, которые затем выполняются на регистрах. Регистры начинаются со значения 0. Каждая команда может увеличить или уменьшить значение регистра. Программа должна возвращать максимальное значение в любом регистре в конце выполнения, а также максимум, который должен был иметь любой регистр в течение всего процесса.

Инструкции выглядят следующим образом: <register> <operation> <integer> if <register> <boolop> <integer>. Где register - это имя регистра, operation является либо inc, чтобы увеличить значение регистра, или dec, чтобы уменьшить его значение. boolop - оператор сравнения. Инструкция должна выполняться только в том случае, если условие истинно, например. a inc 10 if b < 0 увеличит значение регистра a на 10, если текущее значение для регистра b меньше 0.

Я прочитал ввод из файла с именем in. Пример ввода можно найти здесь , Ожидаемым выходом для этого ввода будет (2971, 4254).

import re

def is_int(str):
    if str == '':
        return False
    if str[0] in ('+', '-'):
        return str[1:].isdecimal()
    return str.isdecimal()

def value(registers, val):
    if is_int(val):
        return int(val)
    if not val in registers:
        registers[val] = 0
    return registers[val]

def max_register(registers):
    return max([x for _, x in registers.items()])


class Operation:
    def __init__(self, line):
        res = re.search('([^ ]*) ([^ ]*) ([^ ]*)', line)
        self.left = res.group(1)
        self.operator = res.group(2)
        self.right = res.group(3)

    def __repr__(self):
        return self.left + ' ' + self.operator + ' ' + self.right       

    def perform(self, registers):
        l, r = value(registers, self.left), value(registers, self.right)
        if self.operator == 'inc':
            registers[self.left] = l + r
        elif self.operator == 'dec':
            registers[self.left] = l - r
        else:
            print('Unsupported operation', self.operator)


class Condition:
    def __init__(self, line):
        res = re.search('if ([^ ]*) ([^ ]*) ([^ ]*)', line)
        self.left = res.group(1)
        self.operator = res.group(2)
        self.right = res.group(3)

    def __repr__(self):
        return self.left + ' ' + self.operator + ' ' + self.right

    def test(self, registers):
        l, r = (value(registers, self.left), value(registers, self.right))

        if self.operator == '==':
            return l == r
        elif self.operator == '!=':
            return l != r
        if self.operator == '>':
            return l > r
        elif self.operator == '>=':
            return l >= r           
        elif self.operator == '<':
            return l < r
        elif self.operator == '<=':
            return l <= r
        else:
            print('Unsupported condition operator', self.operator)
            return False

class Instruction:

    def __init__(self, line):
        res = re.search('(.*) (if .*)', line)
        self.operation = Operation(res.group(1))
        self.condition = Condition(res.group(2))

    def __repr__(self):
        return str(self.operation) + ' ' + str(self.condition)

    def perform(self, registers):
        if self.condition.test((registers)):
            self.operation.perform(registers)

class Programm:

    def __init__(self, instr):
        self.instructions = instr
        self.position = 0

    def __repr__(self):
        return '@' + self.position + '\n' + str(self.instructions)

    def run_next(self, registers):
        if self.position >= len(self.instructions):
            return False
        self.instructions[self.position].perform(registers)
        self.position = self.position + 1
        return True

    def run(self, registers):
        for _ in range(len(self.instructions)):
            self.run_next(registers)


def parse_programm(file):
    lines = file.read().splitlines()
    return Programm([Instruction(i) for i in lines])

def run(input):
    with open(input) as infile:
        programm = parse_programm(infile)

    registers = {}
    curmax = 0
    while programm.run_next(registers):
        curmax = max(curmax, max_register(registers))
    return (max_register(registers), curmax)

if __name__ == '__main__':
    print(run("in"))

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

11 голосов | спросил Sebastian Stern 13 WedEurope/Moscow2017-12-13T17:11:11+03:00Europe/Moscow12bEurope/MoscowWed, 13 Dec 2017 17:11:11 +0300 2017, 17:11:11

2 ответа


13

Немного nitpicks, возможные улучшения и случайные идеи:

  • if not val in registers: немного менее естественен, поскольку if val not in registers:
  • вы можете заменить if str == '': на if not str:
  • str, однако, не является хорошим именем переменной, так как shadows встроенный str type
  • is_int, хотя, вероятно, можно использовать EAFP - попробуйте преобразовать его в int и обработать возможные ошибки:

    def is_int(value):
        try:
            int(value)
            return True
        except (ValueError, TypeError):
            return False
    
  • max_register можно упростить до:

     return max(registers.values())
    
  • Хорошей практикой является определяют строки регулярных выражений как необработанные строки
  • Я думаю, вы можете распаковать захваченные значения группы в переменные :

    res = re.search(r'([^ ]*) ([^ ]*) ([^ ]*)', line)
    self.left, self.operator, self.right = res.groups()
    
  • Класс Condition может извлечь выгоду из operator . Вместо того, чтобы иметь несколько if /elif s , вы можете определить сопоставление операторных строк с функциями operator:

    import operator
    
    OPERATIONS = {
        '==': operator.eq,
        '!=': operator.ne,
        '>': operator.gt,
        '>=': operator.gte,
        '<': operator.lt,
        '<=': operator.le
    }
    
    l, r = value(registers, self.left), value(registers, self.right)
    return OPERATIONS[self.operator](l, r)
    

    Обратите внимание, что я проверил бы на достоверность operator, когда вы извлечете его из регулярного выражения в __init__. Кроме того, OPERATIONS может, вероятно, быть модулем или константой уровня класса.

  • подумайте о предварительном компилировании регулярных выражений с помощью re.compile() и повторного использования
ответил alecxe 13 WedEurope/Moscow2017-12-13T17:28:47+03:00Europe/Moscow12bEurope/MoscowWed, 13 Dec 2017 17:28:47 +0300 2017, 17:28:47
10

У меня сложилось впечатление, что вы написали слишком много кода.

Я хотел бы призвать вас еще раз взглянуть на ваш дизайн. Регистры называются многобуквенными строками и имеют значение по умолчанию, равное нулю. Для этого есть встроенная структура данных: collections.defaultdict будет обрабатывать это автоматически для вас, если вы используете int в качестве заводской функции:

>>> import collections
>>> registers = collections.defaultdict(int)
>>> registers['abc'] += 100
>>> registers['def'] -= 10
>>> print(registers)
>>> defaultdict(<class 'int'>, {'abc': 100, 'def': -10})

С учетом этого, если вы считаете, что ваша программа работает с единственным объектом - регистрационным файлом - вместо работы с коллекцией Condition и Statement?

def run(input):
    """Evaluate a series of statements of the form:

       register_name [inc|dec] integer if register_name relop integer

    where values for register_name default to zero. Return a tuple, 
    (max_reg, max_ever) containing the maximum value left in any
    register at the end of execution, and the maximum value ever 
    computed during the program.

    """
    curmax = -float('inf')
    registers = collections.defaultdict(int)

    with open(input) as infile:
        for line in infile:
            result = eval_line(line, registers)

            if result is not None:
                curmax = max(result, curmax)

    return (max(registers.values()), curmax)

Я бы предположил, что eval_line может быть одной функцией, используя более подробное регулярное выражение:

instr_re = re.compile(r'(\w+) (inc|dec) (\d+) if (\w+) ([=!><]=|[=!><]) (\d+)')

m = instr_re.match(line)
if m is None:
    return None

target, incop, opnd1, condreg, relop, opnd2 = m.groups()

Если ваше регулярное выражение является правильным, не должно быть проблем с оценкой ваших компонентов:

opnd1 = int(opnd1)
opnd2 = int(opnd2)

relfn = {'==': operator.eq, ...}[relop]
incop = {'inc': operator.iadd, 'dec': operator.isub}[incop]

if relfn(registers[condreg], opnd2):
    return incop(registers[target], opnd1)

return None
ответил Austin Hastings 13 WedEurope/Moscow2017-12-13T19:35:06+03:00Europe/Moscow12bEurope/MoscowWed, 13 Dec 2017 19:35:06 +0300 2017, 19:35:06

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132