#AoC 2023 | Day 3 | Solutions & Spoilers
1810 messages · Page 2 of 2 (latest)
"easy"
I think thats what I made roughly
So docstrings are literally strings you find at the top of your file, classes, and functions.
Oh is it the """ """
def get_number(seen,x,y):
""" this uses regular expressions to find all the digits that are around x,y
and turns them into a number and returns it, it also updates a set of seen
coordinates to avoid double chekcing the same number"""
b = ''.join(re.findall('\d+$', aocdata[x][:y]))
a = ''.join(re.findall('^\d+', aocdata[x][y:]))
for z in range(y-len(b),y+len(a)): seen.add((x,z))
return int(b+a)
I think I have a problem of overcommenting because when I started learning I was always worried I'd forget what something was for and delete it
"started learning" makes it sound like ive been doing thius for awhile. I think I started at the end of Oct
You're in good shape then for that short a time frame, keep at it, and ask lots of questions. And while you're doing aoc make sure to look at all the other solutions for ideas.
Yeah ive been trying to use this a learning opportunity to see what others do
Its been alot of fun so far!
Speaking of code review
@cyan needle would you say that these are too long?
Just don't get caught up if you see something and think "I can't do that" just add a "yet" at the end, you'll get there.
Oh yeah, I love trying to figure out the more complicated stuff with the skills I do have, and then going over it to make it more effiecient! My big project is a spotify backup tool that logs and backs up all changes to your liked songs. Still learning SQL and API requests for that
I might not be the best person to ask that. I'm not a big fan of anything I can't just take in at a glance. Like there's so much text for random_string() that it might be getting in the way of just a simple explanation.
On the other hand, you'll encounter people that love that much verbosity.
, hi, that's me
if you couldn't tell
I did
That is nice
I had fun filling out every little element for Sphinx to pick up on
amazing documentation
sweet
Have you considered edge cases? (literally). Like a number on the right or bottom edge of your input?
this is the core of my code, just trying to build a list with the values and whether we are going to sum them or not
def part_one(schematic):
map = np.array([list(line.strip()) for line in schematic])
h,l = map.shape
digits = ['0','1','2','3','4','5','6','7','8','9']
symbols = ['*','#','$','+']
num = ''
nums = []
for (x,y), char in np.ndenumerate(map):
to_sum = False
if char in digits:
num += char
for r in [-1,0,1]:
for c in [-1,0,1]:
if x+r in range(0,h) and y+c in range(0, l):
if map[x+r][y+c] in symbols:
to_sum = True
else:
if num:
nums.append((int(num), to_sum))
num = ''
print(nums)
Even with the simplest example like 123# it recognizes the symbol but to_sum is always False no matter what. Probably some issue related to going way deeper in for loops than your supposed to
that be a lot of nesting
7 indent gang 😎
just messed something up in the logic because of that
at least if i counted right
mobile is tough
7 looks right
if 0 > (y + i) or (y + i) >= len(grid) or x + j < 0 or x + j >= len(grid[0]):
continue
``` i mean this should just skip if its out of bounds
have you tested it?
i've got something similar to this
this shouldn't be an issue
https://github.com/vetsin/aoc/blob/main/2023/day3-py.ipynb how insane is this
if I add some debug prints it finds all the data right but still has to_sum as False given 467#
found symbol # next to 7
[(467, False)]
do you add that under the if where you set it to true?
Oh, its because to get to the else branch it gets set to False before that
for (x,y), char in np.ndenumerate(map):
to_sum = False
if char in digits:
...
else:
all fixed
but with cursed nesting
actually still broken because of cases where it rolls over to the next line
not sure why the newline isnt catching that
don't share inputs. aoc explicitly asks you not to.
ah
paste your code instead and I can check it on my input. but that number sounds close.
it is. i just checked with some code i stole from here. 957 difference
@strange niche I recommend using the example input and check if it works.
it does
Yeah also had this issue.. the example input is kinda bad.
well apparently i'm missing a lot more then I thought
if the example works, make sure you're checking the last column as wellm as the example doesn't test that.
i'm curious why people are having bugs with the last column. i didn't encounter that
do numbers wrap or are they separate?
I was incrementally building up numbers and then adding to total when I saw first non-number digit
Doesn't work when you're on the last column
ah. the falling edge thing
probably an off by one error when checking numbers. I also saw some cases where people were looking for idx-1 at the start, and -1 wraps.
can someone posto a working solution for part b
Yeah, classic running-total mistake
i didn't see an indication we needed to wrap ¯_(ツ)_/¯
so just checked. yes I'm 967 off... missing some numbers
Not in python, but feel free to take inspiration: https://github.com/mustafaquraish/aoc-2023/blob/master/src/03.oc
wrapping as in donut topography?
i parsed the numbers with
-- [((start-ind, end-ind), num)]
-- inclusive indexes
-- Extract all numbers with start and end index
findNumbers :: String -> [((Int, Int), String)]
findNumbers line = foldl solve [] (zip [0..] line)
where solve acc (i, c) = if isDigit c
then case acc of
[] -> [((i, i), [c])]
all@(((s, e), num) : xs) -> if e == i - 1
then ((s, i), num ++ [c]) : xs
else ((i, i), [c]) : all
else acc
-- [(Length, X, Y)]
numParse :: [String] -> [((Int, Int, Int), Int)]
numParse inp = zip [0..] (map findNumbers inp) >>= broadcastRow
where broadcastRow (r, nums) = map (\ ((start, end), num) -> ((end - start + 1, start, r), read num)) nums
That's been used in the past. Oblate Toroids are my favorite breakfast.
Why are we talking about mugs?
i updated the number after every character anyway, so i didn't run into any falling edge shenanigans i guess
I stole some of my code from my implementation of game of life. just had to remove the wrap around
So bizaarely your input exposed a flaw in one of my methods. I run 5 different ways. and they're all in sync for my input and the test inputs, but one of them was not in sync for yours.
interesting. well I should sleep. good night.
what language is that?
Aha. So there was an assumption that the input did not have symbols with 2 adjacent numbers that were not * but your input does not follow that. So need to check for * after all.
My own one, ocen
did you paste a link to your code?
!paste a link to your code and some of us might look at it and see something you missed.
If your code is too long to fit in a codeblock in Discord, you can paste your code here:
https://paste.pythondiscord.com/
After pasting your code, save it by clicking the Paste! button in the bottom left, or by pressing CTRL + S. After doing that, you will be navigated to the new paste's page. Copy the URL and post it here so others can see it.
can you explain this code for me. I don't quite understand
Sure, which part?
so board is the input file.
chars is a dictionary of the symbols with empty lists
then he goes through the board looking for numbers, and when he finds them adds them to the symbol's list in chars
finally he prints part1 and part2 using the data in that dictionary
that might just be my problem. going to check if the split works
nope splits fine. well not sure what I do wrong

Why'd you change the name?
oh I just rewrote a new one
this time its even better™
just start off simple, i was scared for the longest time because i kept thinking of all the complicated things
for instance, write a language that literally just lets you do
let x = 1
let y = 2
build up from there
i will once again link the example pico-language i made in an hour https://gist.github.com/dawnofmidnight/c61c1efc400a671fe17dc293fd5f2694
and crafting interpreters is always good for a proper guided approach
yeah, looks like a really nice starting point
wow, me 5 months ago came up with the same term i just did
double check your code to see how it handles single digit numbers.
(hint hint)
Huh. Yeah that might make problems
for your input the 46th and 47th numbers are 1 and 4, and you skip them. (and presumably others)
I think there might also be a problem if I have two times the same number in the row
I tested that and it seemed to work ok
(using your code)
Weird. Because it would always find the first instance of that number in the row. So presumably error when you have one digit numbers
is weird, your code works for my modified testdata.
467..50+50
...*......
..35..633.
......*100
617*......
.....+.58.
....2.....
......755.
...$.*....
.664.5....
50.+50 should error
467.50.+50
...*......
..35..633.
......*100
617*......
.....+.58.
....2.....
......755.
...$.*....
.664.5....
here try this:
467..50+50
...*......
..35..633.
......*100
617*......
.....+.58.
2.2.2.....
......755.
...$.*....
.664.5....
you don't find the correct '2' on the line with the bad 2s
Yes that makes sense. I should replace the number each time I do find with spaces
Or "." I guess. Then it should be fixed
So there I was, wanting to use regex for all the days. I thought, "This should be easy." Then the fact that all lookbehinds must be fixed width existed. So now I present probably the worst way of doing part 1. Still haven't looked at part 2, and I am very scared. ```py
import re;d,g,h,t=r"[^\d\n.]","(?<=%s%s)%s","|%s(?=%s%s)",open(0).read();l=len(t.split("\n")[0]);r="(?s)"+"|".join("|".join(g%(a:=(d,f".{{{i+l-j-1}}}",(e:=f"(\d{{{j+1}}})")))+h%a[::-1] for i in range(j+3))+"|"+g%(p:=(d,"",e))+h%p[::-1]for j in range(3)[::-1]);print(sum([int([*filter(None,x)][0]) for x in re.findall(r,t)]))
I probably would have pulled my hair out if not for regex101.
That's both cool and awful at the same time. 🙂
... there's no way that regex works? to solve the puzzle?
looks like it works for part 1. Good luck with part 2
You could also consider saving the index where you found a number:
numrowssearchlist = [
"".join([char if char.isdigit() else " " for char in line]) for line in lines]
modify that to store the number AND it's location.
I got the right answer, so https://cdn.discordapp.com/emojis/811812701244358668.webp?size=48&name=Shrug&quality=lossless . It's conceptually the same as (?<=[^\d\n.].{1,4})(\d)|(?<=[^\d\n.])(\d)|(\d)(?=[^\d\n.].{1,4})|(\d)(?=[^\d\n.]) just massively complicated by the fact that lookbehinds must be fixed lengths, I threw in arbitrary line length size so it could also work for the example, and there are 3 different number sizes to account for.
Don't I already do that? Should be in in indexesandlength
I store number (as a string but still) row, start of number and length
No, I think that does a find? so it finds the first occurance? not the original location ?
I might be misreading it
It finds the index and then stores it. But I'm not sure how I am supposed to get that otherwise. Aim was to get the start of the number in the current line. And I screwed up because duplicates interfere. So I'll have to do a replace of the number on the line but just once
i originally thought part 2 would be some more difficult math due to the existence of the other symbols, +, -, / i think were there
looking forward to day 4, but does it progress in difficulty?
something like that
it fluctuates, weekends are typically harder, unless it's the first few days.
so this weekend not too bad. next weekend might be mad at us.
here try this:
indexesandlength = []
for rownum,line in enumerate(numrowssearchlist):
index = 0
while index<len(line):
num = ''
start = index
while index<len(line) and line[index].isdigit():
num+=line[index]
index += 1
if num:
indexesandlength.append((num, rownum, start, len(num)))
index += 1
print(indexesandlength)
should correctly put the indexes you want into indexesandlength and then your code works correctly for your input.
would be a bit easier with regex.
i'm in awe damn
why do you even have a util for this? 🥴
I had a shit ton of duplicate code that I was trying to consolidate, and this was some low hanging fruit
It also gives me a really simple package to test tools on.
For example, it was my first PEP 517 compliant package, and it’s where I learned most of what I know about Sphinx.
I mean I guess it's nice for practice with that
but as a util it's pretty 🥴 to me
for some reason it reminds me of something like
# Increments the value of x by the integer one, resulting in a value of 1 more than the current value of x.
x += 1
I mean, having a util module that you just add generalized functions to in case you need to yoink em for future projects is actually incredibly useful
Anyway, here's my initial solution for p2:
import re
from typing import (
Dict,
Tuple,
List,
Set,
)
import math
def f(prompt: str) -> int:
parts: Dict[Tuple[int, int], int] = dict()
gears: List[Tuple[int, int]] = list()
# populate
for y, line in enumerate(prompt.strip().splitlines()):
for m in re.finditer(r"\d+|\*", line):
if m.group() == "*":
gears.append((m.start(), y))
continue
value = int(m.group())
for x in range(m.start(), m.end()):
parts[(x, y)] = value
# process
total: int = 0
for gear_x, gear_y in gears:
neighbors: Set[int] = set()
for y in range(gear_y - 1, gear_y + 2):
for x in range(gear_x - 1, gear_x + 2):
if (x, y) in parts:
neighbors.add(parts[(x, y)])
if len(neighbors) >= 2:
total += math.prod(neighbors)
return total
I mean that's the thing, at some point something is too simple to be a util
Ohhhh yeah, a lot of the functions in the util that was linked are pretty useless - I thought you were talking about util files in general
extracting general patterns is for sure useful
I found an entire function dedicated to:
for substring in substrings:
text = text.replace(substring, "")
lmfao
there are interesting things you can improve on for a "ReplaceAll" style function
There’s one of those in my package too!!
no, questions like what should something like this do?
ReplaceAll("abracadabra", [("ab", "blah"), ("abraca", "blahblah"), ("cadabra", "bleh")])
ahh
common prefixes, overlapping patterns
if you don't allow overlaps other than prefixes you can actually do something nice by just sorting replacements in reverse
but for overlaps things get hairier
Input
with open('day3/input.txt','r') as f:
inputlines = f.read().splitlines()```
**Part One**
```py
symbols = "! @ # $ % ^ & * / + - = ".split()
r_length = len(inputlines)
c_length = len(inputlines[0])
numbers = []
parts = []
for r_index,row in enumerate(inputlines):
for c_index, char in enumerate(row):
if char in symbols:
for r_offset in range(-1,2):
for c_offset in range(-1,2):
r,c = r_index+r_offset,c_index+c_offset
if r < 0 or c < 0:
continue
if r >= r_length or c >= c_length:
continue
if inputlines[r][c].isdigit():
part = inputlines[r][c]
p_index = (r,c)
for offset in [1,2]:
if inputlines[r][c-offset].isdigit():
part = inputlines[r][c-offset] + part
p_index = (r,c-offset)
else:
break
for offset in [1,2]:
if inputlines[r][c+offset].isdigit():
part = part + inputlines[r][c+offset]
else:
break
part = (int(part),p_index)
if part not in parts: parts.append(part)
ans = sum([part[0] for part in parts])
print(ans)```
Part 2
r_length = len(inputlines)
c_length = len(inputlines[0])
ratios = []
for r_index,row in enumerate(inputlines):
for c_index, char in enumerate(row):
if char == '*':
parts = []
for r_offset in range(-1,2):
for c_offset in range(-1,2):
r,c = r_index+r_offset,c_index+c_offset
if r < 0 or c < 0:
continue
if r >= r_length or c >= c_length:
continue
if inputlines[r][c].isdigit():
part = inputlines[r][c]
p_index = (r,c)
for offset in [1,2]:
if inputlines[r][c-offset].isdigit():
part = inputlines[r][c-offset] + part
p_index = (r,c-offset)
else:
break
for offset in [1,2]:
if inputlines[r][c+offset].isdigit():
part = part + inputlines[r][c+offset]
else:
break
part = (int(part),p_index)
if part not in parts: parts.append(part)
if len(parts) == 2:
ratios.append(parts[0][0] * parts[1][0])
ans = sum(ratios)
print(ans)```
Struggled a lot with this one and making it actually efficient/clean, couldn't manage to, would appreciate tips!
e.g. depending on how you define behavior this could bite you
ReplaceAll("abra cablahbra", [("abra", "blah"), ("blah", "abra")])
I think a usual choice is to greedily pick and consume the earliest and longest substring appearing
does this code actually work in general? 
the offset thing feels like it would break for something like
12345.
.....+
It only works for 1, 2 and 3 digit numbers
yeah
that could be changed pretty easily by just incrementing the offset by one and then breaking when you see a non digit char
this was just easier
while thing[offset].isdigit():
...
Yeah exactly
I think this also breaks
.1
+.
with an indexing error
(and unintentional wraparound in the mirrored case)
r,c = r_index+r_offset,c_index+c_offset
if r < 0 or c < 0:
continue
if r >= r_length or c >= c_length:
continue```
this*
second for loop with [1, 2]
can index outside the grid afaict
hm
it does seem like that yeah, I'm surprised that didn't come up in the input at all
like the case never occured
I would probably have gone for this kind of pattern if I had wanted to do this
start = ...
while start-1 > 0 and s[start-1].isdigit():
start -= 1
end = ...
while end < length and s[end].isdigit():
end += 1
your_string = s[start:end]
I guess another thing, you could just use a set for parts in part 1 which would avoid doing an expensive in check in the list
if you want unique elements then that's what set is kinda for
ok, so i rewrote my code so it is less egregiously nested, still running into the wrapping issue where:
23.$456
789....
returns 456789 because enumerate just keeps chugging along
def is_part(map, idxs):
symbols = ['*','#','$','+','-', '=', '/', '@', '%']
h,l = map.shape
for idx in idxs:
x,y = idx
for r in [-1,0,1]:
for c in [-1,0,1]:
if x+r in range(0,h) and y+c in range(0, l) and map[x+r][y+c] in symbols:
return True
return False
def part_one(schematic):
map = np.array([list(line.strip()) for line in schematic])
digits = ['0','1','2','3','4','5','6','7','8','9']
num_str = ''
num_idxs = []
sum = 0
for (x,y), char in np.ndenumerate(map):
if char in digits:
num_str += char
num_idxs.append((x,y))
else:
if num_str and is_part(map, num_idxs):
sum += int(num_str)
num_str = ''
num_idxs.clear()
print(sum)
ARGH
another goddamn day where I forget something simple and have to dig through the big dataset to figre out what the fuck I forgot
It was stupid and simple but that doesnt' mean I'm not still salty
I forgot to terminate parsing a number at the end of a line
Oh I did that too
the example doesnt' have any numbers taht reach the end of the line -.-
pad and forget about edge cases 😌
I built something to print out a color-coded dump of the stuff and that revealed it
It wouldn’t be fun if they covered everything
I blew up mine on part 2
Because I’m a dumbass I used the numbers as dict keys
Turns out they aren’t unique
I caught that one early
trying to do the drawing
turns out one of my first numbers was a dupe and I was like "oh.. great."
ah, I did a groupWith isDigit so no weird edge cases
so then I just stored [(word, coordinates)]
🥴 that would have been a good idea
thankfully I don't even know how to spell hashmap in haskell
i don't think they have hashmaps builtin. Map is a search tree
findNumbers line = foldl solve [] (zip [0..] line)
where solve acc (i, c) = if isDigit c
then case acc of
[] -> [((i, i), [c])]
all@(((s, e), num) : xs) -> if e == i - 1
then ((s, i), num ++ [c]) : xs
else ((i, i), [c]) : all
else acc
``` i kinda implemented my own groupby here 🥴
with part 2
it's like a christmas tree 🥺
maybe color non-"*" differently?
my part1 that sadly did not generalize well for part 2
https://github.com/algmyr/aoc-2023/blob/main/day03/main.hs
Mm yeah that would have been better
oh right. yeah, I can do that..
Oh yeah I did forget about sets so ty I'll remember that for next time
as usual, ignoring the fine details things look half sane
part1 lines = sum numbers
where
numbers = extract_valid_numbers grouped
grouped = concat $ map group_if_digits $ grid_zip lines symbol_adjacent
symbol_adjacent = expand_mask symbols
symbols = grid_map (\c -> c /= '.' && not (isDigit c)) lines
i had fun with comprehensions generating the surrounding chars
i guessed we would need to do something with the symbols since some of them looked like math operations and got lucky
day03/main2.hs lines 21 to 29
parse_line' digits (x:xs) | isDigit x = parse_line' (x:digits) xs
| x == '.' = to_ints digits++empty:parse_line' [] xs
| x == '*' = to_ints digits++gear :parse_line' [] xs
| otherwise = to_ints digits++other:parse_line' [] xs
parse_line' digits [] = to_ints digits
parse_line digits = parse_line' "" digits
parse :: [String] -> [[Int]]
parse grid = map parse_line grid```
i really like the ' tbh. saves me from needing to add _helper like in other langs
oh that kinda looks like what i did
TLDR it'd be annoying to plump it through so I'm not going to
huh. what are you using currently to do the coloring?
A grid of characters that I edit the colors of before printing
like, how do you edit though
by giving them a color
like, manually ?
I have a list of numbers, a list of part numbers
I loop through the part number and assign each matching character slot a color
The irritating part is I don't save the gear-making character
In order to mark them, I'd have to pass along their coordinates and the character through with each gearset, which gets complex enough I should probably use a dataclass instead of the tuple that's already there
I enjoyed the fact that you can just do local reasoning for part 2
i.e. you can look at all 3x3 neighborhoods independently
but you still have to map stuff back to a number which might span a neighborhood 🤔
you saw my parsing
I have the number info there already
12..
..*7
[12, 12, 0, 0]
[ 0, 0, -2, 7]
that kind of thing
ohh. i read it wrong
(and I pad my stuff, so this would be surrounded by a frame of 0s)
it puts the full int value in all the cells yeah
that's the weird part of the parsing
also you could write parse = map parse_line i think. and same with parse_line
yeah
my linter keeps telling me to do stuff like this but idk if it's more readable that way
are these are the symbols used?
['*','#','$','+','-', '=', '/', '@', '%']
you could just check whether it's ., a digit, *, or anything else
c != '.' and not c.isdigit()
i made the mistake of manually typing out all the ones i could find at first 😩
i have no idea why i did that. must have been tired or something 🥴
too tired to be lazy
i think i thought they would all just be symbols that you could get by shift-[0-9] on my keyboard?
that got me a wrong answer on part 1 lol
i like this. i think it's best and safest to just do what you know it can't be
something gotta love regex for too
@dim frigate coding without LSP is an interesting blast from the past
lots of searching on hoogle and elsewhere today
so if the test case passes, but the puzzle input is too small, what factors could be at play? already went through the debugger and the logic works fine. the input is too small which would mean certain valid numbers are not being accepted for whatever reason
i ended up using numpy for some reason because i saw 2d arrays which became redundant, so i probably should have, but its kind of late now lol
think about all the cases that can exist
honestly haven't had LSPs working for my programming for almost 2 years now
copilot / codewhisperer kinda make up for it
for my personal stuff my language doesn't have an LSP, for work stuff our code setups are such a nightmare setting up an LSP is it's own whole thing so I've just given up
raw-dogging programming
numbers at edge of matrix. symbols at edge of matrix. multiple symbols to a number. multiple numbers to a symbol. symbols with no numbers. bumbers with no symbols. etc
come up with your own sample input
something small enough to do by hand to verify your code does what you intend
i.e. write a test suite
I ended up writing a few cases to debug part 2
(I had forgotten to even deal with number on the same row as the gear)
i probably should do a tests.py but i've been lazy and prefer just raw dog getting the solution
like, I thought I added it, but apparently I forgot
I don't write my own tests unless I get it wrong 😛
^
I'm curious, what's the general approach you all took for part 2? Mine was:
gear_map: Map<gear, list of numbers>
loop over the grid:
for current number, keep track of adjacent gears
after number is done, add the number to the list for the adjacent gears in gear_map
for each gear in gear_map:
if we have 2 numbers, add prod to the total
not sure if this is too basic, but I made something like
..12...
....34$
56@.+78
.......
$100$..
.....4.
which covers all of the scenarios you mentioned
looks correct when i calculate, will try to come up with more scenarios
for all 3x3 neighborhoods, if there is a gear in the middle, collect all the numbers around it
filter to all with 2 numbers
product, sum
uh yeah about that
theres & too
partTwo inp = sum [product v | v <- Map.elems counts, length v == 2]
where counts = Map.fromListWith (++) $ nums >>= surroundingChars
surroundingChars ((length, x, y), n) = [(p, [n]) | (p, c) <- adjacentSymbols sm length x y]
sm = symbolMap inp
nums = validNumbers sm $ numParse inp
``` for all numbers find adjacent symbols, if one's a gear then make a tuple with (location of gear, number) then join these into a map from gear to number by concatenating the lists
learn to be lazier 😛
collect all the numbers around it
How do you "collect all the numbers"? Do you pre-parse the numbers and store them in a lookup table? Or do you parse the numbers on the fly by "expanding" any digits you see in the 3x3 neighbourhood?
i had the logic right the whole time, was just missing that symbol
so that was fun!
I replaced every digit with its number value (the full value including its neighbours) with a counter to keep non-together numbers unique, and then for p2 I just iterated over all the symbols and took every set that had two numbers in it, and then summed their products
pre-parse #1180738420272799814 message
oh that's like what fenix did
ah I see, that's smart
I was zoned-in to only thinking of the grid as a grid of characters
sounds kinda similar to mine
minus being completely imperative lol
i ditched the chars and just worked with the numbers and symbols by positions
I worked with a grid, which is painful in haskell
yeah, i wanted to avoid that lol
yeah my grid was just set as &Vector<str> so didn't even think to change the str to be a nested list
skill issue
&[str] pls
don't have slices in my own lang
oh lol, I read that as rust
it's kinda-rust syntax but C semantics basically
nope
basically my thought process was I really like the precise control and freedom you get to do whatever the hell you want in C, but syntactically it's kind of a mess, and don't even get me started on header files and the whole (lack of) a proper module system and namespacing
so i just made that
do you have generics and RAII and stuff
RAII my beloved
i have basic templates (a la C++), no fancy traits or whatever
no RAII
once again, C-style programming
C style sadness
RAII is nice, but not what I was going for
there's a defer statement which you can kinda use, but that's about it
what's that do
"do this at end of scope"
interesting
or whenever you exit the scope
let f = File::open(...)
defer f.close()
if (...) return // closes file
return // closes file
it still leaves doing stuff to the programmer, and I don't trust people with cleanup 😛
good thing that no one else uses my language then 😅
well uh... it works?
def find_gear(map, idxs):
h,l = map.shape
for idx in idxs:
x,y = idx
for r in [-1,0,1]:
for c in [-1,0,1]:
if x+r in range(0,h) and y+c in range(0, l) and map[x+r][y+c] == '*':
return (x+r,y+c)
return None
def part_two(schematic):
map = np.array([list(line.strip()) for line in schematic])
digits = ['0','1','2','3','4','5','6','7','8','9']
num_str = ''
num_idxs = []
sum = 0
gears = defaultdict(list)
for (x,y), char in np.ndenumerate(map):
if char in digits:
num_str += char
num_idxs.append((x,y))
if y == map.shape[1]-1:
if find_gear(map, num_idxs): gears[find_gear(map, num_idxs)].append(int(num_str))
num_str = ''
num_idxs.clear()
elif num_str and find_gear(map, num_idxs):
gears[find_gear(map, num_idxs)].append(int(num_str))
num_str = ''
num_idxs.clear()
else:
num_str = ''
num_idxs.clear()
for g in gears:
if len(gears[g]) == 2: sum += math.prod(gears[g])
print(f"part two sum: {sum}")
my solution for this one probablly could have been better
you're indexing out of bounds presumably
the way I made it, it knows it's found the end of a number by checking if the next character is not a number
I was too lazy to try to add some logic for that so I just did this
yeah, that can break at end of rows
oh man, that could have been happening to every row
i didn't actually think of that
amazing it worked though
depending how you read lines maybe you were saved by newline characters
i believe splitlines does remove the newlines
it does
it seems somehow there are some lines that do have numbers on the end
how it managed to get those, idk
unless they don't have symbols adjacent and didn't affect the output
nope, they do
strange
lemme just contribute my code before the next day starts
import re
with open("input.txt", "r") as file:
inp = file.read().split("\n")
part1 = 0
inp = ["."*len(inp[0])] + inp + ["."*len(inp[0])]
for index, line in enumerate(inp):
inp[index] = "." + line + "."
pattern = re.compile(r"\d+")
for x, line in enumerate(inp):
for match in pattern.finditer(line):
start, stop = match.span()
surroundings = inp[x-1][start-1:stop+1] + line[start-1] + line[stop] + inp[x+1][start-1:stop+1]
if re.findall(r"[^\d\.]", surroundings):
part1 += int(match.group())
print(part1)
part2 = 0
for x, line in enumerate(inp):
for match in re.finditer("\*", line):
gear_start, gear_stop = match.span()
nums = []
for row in (1, -1):
for num_match in pattern.finditer(inp[x+row]):
num_start, num_stop = num_match.span()
if gear_start-1 <= num_start <= gear_stop or gear_start <= num_stop <= gear_stop+1:
nums.append(num_match.group())
for num_match in pattern.finditer(line):
num_start, num_stop = num_match.span()
if num_stop == gear_start or num_start == gear_stop:
nums.append(num_match.group())
if len(nums) == 2:
part2 += int(nums[0]) * int(nums[1])
print(part2)
https://paste.pythondiscord.com/UL6A I just added that one line for the replace. It works but man is it ugly
Part 1```py
lst = [x.replace("\n", "") for x in open("2023/day3/data.txt", "r").readlines()]
tot, used_nbrs = 0, []
for i, x in enumerate(lst):
for v, y in enumerate(x):
if not y.isdigit() and y != ".":
for p in ((-1, 0), (-1, -1), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)):
row = lst[i + p[0]]
if row[v + p[1]].isdigit() and (i + p[0], v + p[1]) not in used_nbrs:
new_int = row[v + p[1]]
for dir in (1, -1):
for l in range(1, len(row) - v - 1):
cur_ind = v + p[1] + dir * l
if row[cur_ind].isdigit():
used_nbrs.append((i + p[0], cur_ind))
new_int = new_int + row[cur_ind] if dir > 0 else row[cur_ind] + new_int
else:
break
tot += int(new_int)
print(tot)
Part 2: ```py
lst = [x.replace("\n", "") for x in open("2023/day3/data.txt", "r").readlines()]
tot = 0
for i, x in enumerate(lst):
for v, y in enumerate(x):
if y == "*":
ind, nmbs = [], []
for p in ((-1, 0), (-1, -1), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)):
row = lst[i + p[0]]
if row[v + p[1]].isdigit() and (i + p[0], v + p[1]) not in ind:
new_int = row[v + p[1]]
for dir in (1, -1):
for l in range(1, len(row) - v - 1):
cur_ind = v + p[1] + dir * l
if row[cur_ind].isdigit():
ind.append((i + p[0], cur_ind))
new_int = new_int + row[cur_ind] if dir > 0 else row[cur_ind] + new_int
else:
break
nmbs.append(int(new_int))
if (len(nmbs) == 2):
tot += nmbs[0] * nmbs[1]
print(tot)
puzzle_a.py
from typing import Iterable
from common import Schematic, SchematicNumber, get_schematic
def main() -> None:
schematic = get_schematic()
part_numbers = find_part_numbers(schematic)
part_numbers_sum = sum(part_number.number for part_number in part_numbers)
print(f'The sum of part number for {schematic.file_name} is {part_numbers_sum}')
def find_part_numbers(schematic: Schematic) -> Iterable[SchematicNumber]:
symbol_locations = {symbol.location for symbol in schematic.symbols}
for number in schematic.numbers:
if symbol_locations & number.bounding_box:
yield number
if __name__ == '__main__':
main()
puzzle_b.py
from collections import defaultdict
from typing import Iterable
from common import Schematic, get_schematic
def main() -> None:
schematic = get_schematic()
gear_ratio_sum = sum(get_gear_ratios(schematic))
print(f'The sum of gear ratios for {schematic.file_name} is {gear_ratio_sum}')
def get_gear_ratios(schematic: Schematic) -> Iterable[int]:
numbers_by_bounding_box_location = defaultdict(list)
for number in schematic.numbers:
for location in number.bounding_box:
numbers_by_bounding_box_location[location].append(number)
valid_gear_locations = {
location for (location, numbers) in numbers_by_bounding_box_location.items()
if len(numbers) == 2
}
for symbol in schematic.symbols:
if symbol.symbol == '*' and symbol.location in valid_gear_locations:
first_number, last_number = numbers_by_bounding_box_location[symbol.location]
yield first_number.number * last_number.number
if __name__ == '__main__':
main()
common.py
import string
from argparse import ArgumentParser
from dataclasses import dataclass
from typing import Iterable
@dataclass(frozen=True)
class SchematicNumber:
number: int
line_number: int
start_index: int
end_index: int
@property
def bounding_box(self) -> set[tuple[int, int]]:
box_locations = {
(self.start_index + x_offset, self.line_number + y_offset)
for x_offset in range(-1, self.end_index - self.start_index + 2)
for y_offset in range(-1, 2)
}
number_locations = {
(self.start_index + x_offset, self.line_number)
for x_offset in range(self.end_index - self.start_index + 1)
}
return box_locations - number_locations
@dataclass(frozen=True)
class SchematicSymbol:
symbol: str
line_number: int
index: int
@property
def location(self):
return (self.index, self.line_number)
@dataclass
class Schematic:
file_name: str
lines: list[str]
numbers: list[SchematicNumber]
symbols: list[SchematicSymbol]
def get_schematic() -> Schematic:
file_name = _get_arguments()
lines = _read_file(file_name)
numbers, symbols = _parse_lines(lines)
return Schematic(
file_name,
lines,
numbers,
symbols,
)
def _get_arguments() -> str:
parser = ArgumentParser()
parser.add_argument('file_name')
args = parser.parse_args()
return args.file_name
def _read_file(file_name: str) -> list[str]:
with open(file_name) as file:
return file.read().splitlines()
def _parse_lines(lines: list[str]) -> tuple[list[SchematicNumber], list[SchematicSymbol]]:
numbers: list[SchematicNumber] = []
symbols: list[SchematicSymbol] = []
for line_number, line in enumerate(lines):
for number_or_symbol in _parse_line(line_number, line):
if isinstance(number_or_symbol, SchematicNumber):
numbers.append(number_or_symbol)
else:
symbols.append(number_or_symbol)
return numbers, symbols
def _parse_line(line_number: int, line: str) -> Iterable[SchematicNumber | SchematicSymbol]:
current_number: list[tuple[int, str]] = []
def create_schematic_number(end_index: int) -> SchematicNumber:
return SchematicNumber(
int(''.join(x[1] for x in current_number)),
line_number,
current_number[0][0],
end_index,
)
for index, char in enumerate(line):
if char in string.digits:
current_number.append((index, char))
continue
if current_number:
yield create_schematic_number(index - 1)
current_number.clear()
if char != '.':
yield SchematicSymbol(char, line_number, index)
if current_number:
yield create_schematic_number(len(line) - 1)
Perhaps, over-engineered, but I ended up fully parsing the input, getting a more useful representation of every symbol and number which then meant that the processing for each part of the problem was quite simple after that.
I'm aiming for good practices/readability in the advent challenge (excepting when I try golfing the solution) so don't mind it being a bit longer.
Okay this one wrecked me 😭 My code is UGLY but I'm jusy happy it's done:
https://github.com/devlarabar/advent-of-code/blob/main/2023/03-gear-ratios-1.py
was one of those days where you see part 2 and instantly know that you chose the worst possible approach to part 1 😛
I ended up redoing my part 1
If you assign an id to every part of a number, during parsing, it makes both parts fairly straightforward
This is surprisingly similar to what I did, and the issues I had too 🫠
I tried first using a hashmap to store everything, until i realised how annoying that made checking the surroundings of items
Yeah, I was always pretty into the grid approach. But when I did part 1, I did fairly naive parsing (just parsed each grid cell to a part, digit, or nothing), and then I looped over it, and handled things by number
It was reasonable but as soon as I read part 2, I realized that part practically had to be done handling things by part
@lilac jewel My day 3: https://paste.pythondiscord.com/S23Q
I might return to this day and try and do a regex solution
ooh, storing the span of the word itself is pretty clever
as is using regex
was thinking re.sub everything that isn't a part number into +, and then just evaling the expression
difficulty is the pattern tho since you have variable digit lengths
I actually encountered a bug with regex because I had +-/, so it included .
also the france string
disclaimer: this is the least proud bit of code i've written so far for AoC: https://paste.pythondiscord.com/6WZA
because i haven't actually checked if the input data allowed for repeats of the same number, i just attached an ID to each number
so then when looking at the neighbours around a tile, you can remove any duplicate-ID nums for part 1
the parser is a monstrosity
yeah, quite similar to mine
I debated between the two approaches and think it's all around cleaner though to only store the ID, and then a separate map for ID -> value
ooh, that is kinda clean
fn parse_grid(input: &str) -> (Grid<Entry, 2>, Vec<i64>) {
that's why my parser's signature looks like this
#[derive(Debug, Clone, Copy)]
enum Entry {
Nothing,
Part,
MaybeGear,
Number(usize),
}
Number is only storing an ID, and then you just get the value from the Vec (the ID's are just getting incremented so they go 0 to N-1
i was kinda so fed up that i'd missed the deadline of completing it before day 4 came out that i was just throwing whatever ideas came into my head together :P
that's what mine looked like
fair enough. I was also pretty irritated at rewriting the whole thing for part b) 😛
I remember looking at part a) and being like "okay, do iterate over parts, or over numbers?"
bad choice lol
part 2 was easy with what i chose
Yeah, I started by doing naive parsing (just into part/digit) and then part a was identifying numbers, and searching around the whole border for a part
probably actually my part a) was a little less clean with my original approach, but I'd guess that it's much faster
i did that for both parts though, searching around the border of a number
in the new approach I have to keep a HashSet of ID's
you did that for b)? That seems a lot more painful
i just made one dict and used it for both parts
def find_symbol(y, x1, x2):
for v in [y - 1, y, y + 1]:
for u in range(x1 - 1, x2 + 1):
if 0 <= v < H and 0 <= u < W and GRID[v][u] not in ".0123456789":
return v, u
def find_all_symbols():
for y, line in enumerate(MATCHES):
for part, (x1, x2) in line:
if coord := find_symbol(y, x1, x2):
COORDS.setdefault(coord, []).append(part)
find_all_symbols()
def part_one():
return sum(sum(parts) for parts in COORDS.values())
def part_two():
return sum(a * b[0] for a, *b in COORDS.values() if b)
MATCHES are just spans of numbers
I guess I don't quite follow. If you're searching around the borders of numbers, you'd have to have some extra persistent state to go back later and see if the *'s you found around the number are really gears
when i find a symbol, i store it in a dict with its coordinate as the key, and the value a list of parts
i do that while i'm going around the border of a number
okay, sure, that's what I mean
It seemed a lot cleaner to me, once I read part b) to do things part-oriented isntead
I never have to go around the borders of numbers anymore, which I found was the ugliest part of my original approach
don't you have to go around the border of a symbol though?
you do, but going around the borders of parts is simpler/cleaner
^
because it's always the same
why
yeah, but it's hard to find the start of a number it touches
why is it hard to find the start of the number?
oh, you mean, once you find a digit, you have to go back and parse the whole thing?
yeah
yeah, my approach avoids that too
dunno, going around a number seems just as easy as going around a part
when I'm doing the initial parsing, when I find a run of digits, I give them all the same ID. And then I push their value into a separate container
like, the same loop
so if if have
.....
.123.
.....
then after parsing, what I'll have there is
.....
.000.
.....
and then I push the value 123 into a separate array
preprocessing numbers also seems harder
it's not really. Whenever you deal with it, you'll need to deal with computing the value of each number
it's somewhat ugly no matter what
not hard, just harder, i think both ways end up doing about the same amount of work
i suppose there's less symbols to search around
idk, I switched from one to the other in part a) and this approach was cleaner IMHO. And part a) was the one where it's more of a toss up, for b) it's even easier. but it also depends on your tolerance for creating extra dicts
i just need the one dict though
i feel like my solution was clean
not as clean as my rect solution
I guess I didn't really follow how it's one dict. it's a bit hard for me to understand the code there
COORDS is dict[tuple[int, int], list[int]]
it's a map from coordinates to what exactly?
the tuple[int, int] is the coordinate of the symbol that was touching a number
and the list is the number(s)
okay, so for all symbols that were touching something, it's a map of symbol coordinates to numbers
yeah
So you built this dict by going around parts directly during parsing
yeah
yeah, I mean that's fine, I just didn't like having to do that
both because I don't like having to go around a number, but also because I didn't really like the idea of building a reverse lookup like that
i had most of this written for part 1, so it didn't take much to add part two
because of fairly arbitrary preferences around efficiency
Yeah, so it was different for me, because I didn't, and didn't want to build the reverse lookup
I was going around parts for part a) (not during parsing), and simply immediately accumulating
sorry, around numbers
for part 1) I'm not sure what motivated you to build the reverse lookup; you don't need it for anything
no i didn't have it for part 1
okay, sorry, you just said "i had most of this written for part 1"
i just accumulated, but i had the loops around the numbers already
yeah, to add the dictionary was like 2-3 more lines
yeah, I thought about doing that, I just didn't like it
since i was already looping around numbers i just needed a way to remember which symbols i had seen
right
for me, just assigning an ID per number was a lot simpler. You had to anyway traverse the number to accumulate the full value right
so I'm not sure what you mean by "preprocessing numbers seems harder"
yeah, just creating the id -> number, is an extra step
well, that part is basically one line, it's the easiest part
but like both approaches are doing this
if row.chars().next().unwrap().is_digit(10) {
let non_digit = row.find(|x: char| !x.is_digit(10)).unwrap_or(row.len());
values.push(row[..non_digit].parse().unwrap());
row = &row[non_digit..];
let non_digit = non_digit as i64;
for i in col_index..(col_index + non_digit) {
grid[[i, row_index as i64]] = Entry::Number(id_num);
}
col_index += non_digit;
id_num += 1;
continue;
}
the values.push(...) is where the ID -> number mapping is created
have you seen a solution use rectangular regions?
from rects import Rect, Region
operators = Region()
for y, line in enumerate(GRID):
for m in re.finditer(r"([^\d\.])", line):
operators |= Region.from_rect(Rect(m.start(), y, 1, 1))
print(sum(
int(m[1])
for y, line in enumerate(GRID)
for m in re.finditer(r"(\d+)", line)
if Region.from_rect(Rect(m.start() - 1, y - 1, len(m[1]) + 2, 3)) & operators
))
just make a big set of rectangles around all the operators and intersect each rectangle around a number with that set
I can't even begin to understand how this works 😛
its like using line sweeps, library is meant for, like, widget regions in a gui
So I guess it makes operators the set of points that are rectangles with border 1 around digits
yeah
around symbols
not the digits, but you could also do it around digits
it would work either way
yeah, same
the opposite would be signficantly more annoying because then when you scan the symbols in part two you wouldn't have the values
actually i should've done around digits instead of spans of numbers cause its simpler
so you'd have to store that somewhere
yeah i didn't think of how to do part 2 with this
and then it would start to look more like other solutions
but i thought it was cool it could do part 1
but obviously the rectangle library is doing a lot of the heavy lifting here
also I gotta say I really hate golfed python 😦
yeah, it also takes like 5 or 6 seconds to do all these intersections
these long list comprehensions are so hard to read compared to their equivalents in other languages since everything is backwards
Horrible solution
def part01(schematic):
lines = schematic.split("\n")
total = 0
height = len(lines)
width = len(lines[0]) if height > 0 else 0
symbols = {"*", "#", "+", "$", "=", "%", "/", "@", "-", "&"}
def is_symbol_adjacent(x, y, length):
for i in range(length):
if any(
check(x + i, y)
for check in [
is_symbol_left,
is_symbol_right,
is_symbol_above,
is_symbol_below,
is_symbol_diagonal_up_left,
is_symbol_diagonal_up_right,
is_symbol_diagonal_down_left,
is_symbol_diagonal_down_right,
]
):
return True
return False
def is_symbol_left(x, y):
return x > 0 and lines[y][x - 1] in symbols
def is_symbol_right(x, y):
return x < width - 1 and lines[y][x + 1] in symbols
def is_symbol_above(x, y):
return y > 0 and lines[y - 1][x] in symbols
def is_symbol_below(x, y):
return y < height - 1 and lines[y + 1][x] in symbols
def is_symbol_diagonal_up_left(x, y):
return x > 0 and y > 0 and lines[y - 1][x - 1] in symbols
def is_symbol_diagonal_up_right(x, y):
return x < width - 1 and y > 0 and lines[y - 1][x + 1] in symbols
def is_symbol_diagonal_down_left(x, y):
return x > 0 and y < height - 1 and lines[y + 1][x - 1] in symbols
def is_symbol_diagonal_down_right(x, y):
return x < width - 1 and y < height - 1 and lines[y + 1][x + 1] in symbols
for y, line in enumerate(lines):
matches = re.finditer(r"\b\d{1,3}\b", line)
for match in matches:
number = match.group()
start = match.start()
if is_symbol_adjacent(start, y, len(number)):
total += int(number)
return total
def part02(schematic):
lines = schematic.split("\n")
height = len(lines)
width = len(lines[0])
total_ratio_sum = 0
pattern = re.compile(r"\d{1,3}") # Pattern to find numbers up to 3 digits
for x, line in enumerate(lines):
for match in re.finditer("\*", line):
gear_start, gear_stop = match.span()
nums = []
# Check vertically (above and below)
for row in (1, -1):
if 0 <= x + row < height: # Ensure within grid bounds
for num_match in pattern.finditer(lines[x + row]):
num_start, num_stop = num_match.span()
if (
gear_start - 1 <= num_start <= gear_stop
or gear_start <= num_stop <= gear_stop + 1
):
nums.append(num_match.group())
# Check immediate left and right on the same line
for num_match in pattern.finditer(line):
num_start, num_stop = num_match.span()
if num_stop == gear_start or num_start == gear_stop:
nums.append(num_match.group())
# Calculate gear ratio if two numbers are found
if len(nums) == 2:
total_ratio_sum += int(nums[0]) * int(nums[1])
return total_ratio_sum
I'm quite late to the party!
Sheet1
.....................................164.................429.35...........221....................................................34.........,.....................................164.................429.35...........221....................................................34.........,..........
a solution in google sheets !
not my proudest solution, but here: ```py
def part_1(inp: str) -> int:
items, grid = parse(inp)
numbers = [
int(item.value)
for item in items
if item.value.isdecimal() and touching_symbol(grid, item)[0]
]
return sum(numbers)
def part_2(inp: str) -> int:
items, grid = parse(inp)
for item in items:
if item.value.isdecimal():
touching = touching_symbol(grid, item)
if touching[0] and touching[1].value == "*": # type: ignore
sym = typing.cast(GridItem, touching[1])
num = next(i for i in items if i.row == sym.row and i.span[0] == sym.span[0])
num.touching.append(int(item.value))
gears = [i.touching for i in items if len(i.touching) == 2]
return sum(math.prod(i) for i in gears)
items are all GridItems ```py
@dataclass
class GridItem:
value: str
row: int
span: tuple[int, int]
touching: list[int] = field(default_factory=list)
got some time away from school to finish up my solution 😌
Serious question, how ashamed of this code should i be
with open("day3_input.txt", "r") as file:
doc = file.read().split("\n")
doc.pop() # last child is empty
max_i = len(doc) - 1
max_j = len(doc[0]) - 1
information = []
for i, line in enumerate(doc):
found_num = False
for j in range(len(line)):
if not found_num and line[j].isdigit():
found_num = True
start = j
if found_num and j == max_j or found_num and not line[j + 1].isdigit(): # end of line or end of number
if 0 < i < max_i and 0 < j < max_j and 0 < start < max_j: # within
matrix = [doc[i - 1][start - 1:j + 2],
doc[i][start - 1:j + 2],
doc[i + 1][start - 1:j + 2]]
elif i == 0 and 0 < j < max_j and 0 < start < max_j: # top
matrix = [doc[i][start - 1:j + 2],
doc[i + 1][start - 1:j + 2]]
elif 0 < i < max_i and j == max_j: # right
matrix = [doc[i - 1][start - 1:j + 1],
doc[i][start - 1:j + 1],
doc[i + 1][start - 1:j + 1]]
elif i == max_i and 0 < j < max_j and 0 < start < max_j: # bottom
matrix = [doc[i - 1][start - 1:j + 2],
doc[i][start - 1:j + 2]]
elif 0 < i < max_i and start == 0: # left
matrix = [doc[i - 1][start:j + 2],
doc[i][start:j + 2],
doc[i + 1][start:j + 2]]
elif i == 0 and start == 0: # top left
matrix = [doc[i][start:j + 2],
doc[i + 1][start:j + 2]]
elif i == 0 and j == max_j: # top right
matrix = [doc[i][start - 1:j + 1],
doc[i + 1][start - 1:j + 1]]
elif i == max_i and j == max_j: # bottom right
matrix = [doc[i - 1][start - 1:j + 1],
doc[i][start - 1:j + 1]]
elif i == max_i and start == 0: # bottom left
matrix = [doc[i - 1][start:j + 2],
doc[i][start:j + 2]]
found_num = False
info = Info(int(line[start:j+1]), matrix)
if info.is_part_number():
information.append(info.get_value())
print(sum(information))```
not ashamed at all! If it works it works :) But of course there is always space to learn and improve too :)
so i got nerd-sniped by this and managed to golf the regex down from 732 to 243, here's the regex i got with your input
it's still just part 1, haven't found a way to do part 2
How in god's name
Did you not have anything better to do?? (/lh)
LOL
I did have something like that originally, but I decided to have the regex be larger since it made the generation code a lot shorter and easier
Ah that's fair
i have no idea why non-fixed length lookaheads are allowed but lookbehinds aren't
@crude ivy verbosity you might enjoy
Regex is just close enough to being a programming language that it's frustrating when it isn't. (especially the flavors that have recursion.)
I love using regex. It can be fun to solve problems with it
but apparently any number adjacent to a symbol, even diagonally,
oh, fuck you
guess I'll have to learn about matrixes now huh?
so uh, did you write that pattern by hand? lmfao
I didn’t write the 33k by hand, but I did write all the cases by hand. That’s why it’s formatted like it is in the code with white space that gets removed, otherwise it would have been impossible to deal with. I also had to re-write it three times, once I only did 5x3 instead of 7x3, and then I didn’t factor in all the [\d*.]s, I had them as just more \.s.
did you generate it programatically first then?
because jesus fuck, that thing is huge. I consider myself a regex lover, and even that is too extreme for me lmao
I’m not sure I understand the question. I manually wrote all the possible vertical, left, right, null, and vertical special cases. itertools.product then generates the necessary combinations of those, each of which is a mini case that has one above, left, right, and below component. Those mini cases then get combined into the giant regex, which is then applied to solve the problem.
For what it does, I feel like I actually wrote very clean code (especially when compared to my part 1 regex generator).
the fact I have no idea what you're talking about really says a lot about my level of understanding lmao
but thank you for humoring me lol
you'll have real fun at day 5
just from reading day 3 puzzle, I'm thinking of tackling it with numpy matrices
The key part to understand is this line py pattern.append(rf"(?:(?<={above}.{{134}}{left})\*(?={right}.{{134}}{below}))")
It’s in a non-capturing group (?:…) because otherwise all the characters in the entire three rows would be in the match, and that would stop re.findall from using any of that space again, preventing any two cogs in the same row from both matching.
The first part is a look behind (?<=…) where above is a seven character long statement that is one of the possible cases. This is then followed by 134 of any character, which puts the match head right before the start of the 3 character long group to the left of the *. That is what left is for , and then that ends the look behind. This is followed by an escaped \* to match the cog, then the reverse of that pattern for the lookahead (?=…). All of these get joined together with ors |, which is the other reason it’s all in a non capturing group, since that groups it into one or-able statement. All of these combined are preceded by a (?s) to make . match newlines, and that’s the final regex.
If you look at the product statements right above that, you’ll see that all of them have two non-null parts and two null parts, except for the vertical specials which have 3 nulls. This means that every match will only have two capture groups, which is the two numbers that get multiplied. The vertical specials are with three nulls because those are the cases where both numbers are on the same line above/below the cog.
I would suggest not to, tbh. IMO, your best bet would be just to preprocess the data first into a collection of where all the numbers and symbols are located, using a coordinate system. From there it's much easier to work out neighbouring numbers and symbols.
You don't have to go down that approach but it's probably the easiest one to code and to reason about in general.
without having tried yet, here's what I intend on doing:
- Create a class for the numbers. Each instance of the class will be an object that will store the matrix coordinates of all digits in the number, and will have methods to search all coordinates around all digits
- Process the input to create a 2d numpy array, and process it to instantiate the class
- Execute the method and sum the correct numbers
if you are going to have coordinates I'm not sure that you need a numpy array
so, use only nested lists?
I would actually suggest a dictionary of coordinates
What the values are depends on what you want to save but the keys would be your coordinates
so like
"3,5": 5 for example, and then proc that string to separate at the comma and int the digits?
strings are hard to work with
Yeah, would be better to use an object, like a tuple or dataclass
Or you can use the inbuilt complex number type, even though you really shouldn’t :3
what's a "dataclass"?
essentially a way to make lightweight classes quickly without boilerplate
!e
from dataclasses import dataclass
@dataclass
class Coord:
x: int
y: int
def __add__(self, other):
return Coord(self.x + other.x, self.y + other.y)
p1 = Coord(3, 4)
p2 = Coord(1, 2)
p3 = p1 + p2
print(p1, p2, p3)
print(p1.x, p2.y, p3.x + p3.y)
...
did I really just do that
!e
from dataclasses import dataclass
@dataclass
class Coord:
x: int
y: int
def __add__(self, other):
return Coord(self.x + other.x, self.y + other.y)
p1 = Coord(3, 4)
p2 = Coord(1, 2)
p3 = p1 + p2
print(p1, p2, p3)
print(p1.x, p2.y, p3.x + p3.y)
@spiral stump :white_check_mark: Your 3.12 eval job has completed with return code 0.
001 | Coord(x=3, y=4) Coord(x=1, y=2) Coord(x=4, y=6)
002 | 3 2 10
tbf mathematically it's fine
just FPE errors make it an issue
ah this. so, it basically just frees me from having to write the __init__ function?
yes, and automatically provides you with nice representations like the ones shown above
It can also provide you with total ordering, immutability, and some other cool stuff for free
cool
(parameters to the decorator)
not exactly what I had in mind but yeah, nice
what's total ordering and immutability?
slots=True is a good one
https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass
A list of its parameters
isn't total ordering functools
Total ordering = can you <, >, ==, != it
That's for converting one of the >, <, <=, >=, methods + the == method into a full total ordering
Dataclasses can just straight up generate the entire thing
You do have to explicitly ask for order=True though
And immutability means you can't mutate it, like how tuples work
You ask for frozen=True and now when you create one you aren't allowed to modify that instance (you have to create a new one)
That’s what I meant by “you can”. Mathematically it works very well; but semantically it’s bad
But it’s AOC so whatever :)
Although I didn’t appreciate that .real and .imag are floating points
semantically?
As in, these aren’t actually complex numbers but you are using a complex type to represent them
eh
the mapping between 2 dim vectors and complex numbers is pretty ubiquitous across math imo
the argand diagram is very popular and that's essentially just the 2d plot of real and imag
heck treating complex numbers as 2d vectors was a part of my hs math course
I mean mathematically ℂ is generally considered to be isomorphic to ℝ²
Lmao fair enough
I was more pointing out it's not usually 'a mapping' and more that they're literally just the same thing
(two different sets of arithmetic on the same field)
I did learn complex and argand at school. So I do get how these can be represented by complex numbers. I just never really considered them as the same thing
Maybe that’s my lack of math knowledge but my outlook was just because you can represent them as complex numbers, doesn’t mean that that is what they are
they're the same for addition, but different for other things, like C has multiplication C x C -> C whereas R2 doesn't have that notion built in
As I said before, two different sets of arithmetic on the same field 🙃
I don't feel it's proper to say that because part of the field is its operations
R2 isn't a field anyhow so
Yeah I possibly mean set
my personal take on this is that stuff is defined according to what properties they have
we have some intuitive notion of a 2d point in space, and whatever we find that works the same way as we would expect those 2d points to work is effectively a representation of those 2d points
representing 2d points as a tuple of x y coordinates, as complex numbers, as a tuple of radius and phase (polar coordinates), as a ring of polynomials with 1+x^2=0, are all fine representations of the concept of 2d points to me
then picking a representation is just a choice deciding which one is easiest to use
Fair enough. I’ll defer to your opinion on this one. I still think the complex number type is not perfect because it’s floating point though
Even if I only learned this fact a few mins ago and even if the floating point resolution is probably fine for the AOC problems
I mean you don't have to defer to anything
if you feel it's unnatural, then you feel it's unnatural
I do agree with the FP thing
it's not large enough to affect results, but just that they're floats is annoying
like I worked on day 17 for a bit like 5 hours ago and converting in between ints and floats looks ugly
Well I don’t have as much of a mathematical background so I’m not going to argue my point too hard
duck typing but for mathematical constructs :D
exactly
Lmao
btw @crude ivy I've been doing that thing you told me, to write a logic for example first, and then applying to the real input
it has been great
It is pretty good lol
much easier to work with. thanks for the tip
I'll worry about it when I get there lol
It might be worth considering going one step further and using a unit test framework
You can write unit tests in a certain way and use either unittest or pytest packages to automatically run the tests
I have no idea what those are 😂
Here’s my day 1
Notice how there’s a test file?
I’ve written tests in there according to the pytest framework style
With one command I can run any/all the tests to see if they pass or fail
https://dpaste.org/dJauU is my part 1 solution.
I modified it a bit for part 2 but still used the same general strategy: https://dpaste.org/4tBe9
@crude ivy What do you think?
Checking - currently on my phone so ain’t easy :)
GH is easier to view from since it has an app
Right off the bat I can say you don’t need all your code nested in the with
If you are going to read the entire thing in one line then that’s all that’s needed under the with
Yeah I just sort of ran with it haha.
Everything else can be dedented
thanks
It’s cool dw
I’m home with the flu so I have nothing better to do
It’s either this or binge YouTube
oh no :(
I got the flu vaccine and COVID vaccine at the same time and I rarely go outside. My mom and I still mask up when we leave the apartment because she's got lung problems.
flu sucks, hope you feel better
I did get the flu vaccine this winter
Anyway - the code
I think the thing is that you have to check each line for symbols potentially 3 times
Once as a previous line, once as the current line, once as the next line
There’s a lot of scope for repeated checks
Yes
Which is why preprocessing would be more elegant
Also
If you find that you have to consider three lines at a time
Itertools is a good way to do that
more_itertools.windowed(seq, n, fillvalue=None, step=1)```
Return a sliding window of width *n* over the given iterable.
```py
>>> all_windows = windowed([1, 2, 3, 4, 5], 3)
>>> list(all_windows)
[(1, 2, 3), (2, 3, 4), (3, 4, 5)]
``` When the window is larger than the iterable, *fillvalue* is used in place of missing values:
```py
>>> list(windowed([1, 2, 3], 4))
[(1, 2, 3, None)]
```...
This is third party but very useful
oooh more_itertools
Windows of 2 are in std lib
(Kinda annoying that they didn’t make it generic)
But yeah - you can imagine using that over the input
To get a window of 3 lines at a time
I think once I become more familiar with Python as a whole I'll be aware of everything I can use.
If you had no choice ofc
Like I said it's been a long time.
Sure :)
Itertools is a must to get familiar with

@old canopy this is my code
https://github.com/Fiddle-N/advent-of-code/blob/master/year_2023/day_03/process.py
Warning - it be a little bit verbose
I considered OOP
Java was the language the CS department at my university agreed on using (why) so my brain always goes to OOP first.
eh, there's a class :)
True
But the definition of OOP varies
Some people will say that any use of an object is OOP
In which case print('Hello World') is OOP in Python
Which - maybe it technically it is - but let’s face it, no one in reality considers that OOP
For me there has to be composition and/or inheritance to be unambiguously OOP
Maybe I’ll call that uOOP
That seems valid.
I like mixing things up because it makes things fun for me.
I like to have fun programming.
If I were doing this in Java I would've done this like you did here. My first inclination was to do both parts in one program.
I like your solution @crude ivy
I use sets in other problems.
Thanks ☺️
Your main function is really nice. I really like keeping main as direct and clear as possible. I should stop half-assing it haha.
I mean - it is AoC so that’s fine :)
And actually I have a lot more in main than one should have
It should probably be a single fn call
So that’s me also being lazy too
I don't know there's a "should" for what belongs in main. You're right though, I bet you could probably just put all that in _str_ and print the object. I don't know if that's good practice though.
well okay Discord didn't format that the way I typed it
sure
:O
That’s probably a good guide
Would you say it's imperative that it's not imperative?
Haha 😂
Nice one :)
Also, did you check out my tests?
I have full test coverage
Actually I don’t have full test coverage
But I test a lot lol
I saw a little bit of them. I like test-driven programming.
I have to run an errand but I'll be back later today.
It's been nice chatting with you.
Thanks again for reviewing my code.
No worries- enjoy your day! Hit me up either here or in #python-discussion whenever
would anyone mind taking a look at my code to help me determine why my part 1 is not giving the correct answer?
Sure - feel free to post your code
Although let me ask about the main bug people had in their scripts for this day
Does your script parse numbers at the very end of a line?
yes
Ok. Then feel free to send your code over :)
just to preface, i just started learning in the fall, so its probably going to be overcomplicated
That’s fine
symbols = {"!", "@", "#", "$", "%", "^", "&", "*", "/", "+", "=", "-"}
symbol_pos = []
num_pos = {}
parts = []
def check_up():
if s[0] == n[0] and s[1] == n[1] + 1: # checks up
parts.append(int(num_pos[n]))
elif s[1] == n[1] + 1 and n[0] in range(s[0] - len(num_pos[n]), s[0]): # checks upper left
parts.append(int(num_pos[n]))
elif s[1] == n[1] + 1 and s[0] + 1 == n[0]: # checks upper right
parts.append(int(num_pos[n]))
def check_down():
if s[0] == n[0] and s[1] == n[1] - 1: # checks up
parts.append(int(num_pos[n]))
elif s[1] == n[1] - 1 and n[0] in range(s[0] - len(num_pos[n]), s[0]): # checks bottom left
parts.append(int(num_pos[n]))
elif s[1] == n[1] - 1 and s[0] + 1 == n[0]: # checks bottom right
parts.append(int(num_pos[n]))
def check_left_right():
if s[1] == n[1] and n[0] in range(s[0] - len(num_pos[n]), s[0]): # checks left of symbol
parts.append(int(num_pos[n]))
elif s[1] == n[1] and s[0] == n[0] - 1: # checks right of symbol
parts.append(int(num_pos[n]))
with open('aoc3.txt', 'r') as file:
# parses input and places the x, y coordinates of all symbols in the list symbol_pos
# all numbers are placed in the dictionary num_pos with the x,y values as the key and the number itself as a value
for y, line in enumerate(file.readlines()):
tmp_string = ""
for x, char in enumerate(line):
if char in symbols:
symbol_pos.append((x, y))
if char.isnumeric():
tmp_string += char
if char in symbols or char == ".":
if tmp_string.isnumeric():
num_pos[(x - len(tmp_string), y)] = tmp_string
tmp_string = ""
for n in num_pos:
for s in symbol_pos:
check_up()
check_left_right()
check_down()
print(sum(parts))
print(parts)```
!paste
ive tested against random input and everything seems to check out, but there must be errors happening somewhere
because im not getting the correct sum
did you test against the test input for day 3?
yes and it came back with the correct sum
ive also tested against random collections of lines from the actual input and haven't discovered the problem yet
with open('aoc3.txt', 'r') as file:
# parses input and places the x, y coordinates of all symbols in the list symbol_pos
# all numbers are placed in the dictionary num_pos with the x,y values as the key and the number itself as a value
for y, line in enumerate(file.readlines()):
tmp_string = ""
for x, char in enumerate(line):
if char in symbols:
symbol_pos.append((x, y))
if char.isnumeric():
tmp_string += char
if char in symbols or char == ".":
if tmp_string.isnumeric():
num_pos[(x - len(tmp_string), y)] = tmp_string
tmp_string = ""
I'm not convinced that this code is finding numbers at the very end of a line
what part of this would save down a number if it were right at the very edge?
specifically, right at the very right edge
you are right, i thought those numbers were accounted for. I can fix that by checking at the first for loop to see if anyhting exists in the tmp_string, and add it to the dictionary
for y, line in enumerate(file.readlines()):
if tmp_string != "":
num_pos[(x - len(tmp_string), y)] = tmp_string
tmp_string = ""```
You can indeed - but also take care about numbers at the bottom right corner
bottom right of a symbol?
theres no numbers at the bottom right, but i see what you mean
In which case you are lucky but you could have had one there
And created a bug that others would not easily detect even if they ran your script against their input
Would you like recommendations on a better way to parse out numbers?
sure
is regex an included module?
yea that much better than what im doing lol
these puzzles have taught me more about parsing than an entire semester in university lol
granted we didnt spend much time on it
Oh trust me there’s way more about parsing to learn
I can only imagine
None of the puzzles are particularly hard to parse
Certain grammars require tools that even regex cannot parse properly
for me they are lol, but yea I imagine it gets way more complicated
Also with AOC the data is well formed too
You don’t have to worry about crap data
And what you do if you have that
im doing c++ this semester, I don't even want to think about doing these puzzles in that language
although once I feel more comfortable parsing in python im sure ill give it a go
Well I appreciate your input on this, it's very helpful. I will look into regex and start learning.
Unfortunately I am still not getting the right sum, so I'm gonna have to tear this code down and decide if I want to start over.
Did you test against the test input on the page?
Yes C++ is not the easiest to learn lol
Yea, im getting the right sum on the test input
so there must be some condition where a number isnt getting added to the dictionary, or potentially a number is not being parsed properly
Someone else’s code, I mean
Yea, I can compare outputs for specific input and look for differences. That could lead me to whats going wrong in my code.
Indeed. Thanks again, im gonna take a break and come back to this later. I'll update you once I have it fixed.
Best of luck!
its fixed, can't say what it was exactly but I think something may have been wrong with the input file. on to part 2
Digging deeper I found the causes.
- Numbers at the end of lines were not being parsed properly. I fixed that but my fix created the second issue.
- When numbers were parsed their "grid" coordinates were also taken, but my fix caused numbers that were being recorded at the end of the line to have their vertical coordinate increased by 1, so when they were later being checked for adjacency, they were being left out in some cases.
I was able to correct it by implementing an appropriate new solution for problem 1
Awesome that you managed to debug your solution! What new solution did you go for afterwards
for y, line in enumerate(file.readlines()):
tmp_string = ""
for x, char in enumerate(line):
if char in symbols:
symbol_pos.append((x, y))
if char.isnumeric():
tmp_string += char
if char in symbols or char == ".":
if tmp_string.isnumeric():
num_pos[(x - len(tmp_string), y)] = tmp_string
tmp_string = ""
if char == line[-1]:
if tmp_string != "":
num_pos[(x - len(tmp_string), y)] = tmp_string```
I moved the fix to a new conditional statement at the bottom of the loop
basically just checking if the character being parsed was the last of the string
im now realizing that fix could also have unintended consequences
Yeah that’s not robust - what if the char also occurs at the middle of the string?
exactly, i could compare the "x" coord of that character against the lenght of the string
that would work properly in all cases i think
I think so
But yeah manually iterating through the string like this is dodgy
Lots of people made the errors you made
if x == len(line) - 1 and tmp_string != "":
num_pos[(x - len(tmp_string), y)] = tmp_string```
That looks fine
