#AoC 2023 | Day 13 | Solutions & Spoilers
256 messages · Page 1 of 1 (latest)
#..#..####.
#.#####..##
#.##..####.
##.#..####.
..#...#..#.
##...#....#
....##....#
##.##.#..#.
#..#.##..##
##.###.##.#
...#.#.##.#
.#.#.#.##.#
##.###.##.#
#..#.##..##```
what should be the output for this?
Which part?
p1
i get 8 as well, just wanted to confirm 😄
easy day
just changed (a[:trim] == b[:trim]).all() to (a[:trim] != b[:trim]).sum() == 1
exactly
well i had np.sum(~(data[i-m:i] == data[i+m-1:i-1:-1]))
and then i deleted the second one to let copilot to fill in np.sum(~(data[:, i-m:i]…
but didn't notice it also copy ret += 100 * i 💀
2134/1154 sadge
def parse_raw(raw: str):
return list(raw.split("\n\n")).mapped(Grid.from_string)
def reflect_x(grid: Grid, mirror_x: int) -> list[int]:
reflect_range = min(mirror_x, grid[0].len() - mirror_x)
return grid.data.mapped(
lambda row: sum(
a != b
for a, b in zip(
row[mirror_x - reflect_range : mirror_x],
row[mirror_x : mirror_x + reflect_range][::-1],
)
)
)
def reflect_y(grid: Grid, mirror_y: int) -> list[int]:
reflect_range = min(mirror_y, grid.data.len() - mirror_y)
return list(
sum(a != b for a, b in zip(a, b))
for a, b in zip(
grid.data[mirror_y - reflect_range : mirror_y],
grid.data[mirror_y : mirror_y + reflect_range][::-1],
)
)
def part_one(data=data):
total = 0
for i, grid in data.enumerated():
for mirror_x in range(grid[0].len()):
if reflect_x(grid, mirror_x).sum() == 0:
total += mirror_x
for mirror_y in range(grid.data.len()):
if reflect_y(grid, mirror_y).sum() == 0:
total += 100 * mirror_y
return total
def part_two(data=data):
total = 0
for i, grid in data.enumerated():
for mirror_x in range(grid[0].len()):
if reflect_x(grid, mirror_x).sum() == 1:
total += mirror_x
for mirror_y in range(grid.data.len()):
if reflect_y(grid, mirror_y).sum() == 1:
total += 100 * mirror_y
return total
from operator import eq
def find_reflection(block, ignore=None):
for i in range(1, len(block)):
if all(map(eq, block[:i][::-1], block[i:])):
o = i*100
if o == ignore:
continue
return o
block = list(zip(*block))
for i in range(1, len(block)):
if all(map(eq, block[:i][::-1], block[i:])):
o = i
if o == ignore:
continue
return o
def p2_reflection(block):
block = list(map(list, block))
base = find_reflection(block)
for line in block:
for x, _ in enumerate(line):
line[x] = "#."[".#".find(line[x])]
r = find_reflection(block, base)
line[x] = "#."[".#".find(line[x])]
if r is not None and r != base:
return r
raise ValueError
p = q = 0
for i, block in enumerate(blocks):
p += find_reflection(block)
q += p2_reflection(block)
print(p, q)
aaa it took me too long to find out that the part 1 reflection can still be valid
This only allows one line of symmetry? I feel like that isn't a constraint you should assume for today's puzzle lol
ah i added the restriction when i moved it into a function
it used to just add on directly to p1
that would be very annoying though
i'd probably plop the reflection lines into a set then use new_set - base_set
that would be very annoying though
by that i mean it wouldn't have been in the test cases again >:c
although i think i mainly assumed it bc it kept referring to the lines in the singular
the reflection in each pattern
although i do admit i am lucky lol
same thing with day 8 :D
assuming that constraint worked for me too. maybe all the test cases are made in such a way?
may help golf
yeah you def get that from the wording
i initially interpreted multiple and started working on double reflections lol
like, the thing between two mirrors being symmetric
then looked in the input went 'why is it not one huge grid'
back at the question went 'ah one reflection each'
cleaned up a bit:
def reflect_axis(pattern, smudge):
for i in range(1, len(pattern)):
a = pattern[:i][::-1]
b = pattern[i:]
trim = min(len(a), len(b))
if (a[:trim] != b[:trim]).sum() == smudge:
return i
return 0
def reflections(pattern, smudge):
return 100 * reflect_axis(pattern, smudge) + reflect_axis(pattern.T, smudge)
def part_one():
return sum(reflections(pattern, 0) for pattern in DATA)
def part_two():
return sum(reflections(pattern, 1) for pattern in DATA)
My solution for p2 using edit distance. I used a strict equality check for p1.
def transpose(pattern: list[str]) -> list[str]:
"""Transpose a pattern."""
return ["".join(row) for row in zip(*pattern, strict=True)]
def hamming_distance(a: str, b: str) -> int:
"""Calculate the hamming distance between two strings."""
return sum(1 for x, y in zip(a, b, strict=True) if x != y)
def find_reflection(pattern: list[str], allowed_smudges: int = 1) -> int:
"""Check a pattern for reflections."""
for index, (a, b) in enumerate(pairwise(pattern)):
smudges_remaining = allowed_smudges - hamming_distance(a, b)
if smudges_remaining < 0:
continue
for offset in range(1, min(index + 1, len(pattern) - index - 1)):
smudges_remaining -= hamming_distance(
pattern[index + 1 + offset], pattern[index - offset],
)
if smudges_remaining < 0:
break
else:
if smudges_remaining == 0:
return index + 1
return 0
def find_reflections(pattern: list[str]) -> int:
"""Check a pattern for horizontal and vertical reflections."""
if horizontal := find_reflection(pattern):
return horizontal * 100
if vertical := find_reflection(transpose(pattern)):
return vertical
return 0
def calculate_solution(path: Path) -> int:
"""Calculate the solution."""
return sum(find_reflections(pattern) for pattern in read_patterns(path))
Need a little help. for this pattern:
#.##..##.
#...##...
...####..
.########
#..#..#..
#..#..#..
#...##...
.##.##.##
.###..###
#...##...
.###..###
There are both horizontal (at 9) and vertical (at 5) reflection points. what should I do in here?
assuming you're on part 1, are you sure horizontal reflection at line 9 is valid? a mirror must be placed either between two rows or between two columns
oh thx, got the answer 🤦♂️
from typing import Optional
HORZ_S = 100
VERT_S = 1
def count_mismatch(case: list[str], i: int) -> int:
if i < len(case)//2:
check = case[:i+i+2]
else:
check = case[i+i+2-len(case):]
L = len(check)
return sum(
sum(int(a != b) for a, b in zip(check[j], check[L-j-1]))
for j in range(0, L//2)
)
def find_mirror(case: list[str], case_t: list[str], smudges = 0) -> Optional[int]:
for m, c in ((HORZ_S, case), (VERT_S, case_t)):
for i in range(len(c)-1):
if count_mismatch(c, i) == smudges:
return m * (i+1)
print(*case, sep='\n')
return None
with open(0) as f:
cases = list(map(str.splitlines, f.read().split('\n\n')))
cases_t = [ list(map(''.join, zip(*case))) for case in cases ]
print(sum(find_mirror(case, case_t) for case, case_t in zip(cases, cases_t)))
print(sum(find_mirror(case, case_t, 1) for case, case_t in zip(cases, cases_t)))
darn, I just realized I used a soft keyword
starting to get a too terse again, someone stop me, I'm not trying to golf.
rotate = lambda b: list(map(''.join, zip(*b)))
smudge = lambda board,i: sum(sum(a!=b for a,b in zip(row[:i][::-1],row[i:])) for row in board)
def mirror(board):
return next((i for i in range(1,len(board[0])) if smudge(board,i) == 1),0)
boards = [b.splitlines() for b in open(aocinput).read().split('\n\n')]
print(sum(mirror(board) + 100*mirror(rotate(board))for board in boards))
that feeling when you want to shorten it but retain the whitespaces and proper variable names
I think row[:i][::-1] can be shortened
although you could argue the shortened version is less readable
I can never remember how [::-1] works when you include slicing indices lol
Would it be [i::-1] or is that subtly different lol?
(this is why I always just [][::-1])
but it makes sense because [start:stop] start is inclusive and stop isn't
from pathlib import Path
from itertools import groupby
def grid_diff(g1, g2):
return sum(a != b for r1, r2 in zip(g1, g2) for a, b in zip(r1, r2))
def find_hori_mirror(grid: list[str], diff=0):
for i in range(1, len(grid)):
left_slice, right_slice = grid[:i][::-1], grid[i:]
size = min(len(left_slice), len(right_slice))
if grid_diff(left_slice[:size], right_slice[:size]) == diff:
return i
return 0
def solve(notes, diff):
return sum(
100 * find_hori_mirror(note, diff)
+ find_hori_mirror(list(zip(*note[::-1])), diff)
for note in notes
)
with open(Path(__file__).parent / "input.txt") as f:
notes = [list(g) for _, g in groupby(f.read().splitlines(), lambda t: bool(t))][::2]
print(solve(notes, 0), solve(notes, 1))
```I though the catch of part2 was gonna be that comparing strings directly was too slow... guessed wrong
This was a fun puzzle. I solved it super verbosely/dumbly, so it's fun now reading all the very clever ways.
And... the golf ways. @hollow tinsel XD
def parser(raw_data: TextIO):
# noinspection PyTypeChecker
return [
np.genfromtxt(StringIO(grid), delimiter=1, dtype=np.str_, comments=None) == '#'
for grid in raw_data.read().split('\n\n')
]
def part_a_solver(data):
return sum(
next((
col
for col in range(1, grid.shape[1])
if np.array_equal(
np.flip(grid[:, col - (size := min(col, grid.shape[1] - col)):col], 1),
grid[:, col: col + size]
)), 0
)
or
100 * next(
row
for row in range(1, grid.shape[0])
if np.array_equal(
np.flip(grid[row - (size := min(row, grid.shape[0] - row)):row], 0),
grid[row: row + size]
)
)
for grid in data
)
def part_b_solver(data):
return sum(
next((
col
for col in range(1, grid.shape[1])
if np.count_nonzero(
np.flip(grid[:, col - (size := min(col, grid.shape[1] - col)):col], 1) !=
grid[:, col: col + size]
) == 1), 0
)
or
100 * next(
row
for row in range(1, grid.shape[0])
if np.count_nonzero(
np.flip(grid[row - (size := min(row, grid.shape[0] - row)):row], 0) !=
grid[row: row + size]
) == 1
)
for grid in data
)
just used numpy, but in hindsight doing a zip would likely be easier
from typing import Optional
HORZ_S = 100
VERT_S = 1
def find_mirror(case: list[str], smudges = 0) -> Optional[int]:
case_t = list(map(''.join, zip(*case)))
for m, c in ((HORZ_S, case), (VERT_S, case_t)):
for i in range(len(c)-1):
if sum(
sum(int(a != b) for a, b in zip(line1, line2))
for line1, line2 in zip(c[i+1:], c[i::-1])
) == smudges:
return m * (i+1)
print(*case, sep='\n')
return None
with open(0) as f:
cases = list(map(str.splitlines, f.read().split('\n\n')))
for smudge in (0, 1):
print(sum(find_mirror(case, smudge) for case in cases))
inspired by xelf to be terse but readable
realized i never sent my solution 
Part 1
f = open('INPUT.IN')
def validate(matrix):
for i in range(1, len(matrix)):
l = min(i,len(matrix)-i)
valid = all(matrix[i-k] == matrix[i+k-1] for k in range(1,l+1))
if valid: return i
return 0
def evaluate(matrix):
transpose = [[matrix[i][j] for i in range(len(matrix))] for j in range(len(matrix[0]))]
return validate(matrix) * 100 + validate(transpose)
s = 0
while k := f.readline():
m = [list(k.strip('\n'))]
while (newline := f.readline()) not in ['\n','']:
newline = newline.strip('\n')
m.append(list(newline))
ans = evaluate(m)
print(ans)
s += ans
print(s)
Part 2
f = open('INPUT.IN')
def validate(matrix):
for i in range(1, len(matrix)):
l = min(i,len(matrix)-i)
# valid = all(matrix[i-k] == matrix[i+k-1] for k in range(1,l+1))
diff = 0
for k in range(1,l+1):
for j in range(len(matrix[i])):
diff += (matrix[i-k][j] != matrix[i+k-1][j])
if diff == 1: return i
return 0
def evaluate(matrix):
transpose = [[matrix[i][j] for i in range(len(matrix))] for j in range(len(matrix[0]))]
return validate(matrix) * 100 + validate(transpose)
s = 0
while k := f.readline():
m = [list(k.strip('\n'))]
while (newline := f.readline()) not in ['\n','']:
newline = newline.strip('\n')
m.append(list(newline))
ans = evaluate(m)
print(ans)
s += ans
print(s)
Wanted to post this here
type Split<S extends string, Delim extends string> =
S extends `${infer X}${Delim}${infer R}` ? [X, ...Split<R, Delim>]
: Delim extends "" ? []
: [S];
type To2dTuple<T extends string> =
T extends `${infer X}\n${infer R}` ? [Split<X, "">, ...To2dTuple<R>]
: [Split<T, "">];
type Reverse<T extends unknown[]> =
T extends [infer X, ...infer Rest] ? [...Reverse<Rest>, X]
: [];
type Transpose<T extends unknown[][], Col extends unknown[] = [], Result extends unknown[] = []> =
Col["length"] extends T["length"] ? Transpose<T, [], [...Result, Col]>
: T extends [][] ? Result
: T extends [infer Row, ...infer Others extends unknown[][]]
? Row extends [infer X, ...infer Rest]
? Transpose<[...Others, Rest], [...Col, X], Result>
: never
: never;
type IsMirror<A extends unknown[], B extends unknown[]> =
A extends [] ? false
: B extends [] ? false
: Reverse<A> extends [...B, ...infer _] ? true
: Reverse<B> extends [...infer _, ...A] ? true
: false;
type FindHorizontalMirror<Right extends unknown[], Left extends unknown[] = []> =
IsMirror<Left, Right> extends true ? Left
: Right extends [infer X, ...infer R]
? FindHorizontalMirror<R, [...Left, X]>
: [];
type Mul<A extends unknown[], B extends unknown[]> =
A extends [infer _, ...infer Rest] ? [...B, ...Mul<Rest, B>]
: [];
type Two = [unknown, unknown];
type Five = [unknown, unknown, unknown, unknown, unknown];
type OneHundred = Mul<Two, Mul<Two, Mul<Five, Five>>>;
type SolveBoard<T extends unknown[][]> = [
...Mul<FindHorizontalMirror<T>, OneHundred>,
...FindHorizontalMirror<Transpose<T>>,
];
type SolveBoards<T extends string[]> =
T extends [infer X extends string, ...infer R extends string[]]
? [...SolveBoard<To2dTuple<X>>, ...SolveBoards<R>]
: [];
type Solve<T extends string> = SolveBoards<Split<T, "\n\n">>["length"];
// Testing
type Test = Solve<`#.##..##.
..#.##.#.
##......#
##......#
..#.##.#.
..##..##.
#.#.##.#.
#...##..#
#....#..#
..##..###
#####.##.
#####.##.
..##..###
#....#..#`>;
right thread 
It doesn't work on the actual input though
it's too long and TypeScript doesn't like my sins
🥲
idk typescript but that type OneHundred looks cursed
It is pretty cursed
idk
I can't think of another way to do arithmetic
maybe Peano
how do you know that for each one there is only one that could have been smudged? (part 2)
part 1. ```py
with open("aoc13.txt") as f:
text = f.read()
blocks = text.split("\n\n")
blocklines = [block.split("\n") for block in blocks]
blockcollumns = []
count = 0
for block in blocklines:
blockcollumns.append(list(map("".join, zip(*block))))
def findsymetry(argument):
length = len(argument)
for i in range(1, len(argument), 1):
searchlenght = min(len(argument[:i]), len(argument[i:]))
if argument[:i][-searchlenght:] == argument[i:][:searchlenght][::-1]:
return i
return 0
for i in range(len(blocks)):
result = findsymetry(blockcollumns[i])
if result == 0:
result = 100 * findsymetry(blocklines[i])
count += result
print(count)```
that's just an assumption you make, so the problem makes sense
ie. has a unique answer
it's fine to assume the problem is well defined for aoc imo
https://paste.pythondiscord.com/OVMQ
do you know what is wrong with my part 2
it works for part 1
but the number outputted for part 2 is around 100 times larger than part 1
aoc says my answer is too high
is it overcounting in some way?
part 2: so you can have 2 axis in the same field?
The problem tells you so
Upon closer inspection, you discover that every mirror has exactly one smudge: exactly one . or # should be the opposite type.
with what logic are you detecting a smudge?
i'm just iterating through the 2d array and seeing if changing it to the opposite one makes a reflection work in either the horizontal or vertical axis
at that point, i break out of both loops and go onto the next 2d array (the breaking out i just added and it reduced my output to something similar to part 1 but is still not right for part 2)
wouldn't there always be 2 possible smudges?
I think it's designed so that there's only one solution
I was thinking does it matter if I change the smudge on the topside or the bottom. the reflection would still work
YEAR = 2023
DAY = 13
_U = 1
if _U:
from aoc_lube import fetch, submit
s = fetch(YEAR, DAY)
else: exec(r'''
s=r"""
#.##..##.
..#.##.#.
##......#
##......#
..#.##.#.
..##..##.
#.#.##.#.
#...##..#
#....#..#
..##..###
#####.##.
#####.##.
..##..###
#....#..#
"""
''');s=s.strip()
from itertools import islice
*r, = map(str.splitlines, s.split('\n\n'))
p1 = 0
tb = {}
for F in 100, 1:
for I, x in enumerate(r):
for i in range(len(x)-1):
if x[i] == x[i := i + 1]:
print("@@#!")
print(x[i-1], x[i])
i2 = i + i - 1
for j in range(i - 1):
if i2 - j < len(x) and x[j] != x[i2 - j]: break
if i2 - j < len(x): print(x[j], x[i2 - j])
else: break
else: continue
tb[I] = (F, i)
p1 += i * F
r = [[*zip(*x)] for x in r]
ff = lambda x, y: sum(a != b for a, b in zip(x, y))
new = tb.copy()
for F in 100, 1:
for I, x in enumerate(r):
#if I in found: continue
for i in range(len(x)-1):
c = ff(x[i], x[i := i + 1])
if c <= 1:
print("@@#!")
print(F, I, i, x[i-1], x[i])
i2 = i + i - 1
for j in range(i - 1):
if i2 - j < len(x):
c += ff(x[j], x[i2 - j])
if c>1:break
else:
if c==1:break
else: continue
t = (F, i)
if t != tb[I]:
print(t, tb[I])
new[I] = t
r = [[*zip(*x)] for x in r]
p2 = sum(x * y for x, y in new.values())
if _U:
submit(YEAR, DAY, 1, lambda: p1)
submit(YEAR, DAY, 2, lambda: p2)
else:
print(p1)
print(p2)
finally
no timeouts
def calculate_reflection(p1, p2, y=-1, smudge=False):
if e and len([u for u in range(len(d[e])) if d[e][u] != d[e - 1][u]]) < 2:
for i in range(e, len(d) + 1):
y += 2
if i - y < 0 or i == len(d):
if smudge: p2 += e * multiplier
else: p1 += e * multiplier
break
if d[i] != d[i - y]:
if not smudge and len([u for u in range(len(d[i])) if d[i][u] != d[i - y][u]]) == 1:
smudge = True
else: break
return p1, p2
with open("day13.txt", "r") as file:
data = file.read().split("\n\n")
p1 = p2 = 0
for grid in data:
h = [*map(list, grid.split())]
v = [*map(list, zip(*h))]
for d, multiplier in ((h, 100), (v, 1)):
for e, x in enumerate(d):
p1, p2 = calculate_reflection(p1, p2)
print(p1, p2)```
from itertools import product
from aoctk.data import Unbound2DGrid as U2DG
from aoctk.input import get_groups as gs
def mirror(m: U2DG, a: int, b: int, d: complex, t: int = 0) -> int:
c, fm = (a + b) / 2 * d, {_ for _ in m if a <= (d.conjugate() * _).real <= b}
return (
int((a + b + 1) / 2 * (1 if d == 1 else 100))
if len({_ - 2 * (d.conjugate() * (_ - c)).real * d for _ in fm} - fm) == t
else 0
)
def value(m: U2DG, t: int = 0) -> int:
for (b, d), s in product(zip(m.size(), (1, 1j)), (2, -2)):
for c in range(b % 2 if s > 0 else b - 1 - (b % 2), b - 1 if s > 0 else 0, s):
if n := mirror(m, c if s > 0 else 0, b - 1 if s > 0 else c, d, t):
return n
def solve(data: str, t: int) -> int:
return sum(
value(m, t)
for m in (U2DG.from_group(g, filter=lambda _: _ != ".") for g in gs(data))
)
def part_one(data="input.txt"):
return solve(data, 0)
def part_two(data="input.txt"):
return solve(data, 1)
made solve a one-liner XD
def solve(data: str, t: int) -> int:
return sum(
next(
dropwhile(
lambda _: _ == 0,
(
mirror(p, c if s > 0 else 0, b - 1 if s > 0 else c, d, t)
for (b, d), s in product(zip(p.size(), (1, 1j)), (2, -2))
for c in range(
b % 2 if s > 0 else b - 1 - (b % 2), b - 1 if s > 0 else 0, s
)
),
)
)
for p in (U2DG.from_group(g, filter=lambda _: _ != ".") for g in gs(data))
)
one liner gang let's go
I'm a bit confused here
what does this mean? what is language roulette?
a challenge where you solve the puzzle in a different language each day, the language is selected before the puzzle is released and is announced in the thread
Setup
with open('day13/input.txt') as f:
data = f.read().split('\n\n')
patterns = [[list(line) for line in pattern] for pattern in [pattern.splitlines() for pattern in data]]
ans = 0```
P1
```py
def solve(pattern):
for index in range(len(pattern[1:-1])+1):
if all(pair[0] == pair[1] for pair in zip(pattern[:index+1][::-1],pattern[index+1:])):
return index+1
for pattern in patterns:
val = solve(pattern)
if val:
ans += val*100
else:
ans += solve(list(zip(*pattern)))
print(ans)```
P2
```py
def solve(pattern,og):
for index in range(len(pattern[1:-1])+1):
if all(pair[0] == pair[1] for pair in zip(pattern[:index+1][::-1],pattern[index+1:])):
if index+1 == og:
continue
return index+1
for pattern in patterns:
og_val = (solve(pattern,-1),0)
if not og_val[0]:
og_val = (solve(list(zip(*pattern)),-1),1)
for ri,row in enumerate(pattern):
for index,char in enumerate(row):
new_pattern = deepcopy(pattern)
new_pattern[ri][index] = '#' if char == '.' else '.'
val = solve(new_pattern,og_val[0] if not og_val[1] else -1)
if val:
ans += val*100
break
else:
val = solve(list(zip(*new_pattern)),og_val[0] if og_val[1] else -1)
if val:
ans += val
break
else:
continue
break
print(ans)
Quite proud of my part1! I thought it was quite elegant. Not so much my part2....
As always, lmk any obvious improvements!
with open("aoc13.txt") as f:
text = f.read()
blocks = text.split("\n\n")
blocklines = [block.split("\n") for block in blocks]
blockcollumns = []
count = 0
for block in blocklines:
blockcollumns.append(list(map("".join, zip(*block))))
def findsymetry(argument):
for i in range(1, len(argument), 1):
searchlenght = min(len(argument[:i]), len(argument[i:]))
leftside, rightside = argument[:i][-searchlenght:], argument[i:][:searchlenght][::-1]
a = [x != y for x, y in zip(leftside, rightside, strict=True)]
if sum(a) == 1 or a == [True]:
index = a.index(True)
b = [x != y for x, y in zip(leftside[index], rightside[index], strict=True)]
if sum(b) == 1:
return i
return 0
for i in range(len(blocks)):
result = findsymetry(blockcollumns[i])
if result == 0:
count += 100 * findsymetry(blocklines[i])
else:
count += result
print(count)``` my p2
I just cleaned it up a bit
I like that I didn't have to brute force it completely
and I forgot to delete some testing artefacts it seems.
def find_mirror(case: list[str], smudges = 0) -> int:
case_t = list(map(''.join, zip(*case)))
for m, c in ((100, case), (1, case_t)):
for i in range(1, len(c)):
if sum(
sum(int(a != b) for a, b in zip(line1, line2))
for line1, line2 in zip(c[i:], c[i-1::-1])
) == smudges:
return m * i
raise ValueError
cases = list(map(str.splitlines, open(0).read().split('\n\n')))
print(*(sum(find_mirror(case, smudge) for case in cases) for smudge in (0, 1)), sep='\n')
importless common solver
so done with this now 😮💨
so turns out smudge can be at 2 postitions but the mirror only at one
yes! #aoc-solution-hints message
what do you think of my solution?
I like the way you do things step-by-step, it's easy to see what's going on and debug
although now that you understand what it's doing, there are several places you can simplify
by the way,
if sum(a) == 1 or a == [True]:
calling sum on[True]yields 1 as well
oh yeah. fixed that now
which is what makes recursion hard for me to understand. I have to think some more on day 12
###..##.######.##
..#..#.#......#.#
#.##.#..#.##.#..#
#.#.##.#..##..#.#
#..#...###..###..
#..#...###..###..
#.#.##.#..##..#.#
#.##.#..#.##.#..#
..#..#.#......#.#
###..##.######.##
...#.#..######..#
..###....#..#..#.
#.###....#..#....
##.#####.#..#.###
...#.....####....
..#...#.#.##.#.#.
...###.#......#.#```
where is the reflection point here
I'm sure I'm probably just being blind but I genuinely can't see it
part 2 or 1?
1
row 5
###..##.######.##
..#..#.#......#.#
#.##.#..#.##.#..#
#.#.##.#..##..#.#
#..#...###..###..
-----------------
#..#...###..###..
#.#.##.#..##..#.#
#.##.#..#.##.#..#
..#..#.#......#.#
###..##.######.##
...#.#..######..#
..###....#..#..#.
#.###....#..#....
##.#####.#..#.###
...#.....####....
..#...#.#.##.#.#.
...###.#......#.#
danke
AOC Day 13 SQL/DuckDB (Parts 1 and 2)
I was using someone's part 2 code to verify my code's part 1 output 😅 well that's a few hours I will never get back
lol.
My code above looks scary, but will run easily with a single pip install duckdb.
finally finished my day 13 part 1 solution, not sure how to approach part2 but I have a feeling that my approach did not give me a good baseline for part2. Any help appreciated (I don't really understand how to effectively find smudges)
https://paste.pythondiscord.com/PYLQ
how are you finding reflections rn?
I find 2 neighbouring cols/rows that are the same, then I step away from them in both directions one index at a time and compare the rows/cols at the new indices, e.g. 1221 I would first find 22, then compare 11 etc. until I reach the edge. If I reach the edge, I found reflection, otherwise I continue scanning
this should be convertible for part 2
hard to give a hint without spoiling it
My solution was to find adjacents with 0 or 1 smudge, then expand and accumulate smudges
So, smudge is just a row with one diff
spoiling is fine, I'm happy to learn new ways of solving these problems as I'm about to reach my coding limit
Yah, so in this case: find adjacents with either 0 or 1 diff
i basically checked all mirror positions until i found a working one
for two I look for one who with one row difference
then expand and accumulate smudges I don't get this part. I should be able to write a helper function that counts the number of dissimilar strings
and then I check if the two rows with one difference have a difference of one between them
instead of checking for equality of each col/row, check for how many differences they have
and then find the reflection that only has one difference across all the cols/rows
aaah this feels like a lot better solution than what I came up with 😄 thanks and @golden tide @inland hearth too ❤️
I basically xor the left and right side of the mirror.
Yeah, I tried a few variations, and in the end decided I liked [:i][::-1] as lleast confusing.
i did it like that as well. tried slicing in one but couldn't wrap my head around it
I don't golf! I'm just terse!
rotate = lambda b: list(map(''.join, zip(*b)))
smudge = lambda board,i: sum(sum(a!=b for a,b in zip(row[:i][::-1],row[i:])) for row in board)
mirror = lambda board: next((i for i in range(1,len(board[0])) if smudge(board,i) == 1),0)
totals = lambda board: mirror(board) + 100*mirror(rotate(board))
print(sum(map(totals,map(str.splitlines,open(aocinput).read().split('\n\n')))))
Yah, my solution is iterative so I never do a full comparison: I figured in part 1 that the search space would be too large in part 2, so didn’t want to check every possible point of reflection
So I pruned so I only looked at adjacent mirrors, then expanded 1 at a time
i don't think the difference in time is significant here. I guess its a bit faster
last few days scaling issues has us all concerned about speed 🙂
(your thing that is)
I never solved yesterday in sql
I might just try porting someone else’s solution
I wrote an ugly version in Python, but it’s not transferable to sql.
But there are some far cleverer solutions that might work
T E R S E
did you get part 1 working?
No. Will need to learn how to do recursion properly
For day 12 p1: ||You don’t have to use recursion to solve it. You can brute force p1, as many people did.||
Yes but do I want to do that? No
just for you:
smudges = lambda pattern,i: sum(a!=b for row in pattern for a,b in zip(row[~-i::-1],row[i:]))
recursion is still brute force. 🙂
You don’t have to use recursive brute force 😂 (for part 1)
You don't have to, you can simulate it instead. 🙂
Simulate as in permutations?
what is that ~ doing there?
being snarky. It's the same as i-1
print( [(i-1, ~-i) for i in range(5)] )
# [(-1, -1), (0, 0), (1, 1), (2, 2), (3, 3)]
~ is the bit inversion operator
Parts 1 + 2, part 2 wasn't the worst once I remembered that this is an AoC puzzle with the associated constraints, but it would be nice if they were given explicitly, ie. each pattern will only have one valid reflection line, each pattern with one smudge removed will have only one valid reflection line. py import numpy as np part_1 = 0 part_2 = 0 for index, line in enumerate(open(0).read().split("\n\n")): arr = np.array([*map(list, line.split("\n"))]) for mult in (1, 100): arr = arr.T for row in range(1, (arr.shape[0] + 1) // 2): top_end = arr[0:row] == arr[row:row*2][::-1] bottom_end = arr[arr.shape[0] - (row * 2):arr.shape[0] - row] == arr[arr.shape[0] - row:][::-1] if top_end.all(): part_1 += mult * row if np.sum(top_end) + 1 == top_end.shape[0] * top_end.shape[1]: part_2 += mult * row if bottom_end.all(): part_1 += mult * (arr.shape[0] - row) if np.sum(bottom_end) + 1 == bottom_end.shape[0] * bottom_end.shape[1]: part_2 += mult * (arr.shape[0] - row) print(part_1, part_2)
We had a spirited debate last night about this very topic. Some think the problem assumptions should be stated unambiguously; some think that teasing out the constraints is part of the puzzle.
I'm just lazy, imagine turning on your brain for a coding challenge :p
patterns supported MANY reflections if you evaluated the smudges in differet orders, so order of processing was an issue.
The problem here was that the constraints were not discoverable unless you had a correct answer or happened to do it the right way. You could get the right answer on the 1 test case and be screwed.
There is something to be said about the test cases not having full coverage. For example, my first part 1 code worked for the tests but not actual input, since both test cases are bottom ended reflection with 1 discarded row, leaving top ended reflection calculations and non-1 row bottom end discards untested. This is mitigated by being able to see all the inputs, but it does also hamper shareability of test cases due to the larger size.
for example, an approach that seems within the rules that gets you the correct answer on the test examples, but the wrong answer on part 2: iterating over possible smudges and then searching for reflections. The only way I found out this was wrong was when the answer was wrong and had to try something completely different. (iterate over failed reflections and look to see if a smudge would fix it)
The root of the issue is whatever algorithm was used to generate the inputs/calculate the answer. If either of those are biased, then the resulting problem has to also be solved with that bias, in this case checking for smudged reflections in a certain order. That is where explicitly stating the problem constraints would help, but at that point why no just go to leetcode. The biases in the used algorithms should obviously be minimized as much as possible, but while that is really easy to say, it is basically impossible until the problem reaches a large audience.
What I would suggest is more example test cases, they should be easy enough to generate. Perhaps have them hidden until after you submit a failed answer.
That would help a lot on the days where the test cases and actual input are orders of magnitude different in size instead of just more, like days 10 and 8.
Like "What number comes next" 2, 3, ...
With no other test cases, it's hard to know. are we looking at digits in order? prime numbers, numbers with a specific binary pattern?
Ah well. Bracing for tonight's possibly harder puzzle. 🙂
That took way too long to debug...
finished though?
I can certainly understand the frustration with the test cases not being comprehensive.
On the other hand, I appreciate it as a part of discovery on the problem. Spelling out all the corner cases makes it easier to code towards an answer, but removes part of the puzzle solving.
Looking back at 2023 Day 1 as an example:
||If the test cases included twone or oneight, then some of the potential hiccups of part 2 are removed and the answer is much easier for people to solve.|| I really do look at uncovering corner cases as a part of the puzzle.
i think it's an anti-AI strategy
It's absolutely that as well. You also see that with the weird argument orders. (Like the day where the arg order was dest, source, length)
I should make a website. AdventOfCodeHints.com that just posts extra testcases for each problem. 🙂
Double check Eric's policy around sharing test cases and puzzle inputs: https://www.reddit.com/r/adventofcode/wiki/faqs/copyright/inputs
r/adventofcode: Advent of Code is an annual Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be …
Yeppers. Silly little mistake.
Didn't bother with an OOP solution today
Elixir is quite nice
defmodule Aoc do
def twoscan(line, f) do
f.(line)
|> Enum.chunk_every(2, 2, :discard)
|> Enum.scan(fn x, acc -> Enum.concat(acc, x) end)
|> Enum.map(&f.(&1))
end
def count(start, delta) do Stream.iterate(start, &(&1+delta)) end
def segments(line) do
Enum.concat(
Enum.zip(count(1, 1), line |> twoscan(&(&1))),
Enum.zip(count(length(line)-1, -1), line |> twoscan(&Enum.reverse/1))
)
end
def mismatches(line) do
Enum.zip(line, Enum.reverse(line)) |> Enum.count(fn {a, b} -> a != b end)
end
def transpose(a) do Enum.zip(a) |> Enum.map(&Tuple.to_list/1) end
def find_mirror(lines, t) do
lines
|> Enum.map(&segments/1)
|> transpose
|> Enum.map(&Enum.unzip/1)
|> Enum.filter(fn {_, x} -> Enum.map(x, &mismatches/1) |> Enum.sum == 2*t end)
|> Enum.map(&elem(&1, 0) |> Enum.at(0))
|> Enum.sum
end
def solve(lines, t) do
(lines |> find_mirror(t)) + (lines |> transpose |> find_mirror(t)) * 100
end
end
input = IO.read(:stdio, :all)
|> String.trim
|> String.split("\n\n")
|> Enum.map(fn x -> x |> String.split("\n") |> Enum.map(&String.graphemes/1) end)
input |> Enum.map(fn x -> Aoc.solve(x, 0) end) |> Enum.sum |> IO.inspect(label: "Part 1")
input |> Enum.map(fn x -> Aoc.solve(x, 1) end) |> Enum.sum |> IO.inspect(label: "Part 2")
Pretty language!
I've definitely added |> to my list of nice language features I want more languages to have
(it looks even prettier with ligatures)
screenshot?
Very nice
it's kinda fascinating how fast one can pick up enough of a language to solve a problem
but yeah, this was for sure one of my nicer experiences so far
a mostly functional language that feels very flexible when writing code, with a great stdlib
Nice. Congrats
out of the ones so far this and Scala have been the highlights
seems like it might be a bit more fun to do several problems in a row on the same language so you have more time to explore it or build up some common functions like loading the data.
Right. It's kind of a mixed bag though
Some languages I would like to play with more
Some languages I'm just happy to be done with 😛
I can pick fortran again for you =P
fortran was indeed a thing
fortran does feel a lot like C in the sense of you getting nothing handed to you
and generics? forget it
I'm guessing C will show up sooner or later
I keep telling myself I'm going to do this, and then I just go back to doing the entire year in python
C's a very clean language... if you don't have to worry about all the footguns.
I can recommend picking some language you feel looks interesting and just stick with it for the month
Yeah it's definitely just a time investment thing. Programming isn't my only passion.
and the lack of generics that isn't spelled void*
I did rust last two years, but that kinda got samey
the beginning of the first year was very much me going "I know exactly what I need to write, but I don't know how to spell it in rust"
Sql sql sql!
I was doing some thinking, if this is the case, why does my solution work? I iterate over every possible reflection line in both directions with no early escape, so shouldn’t it break on part 2 if there were multiple valid un-smudged reflections?
I disagree for this problem specifically
I think that being guaranteed a unique solution for an aoc problem is a fine assumption to make
Just me being lazy and complaining because of it :p
There might be only one reflection per smudge. But there are multiple possible smudges for some patterns
I started doing them in Perl, for me python is the alternate language. 🙂
(or was, feel pretty comfortable in python now)
Your solves seem pretty comfortable in python!
Getting there. Haven't done much async yet. Did some pytorch, but not much pandas. And really need to brush up on numpy. It's pretty deep.
Recommend playing around with the httpx library for learning async. Or! Make a discord bot.
Is there an O(n) tilt solution possible? maybe n being number of #s? I don't see how that is possible without counting Os. Which makes it O(n^2)
Did you mean to post this in Day 14?
brute force today
welcome to day 13! you have time-travelled successfully
hehe :)
are vertical rows with smudges a thing?
Probably - just because it wasn’t in the example doesn’t mean it won’t be there
I might check my real input on this point when I’m back at my desk
Based on that my code returns 0 matches with cords that will have matches if you change one vertical point im prettu sure they are there
Do you mind restating this? I don’t follow you here
Ye it was pretty messy english lol had to type it quick
So basicly i figured by looking at my data that i currently have that some of them return 0 matches
And when i look at those paterns with 0 matches in my input i found out that they indeed dont have horizontal matches
Yet there is a option for a vertical match
Right - that makes perfect sense to me
how do i need to handle this case:
..#..####..#.
.############
.############
..#..####..#.
....##..##...
###......#.##```
it can both have a horizontal and vertical line
or does every patern have to have a new line
doesn't it just have a horizontal line of reflection?
ye but it also has a vertical one if you fix the smudge on the fifth character of the bottom row
oh, you're doing part 2?
yep
the puzzle says ||each mirror has exactly 1 smudge||
so i guess you have to always remove that smudge
yes, you only consider cases where it's off by 1
yay