First part
It took me a while to understand the problem, and it becomes substantially easier upon realising the instructions are not in chronological order. I've divided the problem in smaller tasks, and am first taking care of the input.
Input parsing
I'm still trying to do it functionally. Naturally I thought of parsing the whole input, and separate the attribution of sole values
value 43 goes to bot 90
from the exchanges of values between bots, and bots and outputs.
bot 100 gives low to bot 101 and high to bot 31
bot 148 gives low to output 19 and high to bot 156
These attributions and exchanges will be stored in two separate lists.
In the first list, objects are of the form (value, bot)
, meaning the value
to be attributed to the bot
.
In the second list, objects are of the form (bot, [b/o]number, [b/o]number)
, where bot
stands for the bot handing out values, and each element is a string whose first character is a subject (o
for output or b
for bot) andΒ the rest is the corresponding number; eg: ('34', 'o12', 'b111')
stands for bot 34 gives low to output 12 and high to bot 111
.
This is the code so far:
import re
VALUE_REGEX = r"\b\d+\b"
BOT_REGEX = r"""bot (\d+) gives low to \b(\w)\w* (\d+) and high to \b(\w)\w* (\d+)"""
def input_parser(
attrs: list,
exchgs: list,
input_lines: list[str]
):
if not input_lines:
return attrs, exchgs
else:
line = input_lines[0]
# value 43 goes to bot 90
if 'value' in line:
value, bot = re.findall(VALUE_REGEX, line)
return input_parser(attrs + [(value, bot)],
exchgs,
input_lines[1:])
# bot 200 gives low to bot 174 and high to bot 87
# bot 148 gives low to output 19 and high to bot 156
else:
bot, l_sbj, low, h_sbj, high = re.findall(BOT_REGEX, line)[0]
return input_parser(attrs,
exchgs + [(bot, l_sbj + low, h_sbj + high)],
input_lines[1:])
Now I can move onto giving out the initial values β the attributions.
Attributions
I'm thinking of having a dictionary whose keys are bot IDs and values are empty tuples; as attributions are made, these tuples are substituted by a new tuple with the attribution (we won't ever do mutation of data). Through inspection and some python tricks, I know bot 17 will start with two values (some has to, otherwise there would never be any action).
def make_attributions(
bots: dict,
attrs: list[tuple]
) -> dict:
def produce_new_attribution(
old: dict,
key: int,
value: int
) -> dict:
new = copy(old)
new.update({key: old[key] + (value,)})
return new
if not attrs:
return bots
else:
value, bot = attrs[0]
return make_attributions(
produce_new_attribution(bots, 'b' + bot, value),
attrs[1:]
)
The attributions are done without any major hurdles. Moving onto the exchanges.
Exchanges
Also by inspection, I noticed that bots will only do one exchange, if any. This makes the exercise a little simpler.
To make an exchange, a bot must have two values. So, at any given state of the bot collective (the bots
dictionary), whenever a bot holds two values, it can be matched to its instruction.
def make_exchanges(
bots: dict,
exchgs: list,
bot_pairs: list,
) -> dict:
def produce_new_exchange(
old: dict,
exch: tuple
) -> dict:
bot, taking_low, taking_high = exch
value_low, value_high = sorted(list(map(int, old['b' + bot])))
if (value_low, value_high) == (17, 61):
print(f'the bot is {bot}')
new = copy(old)
new.update({'b' + bot: tuple()})
new.update({taking_low: old[taking_low] + (value_low,)})
new.update({taking_high: old[taking_high] + (value_high,)})
return new
if not exchgs:
return bots, bot_pairs
else:
# get any bot liable for exchanging
bot = list(filter(lambda bot: len(bots[bot]) == 2, bots))[0]
# get the corresponding exchange
match = list(filter(lambda ex: ex[0] == bot[1:], exchgs))[0]
return make_exchanges(
produce_new_exchange(bots, match),
[exch for exch in exchgs if exch != match],
bot_pairs + [[match[0],
match[1][1:],
match[2][1:]]]
)
with open("input") as file_input:
INPUT = file_input.read().splitlines()
attrs, exchgs = input_parser([], [], INPUT)
attributed = make_attributions(
defaultdict(lambda: tuple()),
attrs
)
exchanged = make_exchanges(
attributed,
exchgs,
[]
)
This finishes Part 1. In hindsight, the problem was not so complicated. I'm glad I made the decision to separate bot and output early on, which came in handy for Part 2.
Part 2
print(reduce(lambda x, y: int(x) * int(y),
[exchanged[0]['o0'][0], exchanged[0]['o1'][0], exchanged[0]['o2'][0]]))
The syntax is a little messy due to single value tuples, but the result is correct!