Ok, I’ll give it a shot. I didn’t realize that it was like a sort of race each day. I’m in the US east coast, so the advent is at midnight, which is a bit later than I should typically stay up.
Did it start already?? I don’t know if I can do many this year, but maybe this will be a good way for me to practice some Kotlin
It just started!
What is the policy on public discussion of these problems?
Edit: I just saw that people are openly discussing even the solutions on reddit, so I’ll go ahead and make some comments with details hidden.
Here's my mild spoiler/hint for part two of day 1
I got stuck a bit part two not quite understanding exactly what was meant.
I guess it would have been clearer if they gave an example like “twone” is 21.
I think all is allowed as soon as the top 100 spots on the global leaderboard are taken.
https://www.reddit.com/r/adventofcode/comments/1883ibu/2023_day_1_solutions/
I struggled for 23 minutes with part 2 only to discover that the answer only differed by 1 from part 1
What do you mean by this? The two answers that I entered differed by 188 (and both were accepted).
Do different people get different input texts?
Yes, the inputs are different from person to person.
Edit: seems like they are drawn from a limited pool though (rather than generated on the fly for each user).
https://www.reddit.com/r/adventofcode/comments/rh0qlz/are_the_inputs_different_for_each_user/
I think I’m not going to worry too much about spoiling anything unless I think of something really clever.
But I guess it's easy enough to hide most of the details
I’m also a beginner at Python/MicroPython so my code is probably going to be really ugly; that will be another reason to comment out some key bits like this:
num_names = ["zero", "one", # ...
def getnums(line):
for i in range(len(line)):
substring = line[i:]
if isdigit(substring[0]):
yield int(substring[0])
continue
# also check num_names and yield the index
# if there's a match...
It was interesting to finally force myself to learn how to make an iterator in Python!
Yebellz’s reasonable confusion didn’t even occur to me; I briefly considered whether any numbers’ names are entirely contained within other numbers’ names or whether any of the names themselves contained digits and decided there wasn’t any possible ambiguity.
Anyway, this difficult challenge called for some serious computing hardware:
Day 2. Regexes are great for this kind of problem. But today I was disappointed to learn that MicroPython does not implement Python’s re.findall
or re.finditer
methods for getting all matches of a regex. I guess I can write it myself for next time, here’s a somewhat dangerous attempt:
def finditer(regex, s):
while len(s) > 0:
result = regex.search(s)
if result == None:
return
yield result
s = s[result.end():]
Also,
Wasn’t it weird that there was no need to distinguish between ,
and ;
even in part two?
Yesterday I ended up googling a lot to make the right regexes, so was happy to get by with just a bunch of .split():s today
My part 2 in python
total = 0
for line in lines:
minAmounts = {"red": 0, "green": 0, "blue": 0}
index, games = line.split(":")
for game in games.split(";"):
for count in game.split(","):
amount, color = count.strip().split(" ")
if int(amount) > minAmounts[color]:
minAmounts[color] = int(amount)
total += minAmounts["red"] * minAmounts["green"] * minAmounts["blue"]
print(total)
This is very funny to me:
https://www.reddit.com/r/adventofcode/comments/188pgqo/2023_day_1_github_copilot_solve_but_with_a_twist/
A bit late to the party, day 1 part 2 solution in Javascript. If it looks like C, that’s because I don’t know Javascript
Javascript
const numbers = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"];
var total = 0;
var inputArray = text.split('\n');
for (var n = 0; n < inputArray.length; ++n){
var row = inputArray[n];
var first = 0;
var last = 0;
var foundfirst = false;
for (var j = 0; j < row.length; ++j) {
if (isNaN(row[j]) == false) {
last = row[j];
if (foundfirst == false) {
first = row[j];
foundfirst = true;
}
}
else {
for (var i = 0; i < numbers.length; ++i) {
if (row.substring(j, row.length).startsWith(numbers[i]) == true) {
let num = i + 1;
last = num.toString(10);
if (foundfirst == false) {
first = num.toString(10);
foundfirst = true;
}
}
}
}
}
total = total + parseInt(first.concat(last));
}
console.log(total);
Day 2 Part 2 again in C style Javascript.
First time doing the AoC. I managed to do the first 50 project Euler problems, but stopped then as they got a bit too mathematically challenging for my taste.
Hopefully the AoC don’t get too difficult, its kind of addictive once started.
Got to see what the ascii art creates.
Day 2 Part 2 Solution in Javascript
var total = 0;
var inputArray = text.split('\n');
for (var n = 0; n < inputArray.length; ++n){
var red = 0; //max 12
var green = 0; //max 13
var blue = 0; //max 14
var row = inputArray[n];
row = row.replaceAll(';', ',');
const str = row.split(":");
var new_row=str[1];
var items = new_row.split(',');
for (var i = 0; i < items.length; ++i) {
if (items[i].match("red")) { if (parseInt(items[i])>red) {red=parseInt(items[i])}; }
if (items[i].match("green")) { if (parseInt(items[i])>green) {green=parseInt(items[i])}; }
if (items[i].match("blue")) { if (parseInt(items[i])>blue) {blue=parseInt(items[i])}; }
}
total = total + (red * blue * green);
}
console.log(total);
Clever way to do it from a reddit post
just replace the colors with their own prime number and do a prime factorization to get the results
Day 3: I feel bad for @yebellz staying up so late to do this!
Day 3
Today I got the chance to use some tricks I learned while implementing go boards Padded the input with extra dots (one extra row on the top, one extra row on the bottom, and one dot replacing each newline character) to remove any edge cases from the adjacency checks.
with open('3.txt') as data:
lines = data.readlines()
w = len(lines[0])
dots = ''.join(['.']*w)
s = (dots + ''.join(lines) + dots).replace('\n', '.')
i = w
total = 0
while i < len(s):
if s[i].isdigit():
j = i + 1
while s[j].isdigit():
j += 1
if len((s[i-w-1:j-w+1]+s[i-1]+s[j]+s[i+w-1:j+w+1]).replace('.', '')):
total += int(s[i:j])
i = j
else: i += 1
print('Part 1:', total)
total = 0
for i in range(len(s)):
if s[i] == '*':
border = ''
for middle in [i-w, i, i+w]:
start = middle-1
while s[start].isdigit():
start -= 1
end = middle+1
while s[end].isdigit():
end += 1
border += s[start:end+1]
numbers = []
k = 0
while k < len(border):
if border[k].isdigit():
j = k + 1
while border[j].isdigit():
j += 1
numbers.append(int(border[k:j]))
k = j
else: k += 1
if len(numbers) == 2:
total += numbers[0] * numbers[1]
print('Part 2:', total)
I’ve been taking a quick and dirty approach, just trying to solve the problems as quickly as possible, while not caring much about clarity, organization, or efficiency.
On day 3, I got hung up on a parsing bug, where I failed to find numbers that were at the very end of the line. That cost me a lot of time (and even lost a point on the private leaderboard), but I caught back up with @Feijoa for part 2, as that was just some minor, quick and dirty modifications.
Below are my scripts for day 3 (lots of redundancy between the two parts, since I just copy-paste the file to start working on part 2). I have not bothered to go back and clean things up, so you’ll find various debugging prints and other inelegant coding practices.
3, part 1
import fileinput
lines = [l.strip() for l in fileinput.input("3input")]
# lines = [l.strip() for l in fileinput.input("3test")]
h = len(lines)
w = len(lines[0])
print(h, w)
def find_numbers(line):
buffer = ""
found = []
start = 0
for (i, c) in enumerate(line):
if c.isdigit():
if len(buffer) == 0:
start = i
buffer += c
else:
if len(buffer):
found.append((buffer, start))
buffer = ""
if len(buffer): # Parse trailing number
found.append((buffer, start))
buffer = ""
return found
def check_hood(buffer, start, row):
left = max(start - 1, 0)
right = min(start + len(buffer), w - 1)
top = max(row - 1, 0)
bottom = min(row + 1, h - 1)
# print(left, right, top, bottom, len(buffer), start, row)
for x in range(left, right + 1):
for y in range(top, bottom + 1):
ch = lines[y][x]
if (not ch.isdigit()) and ch != ".":
print("Found neighbor", ch, "for", buffer, "at", y, x)
return True
return False
total = 0
for (i, line) in enumerate(lines):
print(line)
print(find_numbers(line))
for (buffer, start) in find_numbers(line):
if check_hood(buffer, start, i):
total += int(buffer)
print(total)
In part two, I’m defining “gears” as the list of asterisk symbols adjacent to one or more numbers, and later I’m filtering out those actual gears that have just two numbers.
3, part 2
import fileinput
lines = [l.strip() for l in fileinput.input("3input")]
# lines = [l.strip() for l in fileinput.input("3test")]
h = len(lines)
w = len(lines[0])
print(h, w)
def find_numbers(line):
buffer = ""
found = []
start = 0
for (i, c) in enumerate(line):
if c.isdigit():
if len(buffer) == 0:
start = i
buffer += c
else:
if len(buffer):
found.append((buffer, start))
buffer = ""
if len(buffer): # Parse trailing number
found.append((buffer, start))
buffer = ""
return found
gears = {}
def append_to_gear(number, x, y):
key = str(x) + "x" + str(y)
if key in gears.keys():
gears[key].append(number)
else:
gears[key] = [number]
def set_gears(buffer, start, row):
left = max(start - 1, 0)
right = min(start + len(buffer), w - 1)
top = max(row - 1, 0)
bottom = min(row + 1, h - 1)
# print(left, right, top, bottom, len(buffer), start, row)
for x in range(left, right + 1):
for y in range(top, bottom + 1):
ch = lines[y][x]
if ch == "*":
print("Possible gear at", y, x)
append_to_gear(int(buffer), x, y)
for (i, line) in enumerate(lines):
print(line)
print(find_numbers(line))
for (buffer, start) in find_numbers(line):
set_gears(buffer, start, i)
total = 0
for _, value in gears.items():
if len(value) == 2:
total += value[0] * value[1]
print(total)
It’s especially silly that I needlessly call find_numbers(line)
twice in the main loop (repeated just for a debugging print), but that’s just how lazy I’m being. At the time of throwing it together quickly, it just felt quicker (typing wise) to drop in that redundancy rather than assigning a variable to reuse the value.
For python, I think the following patterns will probably be fairly helpful for many problems:
import fileinput
lines = [line for line in fileinput.input("input")]
import fileinput
for line in fileinput.input("input"):
line = line.strip() # Optional, but may be helpful for some puzzles
Of course, stripping the whitespace may be omitted or replaced with some other operation (like the substitution of a trailing newline with a period, as done by @antonTobi for day 3.
Yeah, staying up late is not so ideal, and eventually, I will have to break this streak of doing so.
However, it seems that the nature of this game is really all about speed, so I feel kind of compelled to work like this.
You can also just consider it a challenge to do them at all.
I’ll also probably stop doing them at night soon too, so you don’t have to worry too much about your leaderboard spot
I’ve been trying to stick to a single pass and keeping memory usage constant without storing all the data in tables. Partly because the board I’m running on only has 264k of RAM, but also because I like the single-pass aesthetic.
So I do something like this:
Day 3, part 2 main loop
for l in f:
line = l.strip()
gear_ratios = get_gear_ratios(...what goes here?...)
for gear_ratio in gear_ratios:
total += gear_ratio
but actually for my single pass to work it needs to look at three lines at a time. And without knowing what line it’s on, I had to pad the beginning and end with a bunch of blanks; here’s the real thing:
last_line2 = '.'*140
last_line1 = '.'*140
def f_plus_1():
for l in f:
yield l
yield '.'*140
for l in f_plus_1():
line = l.strip()
gear_ratios = get_gear_ratios(last_line1, last_line2, last_line1, line)
for gear_ratio in gear_ratios:
total += gear_ratio
last_line2 = last_line1
last_line1 = line
Looking at this now I feel like I’ve been stuck in this last_line = line
looping pattern for my past 30+ years as a programmer. It’s kind of ugly, and I should probably invent some nicer abstraction for that soon.
And then here’s how I get the gear ratios in a single pass. (Note that MicroPython does not even provide me with the isdigit method)
Gear ratios code
def isdigit(c):
return ord(c) >= ord("0") and ord(c) <= ord("9")
def is_symbol(c):
return not isdigit(c) and c != '.'
# returns iterable of tuples for the numbers on the line: (start, end, value)
def get_num_positions(s):
start = -1
for i, c in enumerate(s):
if start == -1:
if isdigit(c):
start = i
else:
if not isdigit(c):
yield (start, i, int(s[start:i]))
start = -1
if start != -1:
yield (start, i+1, int(s[start:i+1]))
# finds gear ratios on `line` (which is actually `line2`), looking for connected
# numbers on the other lines
def get_gear_ratios(line, line1, line2, line3):
nums = list(get_num_positions(line1)) +\
list(get_num_positions(line2)) +\
list(get_num_positions(line3))
for i, c in enumerate(line):
if c != '*': continue
connected_numbers = []
for num in nums:
if num[0]-1 <= i and i < num[1]+1:
connected_numbers.append(num[2])
if len(connected_numbers) == 2:
yield connected_numbers[0]*connected_numbers[1]
All those +1
s and -1
s really confused me and I ended up having the same end-of-line bug as you as well as missing things on the last line, but I had enough print()s all over that it was pretty quick to resolve the issues.
I also still need to get around to writing some menu system so I can re-run any of my programs when I feel like it.
Like how it seems like everyone has their own way of solving these.
Can’t understand them though as my python skillz are long gone.
Content with brute forcing whatever first idea hits me.
And if that doesn’t work then recursion.
And if that doesn’t work then …
Got stuck a bit with learning how to do things in JS and debugging. A couple of hours to do part 1 and another hour for part 2.
Upwards and onwards to go get that elf water.
Day 3 part 2 - JS solution
function isGear(item) {
const notSymbol = ['0','1','2','3','4','5','6','7','8','9','.'];
for (let i = 0; i < notSymbol.length; i++) {
if (item == notSymbol[i]) { return 0; }
}
if (item == '*') { return 1; }
return 0;
}
function enginePart(x, y) {
var val = 0;
let n = 0;
let m = 0;
let place = "";
if (isGear(grid[x+1][y])) { val+=1; n = x + 1; m = y; }
if (isGear(grid[x-1][y])) { val+=1; n = x - 1; m = y; }
if (isGear(grid[x][y+1])) { val+=1; n = x; m = y + 1; }
if (isGear(grid[x][y-1])) { val+=1; n = x; m = y - 1; }
if (isGear(grid[x+1][y+1])) { val+=1; n = x + 1; m = y + 1; }
if (isGear(grid[x-1][y-1])) { val+=1; n = x - 1; m = y - 1; }
if (isGear(grid[x+1][y-1])) { val+=1; n = x + 1; m = y - 1; }
if (isGear(grid[x-1][y+1])) { val+=1; n = x - 1; m = y + 1; }
if (val == 0) { return 0; }
return 'y' + n.toString(10) + 'x' + m.toString(10);
}
var row = text.split('\n');
var grid = [];
var blankRow = '';
for (i = 0; i < 142; i++){
blankRow = blankRow + '.';
}
grid[0] = blankRow;
for (let i = 0; i < row.length; i++) {
grid[i + 1]= '.' + row[i] + '.';
}
grid[141] = blankRow;
var myMap = new Object();
var total = 0;
for (let i = 0; i < grid.length; i++){
var num = "";
var isPart = false;
var gearIndex = "";
for (let j = 0; j < grid[i].length; j++){
if (isNaN(grid[i][j]) == false) { // it is a number
num = num.concat(grid[i][j]);
let gI = enginePart(i,j);
if (gI) {
isPart = true;
gearIndex = gI;
}
}
else {
if (num != "" && isPart == true) {
if (myMap[gearIndex]) {
total += myMap[gearIndex] * num;
}
else {
myMap[gearIndex] = num;
}
}
num = "";
isPart = false;
}
}
}
console.log(total);