You come upon a factory in which many robots are zooming around handing small microchips to each other.
Upon closer examination, you notice that each bot only proceeds when it has two microchips, and once it does, it gives each one to a different bot or puts it in a marked "output" bin. Sometimes, bots take microchips from "input" bins, too.
Inspecting one of the microchips, it seems like they each contain a single number; the bots must use some logic to decide what to do with each chip. You access the local control computer and download the bots' instructions (your puzzle input).
Some of the instructions specify that a specific-valued microchip should be given to a specific bot; the rest of the instructions indicate what a given bot should do with its lower-value or higher-value chip.
For example, consider the following instructions:
5
goes to bot 2
2
gives low to bot 1
and high to bot 0
3
goes to bot 1
1
gives low to output 1
and high to bot 0
0
gives low to output 2
and high to output 0
2
goes to bot 2
Which leads to:
1
starts with a value-3
chip, and bot 2
starts with a value-2
chip and a value-5
chip.2
has two microchips, it gives its lower one (2
) to bot 1
and its higher one (5
) to bot 0
.1
has two microchips; it puts the value-2
chip in output 1
and gives the value-3
chip to bot 0
.0
has two microchips; it puts the 3
in output 2
and the 5
in output 0
.In the end, output bin 0
contains a value-5
microchip, output bin 1
contains a value-2
microchip, and output bin 2
contains a value-3
microchip. In this configuration, bot number 2
is responsible for comparing value-5
microchips with value-2
microchips.
Based on your instructions, what is the number of the bot that is responsible for comparing value-61
microchips with value-17
microchips?
Let us first understand the requirements.
class
.The question is to find out which bot (number) is responsible for handling chips 61
and 17
, which means that both chips are with the bot at the same time.
We model the bot class as:
Bot:
attributes:
number
chips - 0, 1, 2
handle chips:
give low to <>
give high to <>
actions:
take chip:
add chip to chips
handle chips:
if there are two chips:
resolve which is lower and which is higher
give chips to bots/bins specified by the rules
Reading in the rules line by line, we add bots to the bot-list, and check at each iteration if a bot has two chips, whether they are the specified chips, and if yes, we get the answer as the bot number. We maintain two lists, one for the bots, and the other for output bins. The bots will be a dictionary addressed by the bot's number whose keys will be the class object. The output bins be a dictionary whose keys will be lists for holding chips in that particular output bin.
Note: We always maintain the bot's chips in sorted order to
Reading in the input
In [1]:
with open('../inputs/day10.txt', 'r') as f:
data = [line.strip() for line in f.readlines()]
Class Bot to hold the bot instances
In [2]:
class Bot:
def __init__(self, number):
self.number = number
self.chips = []
self.give_low = None
self.give_high = None
def __repr__(self):
return 'number: %s chips: %s low: %s high: %s' % (self.number, self.chips, self.give_low, self.give_high)
Creating the necessary data structures and regex patterns.
The bot instructions each contain a word specifying who the chip is from/going to and the chip number
We use this information to create the instruction_type
structure to hold the type and number of the instruction.
In [3]:
from collections import namedtuple
instruction_type = namedtuple('instruction_type', ('type', 'number'))
import re
bot_instruction = re.compile('(\w+) (\d+)')
input_instruction = re.compile('(\d+)')
bot_list = {}
output_bins = {}
Helper functions
The ensure...
functions are there to help (or clear up the code) when accessing bots and output bins as they are encountered in the instructions. Another (elegant?) way to do this would be to wrap each of the calls to the dictionary in a try...except
and catch IndexError
for invalid accesses.
In [4]:
def ensure_bot_is_registered(bot_number):
if bot_number not in bot_list:
bot_list[bot_number] = Bot(bot_number)
def ensure_bin_is_registered(bin_number):
if bin_number not in output_bins:
output_bins[bin_number] = []
Actions for bots - giving out chips
Each bot will only act when it has two chips, and it will give those two chips according to the rules specified. The function returns True
if the specified action was performed, False
otherwise.
In [5]:
def bot_chip_handler(bot_number):
# retrieve the bot
bot = bot_list[bot_number]
# check if it has two chips
if len(bot.chips) < 2:
return False
# check if it has the specified chip
if 61 in bot.chips and 17 in bot.chips:
print('answer', bot.number)
# check if it has no instructions, which means no actions
if bot.give_low is None and bot.give_high is None:
return False
# give low chip
if bot.give_low.type == 'bot':
# give chip to bot
bot_list[bot.give_low.number].chips.append(bot.chips[0])
bot_list[bot.give_low.number].chips.sort()
else:
# give chip to output bin
output_bins[bot.give_low.number].append(bot.chips[0])
# give high chip
if bot.give_high.type == 'bot':
# give chip to bot
bot_list[bot.give_high.number].chips.append(bot.chips[1])
bot_list[bot.give_high.number].chips.sort()
else:
# give chip to output bin
output_bins[bot.give_high.number].append(bot.chips[1])
# remove chips from bot
bot.chips = []
# return action succesfull
return True
main
Here we take each in the given instructions, identify whether the line is an instruction for the bot, or an action where bots are given/take chips from the input bin. The instruction lines start with bot
and the action lines start with value
.
For bot instructions, the appropriate bot is retrieved from list, its rules are added to it.
For actions, the bot is retrieved and the chip is added to it. The chip handler function is then run to determine what to do with the bot.
At the end of each iteration, we go from each both in increasing order by running the chip handler, and if any bot gives out chip to another bot, we start again, since the bot can give chips to other bots with a number lesser than theirs.
In [6]:
for line in data:
if line.startswith('bot'):
# this is an instruction for the bot
# get the tokens from the line
main_bot, give_low, give_high = [
# the first is a word, the second is the bot/bin number
instruction_type(token[0].strip(), int(token[1]))
for token in bot_instruction.findall(line)]
# retrieve the bot
ensure_bot_is_registered(main_bot.number)
main_bot = bot_list[main_bot.number]
# handle low/high chip rules
# and whether they are for bots or bins
if give_low.type == 'bot':
ensure_bot_is_registered(give_low.number)
else:
ensure_bin_is_registered(give_low.number)
main_bot.give_low = give_low
if give_high.type == 'bot':
ensure_bot_is_registered(give_high.number)
else:
ensure_bin_is_registered(give_high.number)
main_bot.give_high = give_high
else:
# this is an action where bots are given chips from the input bin
# there are only two values - the chip value and the bot number
chip_number, bot_number = map(int, input_instruction.findall(line))
ensure_bot_is_registered(bot_number)
bot = bot_list[bot_number]
bot.chips.append(chip_number)
bot.chips.sort()
# run handler on this bot since it could now have two chips
bot_chip_handler(bot.number)
# run handlers on all bots in an increasing order
# this is an arbitary choice, it is not specified in the input
condition = True
while condition:
condition = False
for bot_number in sorted(bot_list.keys()):
returned_flag = bot_chip_handler(bot_number)
if returned_flag == True:
condition = True
break
We need to multiple one of the values in output bins 0, 1, and 2
. This means either that they contain only one value or that we have to choose only the first one. In any case, we access only the first element in each bin.
reduce(mul, [output_bins[i][0] for i in range(3)])
This is the functional way to write -
n = 1
for i in range 0 to 2 (3 - 1 or i < 3)
n *= output[i][0]
In [7]:
from functools import reduce
from operator import mul
print('answer', reduce(mul, [output_bins[i][0] for i in range(3)]))
== END ==