#AoC 2022 | Day 9 | Solutions & Spoilers
1800 messages · Page 2 of 2 (latest)
and access at index and index-1
whole ass class there, just update the x, y attributes 😄
🎉
wooooo
my thing was roughly
last = head
for cur in tail:
# update cur wrt last
last = cur
```which was quite convenient
Which route did you take?
me?
no, doggo 😄
oh, which fix
events/advent_of_code/2022/9/python/script_day9.py lines 104 to 109
for index, (previous, knot) in enumerate(pairwise(knots), 1):
new_knot = move_knot(previous, knot)
knots[index].x = new_knot.x
knots[index].y = new_knot.y
visited.add((knots[-1].x, knots[-1].y))```
functional programmers seething
mutate all the things!

for index in range(1, len(knots)):
knots[index] = move_knot(knots[index-1], knots[index])
for direction, distance in data:
for _ in range(distance):
head = (head[0] + direction[0], head[1] + direction[1])
for i, (prev, next) in list([head, *tails]).windowed(2).enumerated():
if list(zip(prev, next)).mapped(lambda i: abs(i[0] - i[1])).max() > 1:
tails[i] = move_towards(prev, next)
visited[tails[-1]] = True
Yep, that passes too
(and is arguably simpler)
for my own solution I also did mutation, but I also had head separate from tail 
That's exactly what I did lmao
I still need to either pick a better hash table impl or implement my own thing...
What?
my solution is almost >1ms :/
too close for comfort
and I know the hash table is a big chunk of the runtime
like 2/3
I suspect my 1ms limit will be impossible soon enough, but not quite yet 😛
hope they have some cryptography based question this time. I love some of those
So uh is there a better way to do this than brute forcing all 12 directions and moving the tail?
For part 1
yes
I just knew part two would be ||a longer rope||, messing up the logic from my solution to part 1.
My solution for both parts today: https://paste.pythondiscord.com/sulufasebi.py
It's not very efficient, but ¯_(ツ)_/¯
I was confused about the warning about new types of movements in the p2 description
I bet tomorrow's will also involve a coordinate plane
turns out my logic was fine already
maybe some matplotlib
psyche, it's 4d hyperspace
If I have an issue with my code Im allowed to ask here right?
It'll probably have something to do with river currents
I haven't followed the story 🥲
theres a story !?
🧝
my part_one() solutions works and i get the right answer, but for part_two() it always prints out 1, no matter what set of input instructions I use. I can’t see what i’m doing wrong… any help? https://hastebin.com/itixoqavey.py
Hastebin is a free web-based pastebin service for storing and sharing text and code snippets with anyone. Get started now.
elf
rude
||x||elf
||s||elf
Ah right yeah. My logic for part 1 was to move the tail to the intersection of two sets: the cardinal neighbours of the head, and the ordinal+cardinal neighbours of the tail. It turns out this doesn't work if the head can move diagonally (the sets don't intersect).
ok, time to actually be at my keyboard to improve my solution from this morning
So for part 2, I found the intersection of the ordinal+cardinal neighbours of both sets, then picked the coordinate out of these that has the minimum Manhattan distance to the head.
By head, I mean the previous knot in the rope.
why not just: if >1 L∞ norm, then move x and y closer by 1 if you can?
i.e. if max(xdist, ydist) > 1, move closer
and the move closer part is just x += sign(dx)
(same for y)
I believe it has to do with my Knot.follow(a, b) method, but im not sure what needs to be changed
Yeah, that's what I do. What I explained above is just the logic of figuring out where to move.
Oh right yeah
I find it a bit easier to reason in terms of sets than numbers, for some reason.
we're definitely going to have to like, climb out of the ravine somehow
def some 3d pathfinding thing
Finally got it
wow
from collections import namedtuple
def tail_in_range(tail: list[int], head: list[int]) -> bool:
return (tail[0] - head[0] in [-1, 1, 0]) and (tail[1] - head[1] in [-1, 1, 0])
def position_change(val_tail: int, val_head: int) -> int:
if val_tail - val_head > 0:
return -1
elif val_tail - val_head < 0:
return 1
else:
return 0
def solve(amt: int) -> int:
Movement: namedtuple = namedtuple("Movement", "d n")
movements = [Movement(d, int(n)) for d, n in [m.split() for m in open("input.txt").read().strip().splitlines()]]
seen = list()
knots: list[list[int]] = [[0, 0]] * amt
seen.append(knots[0])
changes: dict[str: list[int]] = {"U": [0, 1], "D": [0, -1], "L": [-1, 0], "R": [1, 0]}
for move in movements:
for _ in range(move.n):
dx, dy = changes[move.d]
knots[0] = [knots[0][0] + dx, knots[0][1] + dy]
for i in range(1, amt):
tail = knots[i]
head = knots[i - 1]
if not tail_in_range(tail, head):
match move.d:
case 'U' | 'D':
knots[i] = [tail[0] + position_change(tail[0], head[0]),
tail[1] + position_change(tail[1], head[1])]
case 'L' | 'R':
knots[i] = [tail[0] + position_change(tail[0], head[0]),
tail[1] + position_change(tail[1], head[1])]
if knots[-1] not in seen:
seen.append(knots[-1])
return len(seen)
print(f"Part 1: {solve(2)}")
print(f"Part 2: {solve(10)}")```
it used to be way longer
then decided to start from scratch
Thoughts/improvements?
part 1 :
from dataclasses import dataclass
def read_file(filepath):
with open(filepath) as file:
return file.read()
@dataclass
class Coord():
x: int
y: int
def __hash__(self):
return hash((self.x, self.y))
head_movements = {
"R": lambda coord: Coord(coord.x + 1, coord.y),
"L": lambda coord: Coord(coord.x - 1, coord.y),
"U": lambda coord: Coord(coord.x, coord.y + 1),
"D": lambda coord: Coord(coord.x, coord.y - 1),
}
def dist(first, second):
return Coord(x=first.x-second.x, y=first.y-second.y)
def norm(coord):
n = Coord(x=0, y=0)
if coord.y != 0:
n.y=coord.y//abs(coord.y)
if coord.x != 0:
n.x=coord.x//abs(coord.x)
return n
def tail_movements(tail_coord, head_coord):
d = dist(head_coord, tail_coord)
if abs(d.x) <= 1 and abs(d.y) <= 1:
return tail_coord
n = norm(d)
return Coord(x=tail_coord.x+n.x, y=tail_coord.y+n.y)
def part1(content):
head_position = tail_position = Coord(x=0, y=0)
visited = {head_position}
for instr in [l.split(" ") for l in content.strip().split("\n")]:
for i in range(int(instr[1])):
head_position = head_movements[instr[0]](head_position)
tail_position = tail_movements(tail_position, head_position)
if tail_position not in visited:
visited.add(tail_position)
return len(visited)
input_data = read_file("../input.txt")
print(part1(input_data))
part2 is hard
it's just like part 1
and part2 with the same design
def part2(content):
nodes = [Coord(x=0, y=0)] * 10
visited = {Coord(x=0, y=0)}
for instr in [l.split(" ") for l in content.strip().split("\n")]:
for i in range(int(instr[1])):
nodes[0] = head_movements[instr[0]](nodes[0])
for i, n in enumerate(nodes):
if i == 0:
continue
coord = tail_changes(n, nodes[i-1])
if coord == n:
break
nodes[i] = coord
if nodes[-1] not in visited:
visited.add(nodes[-1])
return len(visited)
took me 1h TT__TT
there's more than 3 indentations levels too, which is unacceptable
fwiw str.splitlines exists
it accounts for CRLF and LF at the same time
solved
open() does that for you with universal line endings.
for real?
!pep 278
it's the default in python 3
what am i misunderstanding about part2?
this step doesnt make sense to me
when the head starts going up, each knot needs to go 1 in the direction of the next knot
(if the distance is > 1)
so why isnt that panel like ⬇️
after 2 moves, 3 is not directly to the left of 3. It's slightly down as well.
def move_rope(data, length):
visited = set((0, 0))
rope = [np.array([0, 0]) for _ in range(length)]
for row in data.splitlines():
dr, x = row.split()
for _ in range(int(x)):
rope[0] += {'R': (1, 0), 'L': (-1, 0), 'U': (0, -1), 'D': (0, 1)}[dr]
for i in range(1, len(rope)):
dir_vector = rope[i-1]-rope[i]
if abs(dir_vector).max() > 1:
rope[i] += dir_vector.clip(-1, 1)
if i == len(rope)-1:
visited.add(tuple(rope[i]))
return len(visited)```
1st day after "learning" numpy. Working with vectors is a lot nicer than using tuples.
You know you can use * on the list?
numpy will unpack all the list instances to its own values
np.array([[0, 0]] * length)
ah thank you, thought it was gonna do the usual thing
You could also do ```py
np.array([0] * 10 * 2).reshape([10, 2])
array([[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0]])
not sure i get this either,
H moves one up, 1 moves one up diagonally to the right to stay in range, 2 moves where 1 is, 3 moves where 2 was, etc
all move (possibly diagonally) to stay in range of the next knot
but theyre in range if only 1 moves diagonally and everything shifts to the right? no?
you're moving every time instead of moving when the one in front of you is over 1 away.
so you should use pairwise
any other things that could've been done smarter with numpy?
Not convinced this was a great numpy problem. But willing to be convinced if there's a great np solution.
well just for ease of use operations wise
because you're missing the middle state?
oh you used complex numbers that solves any need to use arrays
whats the middle state then
start:
......
......
......
....H.
4321..
h moves 1 up:
......
......
....H.
......
4321..
1 follows:
......
......
....H.
....1.
432...
2 follows:
......
......
....H.
...21.
43....
etc....
......
......
....H.
.4321.
5.....
after step "1 follows:" knot 2 can move to where 1 was and be in range
why would it move diagonally up to the right and not just right
they always move diagonal
if they're not on the same row/col
What I did was put the distance into a numpy array and used the sign function
np.sign(head - tail)
Ironically, I think I did part 1 wrong, but still ended up with the right answer.
If the head is ever two steps directly up, down, left, or right from the tail, the tail must also move one step in that direction so it remains close enough.
Otherwise, if the head and tail aren't touching and aren't in the same row or column, the tail always moves one step diagonally to keep up:
i am actually blind
lmao i did part1 without taking this into account, false positive i guess
i think the easiest solution for part1 makes any consideration of that unnecessary
You got the right count, but the wrong places. That is lucky I'm surprised the input didn't account for that.
if abs(rope[i+1]-rope[i])>=2: how does this work with c numbers?
abs() returns the magnitude.
negatives are important for direction.
ah, math has been too long
This made zero sense to me. I can only assume (hah) there's an assumption people are likely to make in P1? I gave it zero consideration and my P1 logic for movement was unchanged for P2.
^ I used litteraly the same movement algo
it happens to work for funky reasons
"if you're more than 2 away" is literally the metric from the puzzle. 🙂
2 in L^∞ metric, no?
you'd be screwed if it was something other than 2 right?
well it was better than my original method, which calculated all the neighbors and then checked for membership.
near = lambda x: {x+complex(dx,dy) for dx in (-1,0,1) for dy in (-1,0,1)}
if rope[i+1] not in near(rope[i]):
it depends a bit, but basically
0 is ok, 1 is not, 2 is ok, 3 and above I think is not ok
i guess still a neat trick if it works
i guess you could also just do max(abs(x.real),abs(x.imag)) > 1
more like eww
haha
def next_tail_pos(tail_coord, head_coord):
d = dist(head_coord, tail_coord)
if abs(d.x) <= 1 and abs(d.y) <= 1:
return tail_coord
n = norm(d)
return Coord(x=tail_coord.x+n.x, y=tail_coord.y+n.y)
this is how I did it
is norm() like the sign() function?
normalize would be the better term
idk what's the sign function ?
!d numpy.sign
numpy.sign(x, /, out=None, *, where=True, casting='same_kind', order='K', dtype=None, subok=True[, signature, extobj]) = <ufunc 'sign'>```
Returns an element-wise indication of the sign of a number.
The [`sign`](https://numpy.org/devdocs/reference/generated/numpy.sign.html#numpy.sign "numpy.sign") function returns `-1 if x < 0, 0 if x==0, 1 if x > 0`. nan is returned for nan inputs.
For complex inputs, the [`sign`](https://numpy.org/devdocs/reference/generated/numpy.sign.html#numpy.sign "numpy.sign") function returns `sign(x.real) + 0j if x.real != 0 else sign(x.imag) + 0j`.
complex(nan, 0) is returned for complex nan inputs.
give it a number and it will return -1 if the number is negative, +1 if it's positive, and 0 if it's 0
yes
is normalization even meaningful in L^∞ norm?
To me, normalization is clamping a number between -1,1
it gives you the direction to go to get closer to the head
yea but normalization wouldn't work for (2,1)
normalization for vectors scale the vector to length 1
and since the max movement distance is limited to [-1, 1], the direction and the "offset" for the next move are the same thing
which is unambiguous for the regular euclidean norm
I was working on figuring out the most efficient way to do this, and what I came up with so far was min(max(x, -1),1). Has anyone found anything shorter?
clamping is not normalization in the math sense
you scale to normalize
clamping is...clamping
!! you're a genius, I can actually use that as impl of my norm() func
that's exactly that, with a a little handling if x is 0
do you think min(max)) is faster than the math norm with the extra ifs ?
which is projecting onto the unit circle
only the unit circle in our L^∞ norm is a square 😛
(if you cared about such level of optimization you wouldn't use python)
I'm just interested
I hate my implementation if the 2 ifs to check if the x/y are 0 or not
Of course you would. Stop slow-shaming Python you brute
it's ugly af
do the cleaner impl
def clamp(x):
return max(-1,min(1, x))
That's a good one, I can use that to chop a couple characters off. ```py
min(max(x, -1),1) # OLD
x/max(abs(x),1) # NEW
imo
why do you need the max?
or even better
x/abs(x or 1)```
oh div by 0
err, didn't mean to reply to that
yup
ok I think this looks a little messier, but no abs(complex) for you puritans.
if not(-1<=(a := rope[i].real - rope[i+1].real)<=1):
rope[i+1] += a//abs(a)
if not(-1<=(b := rope[i].imag - rope[i+1].imag)<=1):
rope[i+1] += b//abs(b)*1j
*1j ?
complex numbers.
witchcraft
can do anything you want if you can just make up numbers
my impl went like
Python uses j instead of i, often math uses i for "imaginary" numbers
signum is what you want here
oh no I just hate math in general. j or i it's not really the problem lol
math good :V
This is my implementation for the tail following
yeah, that's the same as I did except for using a sign function
items is a dict of py { 0: [0,0], 1: [0,0], ... }where items[0] is the head and items[max(items.keys())] is the end of the tail
I suppose I could use list with enumerate instead of a dict
I tried to enumerate a slice of items except the first one and I got so confused : the index python gives you is the index in the slice, not the index in the list
separating the head and the tail makes the code a bit neater
so complex numbers are also represented as tuples of 2 numbers, which is what we have here.
the nice thing about complex numbers though, unlike tuples of numbers, is that you can add/subtract them.
for instance here was how I handled moving the head:
path = {'L':-1,'R':1,'U':1j,'D':-1j}
rope[0] += path[line[0]]
whatever the direction is, I just add that to the head.
vade retro
I thought about it, but I ended up not really seeing any reason to
so what's you're saying
you couldn't be bothered to write your own abstraction so you used one that did pretty much what you wanted
I wrote my own abstraction, I feel dumb now
1j**'RUL'.index(x) you could code golf that for fun
I'm trying to avoid golf.
other than anything I accidentally tursly right cause I hate excess code.
but this is actually a cute trick 😭
It is a cute trick though. 🙂
but yeah, I don't code golf much anymore
I had fun code golfing in C a while back
it teaches you weird corners of the language...
abs(int) is fine
if abs(a := rope[i].real - rope[i+1].real) > 1:
rope[i+1] += a//abs(a)
if abs(b := rope[i].imag - rope[i+1].imag) > 1:
rope[i+1] += b//abs(b)*1j
(alcohol pun not intended)
a shame there's no 3 ways operator in python
really ? why did it get removed ?
idk the rationale
stuff like sorted could also accept cmp functions rather than key functions
found this : https://peps.python.org/pep-0207/
yk... rust has a built-in cmp method 👀
we already had that discussion a while ago on golf chat
C++ recently got <=>
else the golf would've been way loweer
(spaceship operator)
I'm trying to learn rust, but all those "weird" things with lifetime and borrowing are making my life difficult 🙂
python has made you soft 😛
it's a whole new concept for me
I guess coming from something like python it might be a bit much
give it 5 years and code won't be needed to be written. chatgpt ftw
well it's pretty unique right : you declare a variable and call a function on it; now the variable is destroyed
coming from C and C++ it felt not so weird
if you move everything I guess not
I'm pretty sure modern C++ uses ownership semantics a lot
but I haven't given up yet, am doing the advent of code in all 3 languages (at least trying)
it doesn't have a destructive move though
std::move is only a cast
but sure, there is ownership semantics in things like unique_ptr
I can’t do it in one 
ok cool, moving away from a hash map slashes my time to 1/3
<1ms dream lives another day
how do you maintain the count of what you've visited? BTreeSet?
hash a struct?
hm?
my part_one() solutions works and i get the right answer, but for part_two() it always prints out 1, no matter what set of input instructions I use. I can’t see what i’m doing wrong… any help? https://hastebin.com/itixoqavey.py
Hastebin is a free web-based pastebin service for storing and sharing text and code snippets with anyone. Get started now.
def solve(n: int, data: str) -> int:
ks = [0] * n
v = {0}
def update(h, t):
d = h - t
return (
t
if abs(d) < 1.5
else t
+ complex(
int(d.real / (abs(d.real) - 0.1)), int(d.imag / (abs(d.imag) - 0.1))
)
)
for d, s in get_tuples(data):
w = {"U": 1j, "D": -1j, "R": 1, "L": -1}[d]
for _ in range(int(s)):
ks[0] = ks[0] + w
for i in range(1, n):
ks[i] = update(ks[i - 1], ks[i])
v.add(ks[-1])
return len(v)
what looks like is happening is that your rope is "detatching", i.e. you aren't covering all possible movement cases and so part of the rope just stops moving forever
thank you, I will double check my follow(self, other) function and make sure im covering all the cases
because sometimes when i call follow() it doesn't go to any of the if statements
I need some help figuring out what's going on with my code. It works for both of the example cases, but it's too low as as soon as I try it on my actual input
https://paste.pythondiscord.com/idopodipuc
if i put a else clause at the end the function goes to that a lot
which shouldn't be happening
remember that with the 10 knots there are more possible movement cases
as stated on the website
then with the 2 knots
i see that it says that, but it also says
Each knot further down the rope follows the knot in front of it using the same rules as before.
so im not sure what these new rules / cases are
consider this example, what happens if you move up twice?
when knot 5 is moved, knot 6 will be 2 down and 2 to the left from it
on the 2nd move
ahh, so now it isnt guaranteed that a knot will be at most, 2 units away from another knot
now it can be 3?
by 2 units do you mean 3 Manhattan distance?
yes
perhaps (yes)
small thing: the way you have your logic structured would be a great use for match
in what way?
some other cases are also slipping through upon further inspection
such as this # other.x = self.x + 1, other.y = self.y - 1
i understand for the move(self, direction) command I can change it to python def move(self, direction): match direction: case ‘R’: self.x += 1
perhaps an iterative approach would be less error-prone then manually checking all the possible conditions
since there are more of them now
Alright, nice. Found the case I was missing :)
yea as I was writing it all out, I knew there had to be a better way… I just couldn’t figure out how it would look
consider the itertools.permutations function
i will take a look at the documentation for it right now
also, what's the relationship between other.x - self.x, other.y -self.y and the actual change you want in the position?
that would be something to consider
im not sure im totally following, but I will try messing around with it for a bit
an array of bools
adjacency matrix !! (jok)
ah, nice. what n did you get? it's probably only like 200 right?
200 is enough I think, I went a bit higher to be safe
oh cool, the point counting went from like 2/3 of runtime to <10%
I wonder if the simulation of the thing could be made faster, I can see some potential for simd, but that feels like way too much work
yeah minimizing n would improve it quadratically

why?
I think it's just hashmaps having a really big constant factor
you have to loop over all points to see which are true right?
the complexity should be the same
no
when I try setting a point, if the current value is false increment a counter as well
right, it would probably help
though the array should still be faster
this is basically a hashmap with perfect hashing
yeah it's direct addressing
hmm, I think simd is actually doable
but a bit awkward
you compute manually until you have a diagonal like this, i.e. the head (1) is 5 along, next tail piece lags one behind, next tail piece one more behind, and so on
....1
...2
..3
.4
5
the next element on row 2 depends on 2 and 1
the next element on row 3 depends only on 2 and 3
and so on
so no data dependencies
so you can compute the next diagonal in parallel
ok i rewrote follow to be a bit more clear, and i even put comments to make sure i was covering all cases, but I’m still only getting 1 as my answer
Hastebin is a free web-based pastebin service for storing and sharing text and code snippets with anyone. Get started now.
(but god I don't want to write that)
but it could give a significant speedup
if you shove coords in 8 or 16 bits, potentially a really big speedup
4x or maybe 8x
im covering the cases where the tail a knot needs to move North, East, South, West, Northeast, Northwest, Southeast, Southwest
idk what other possible moves it could even make
the tail can only move in those directions
im confused lol my brain is melting
how are you covering those cases?
https://hastebin.com/ohobaqokas.py in the Knot.follow(self, other) function here
Hastebin is a free web-based pastebin service for storing and sharing text and code snippets with anyone. Get started now.
with a very long if/elif chain that im sure can be made better but its all i got at the moment
that's not all cases in part 2
you can get a case like
..B
...
A..
(and yeah, there are better ways to do this that is not hard coding cases)
you can have that case?
oh if B is previously northeast of A, and then B gets pulled northeast again due to C moving somewhere
... ..3 ..3
..3 ... ..2
.2. .2. ...
1.. 1.. 1..
(I probably reversed the numbering the problem used)
i understand the scenario youre trying to describe
i think i know what i need to add to account for that, but still lost on how to use itertools.permutations to make it all very clean and concise
you can look at the differences in coordinates
that will tell you if you need to move (chrcking distance) and in which direction to move
oh my god
i bet someone else did this as well
but this working
made me so happy
LMAO
GLORIOUS
my why didn't you just use a loop
can't have a loop in your rope
U 2
D 2
L 1
R 1
L 1
R 1
B A
hacks
can anyone explain to me how to do part 2 of todays challenge? I'm not quite sure on how to do it.
basically in part 1 you have a head knot and a tail knot
and in part 2 you get a chain of multiple knots
yup i understand that part
I was trying to give a better explanation but came up short sorry XD
yea no like i understand the problem its asking
its just im not quite sure on how to solve it
if you have a chain: T-1-2-3-4-H
Then 4 behaves as H's tail, and as 3's head
H is the only one that moves and then you have to update all the tails in a descending order, hopefully with the same logic you already have defined for part 1
well the way i solved part 1 was just to simulate the entire movement and then log the unique positions that the tail visited
however the problem i have with part 2 is this (the example)
im not sure why the 4,3,2 also moves up with 1
they move up-right with 1
but cant they stay below and that would still be okay? as 2 is diagonal to 1?
2 would not touch 1
did you make a visualisation for this day die?
yep
can I see?
oh damn thats neat
okay my brain is absolutely dead. lets say the roped ended at 4, would that have been an okay move then?
2 and 1 don't touch in my post
there's a column separating them
Where do you think the knots came from??
Start:
......
....H.
4321..
Head moves:
....H.
......
4321..
1 Follows:
....H.
....1.
432...
2 Follows:
....H.
...21.
43....
3 Follows:
....H.
..321.
4.....
4 Follows:
....H.
.4321.
......
What do you use to make these UIs?
!pypi nurses_2
cool
something something planck length something something quantum mechanics
okay thank you for making the visualization but im still a bit confused
....H.
....1.
432...
to
....H.
...21.
43....
why did 2 have to move up? could it not have move to the right? like
....H.
....1.
43.2..
that's not how the rules specify it should move
so it also has try to follow the direction given? like in this case Up?
Otherwise, if the head and tail aren't touching and aren't in the same row or column, the tail always moves one step diagonally to keep up:
thank you so much
i must have misread that
illustrated with the example
..... ..... .....
..... ..H.. ..H..
..H.. -> ..... -> ..T..
.T... .T... .....
..... ..... .....
..... ..... .....
..... ..... .....
..H.. -> ...H. -> ..TH.
.T... .T... .....
..... ..... .....
np
is it guaranteed that s is always at the bottom left corner?
you know what would be fun? up the ante and make this problem higher dimensional
RLUDIO
it doesn't matter where s is
s can be wherever you want it to be since it's technically an infinite board
oh wait yeah lmao
I think most people would've had the start be 0,0
i was gonna use numpy for this but ngl i might just use a cartesian plane
but you could set it to like 69,420 and the code would still work
(this won't at all screw people who used complex numbers over)
conway's 4d rope tomorrow
so i didn't specify a starting location and only counted the distance.
against my better judgement I wrote the setup to be able to have simd...
time to implement RxC
now I would have to think about how to actually simd this operation
for j in i..N {
let cur_x = diag_x[j];
let head_x = diag_x[j-1];
let dx = head_x - cur_x;
let cur_y = diag_y[j];
let head_y = diag_y[j-1];
let dy = head_y - cur_y;
let distx = dx.abs();
let disty = dy.abs();
if distx.max(disty) >= 2 {
diag_out_x[j] = diag_x[j] + dx.signum();
diag_out_y[j] = diag_y[j] + dy.signum();
}
}
(and learn how I do such intrinsics in rust)
hey any1 minds to look into my code i get the example correctly everything moves as it should but i cant get my puzzle input to work i have no clue whats wrong
that'd basically be like starting at 0,0 tho
since at the start the rope should be 0,0 distance from the start
id love that
100% agree
it'd be limited at 4D I think
since it'd be annoying to introduce new LR equivalents for each newer dimension
who says you have to stick to letters :P
1 -3
3 10
``` means move -3 in dimension 1, +10 in dimension 3
that way you can use any hashable as a dimension 
basically what i had in mind for my infinity tictactoe
you could also use lower/upper case, or odd and even numbers.
fair
1j 5
nothing wrong with using classes
you could argue complex numbers are easier but if you're not familiar with them classes work just fine
does this logic make sense for part 1? if we need to move the tail along along a horizontal or vertical axis, the direction we move it will be the same direction we just moved the head, so I can just reuse that command on the tail:
import numpy as np
def need_move(h, t):
return (max(abs(h[0] - t[0]), abs(h[1] - t[1])) > 1)
with open ("moves.txt", "r") as f:
moves = f.read().split('\n')
directions = {
'U': np.array([0,1]),
'D': np.array([0,-1]),
'L': np.array([-1,0]),
'R': np.array([1,0])
}
head = np.array([0,0])
tail = np.array([0,0])
for m in moves:
d, dis = m.split()
for _ in range(int(dis)):
head = head + directions[d]
if need_move(head, tail):
dx = head[0] - tail[0]
dy = head[1] - tail[1]
if (dx and not dy) or (dy and not dx):
tail + directions[d]
else:
#diagonal move
pass
that's basically what i did.
now i just need to figure out the diagonal logic
works for part 1...
...but not part 2?
look at using numpy.sign on the array([dx, dy])
...
to replace my need_move ?
also, the if check should compare if abs(dx) > 1 or abs(dy) > 1
need_move is basically calculating dx and dy
yeah, but i never return it
just using it as a bool to determine if the tail is in range of the head
Looks good to me. It probably works.
except I would add the sign to the tail via +=
oh yeah typo
tail += np.sign(np.array([dx, dy]))
but we know it will be because its called after need_move no?
your need_move function is called the chebyshev distance and it's included in numpy already! np.linalg.norm(..., ord=np.inf)
I didn't read that function
or np.linalg.norm(head - tail, ord=np.inf) to be specific
wow, of course theres a function for that
alternatively, you can vectorize your operations: abs(head - tail).max()
i guess i shouldnt be surprised for using numpy
i used wanted easy array addition haha
ill probably use this, because there is no way i will ever remember what the hell my code does if i use that other function
that's fair, but you can look up p-norms on wiki if you want to learn about the ord= arg on the norm function (ord=2 is normal euclidean distance, ord=1 is manhattan distance, and ord=inf is chebyshev distance or chessboard distance)
guess i should have paid attention in algos
it's more math-y than algo-y, might not be covered
https://sabbiu.com/p-norms-taxicab-norm-euclidean-norm-and-infinity-norm here's an article on it
In Linear Algebra, norms are the measure of distance. Here, we derive 1-norm, 2-norm and infinity-norm and visualize them as a unit circle.
(a^n+b^n+...)^1/n
so like
def need_move(h, t):
return np.linalg.norm(h- t, ord=np.inf) > 1
yeah i think that works
with absolute values
i genuinely cannot read
made a whole vis with matplotlib only to realize that it was only asking for the places tail 9 visited, not the entire rope
holy shit lmao
an infinitely long rope would've been interesting
i don't think the tail would move
If it's infinitely long, it would just be 1
def newposition(ly,lx,fy,fx):
#"l" and "f" used as in leader and follower
ymoved = False
xmoved = False
if ly-fy > 1:
fy+=1
ymoved = True
elif ly-fy < -1:
fy-=1
ymoved = True
if lx-fx > 1:
fx+=1
xmoved = True
elif lx-fx < -1:
fx-=1
xmoved = True
if xmoved and not ymoved:
fy = ly
elif ymoved and not xmoved:
fx = lx
return fy,fx
my function for moving the follower
why? you can move the head twice and a knot would move too
ooh that's a nice problem
you can't just make an infinitely long rope at the start
I would probably use like a defaultdict and just move a knot when it's needed
depends on if a knot moving back to the start counts tho
The tail wouldn't move, because the tail is at the end. But if it's infinite, is their a tail at all?
oh also this shoudnt be an else it should be elif: dx and dy
or wait that is else
lol
usually considered bad practice to compare booleans like this in an if statement : if xmoved and ymoved == False:
instead use if xmoved and not ymoved:
wouldn't this just be the maximum chebyshev distance of the head from the origin
I'm sad, my simd didn't end up much faster 😦
like 5% faster at best
maybe forcing the problem into a simd-able state makes things slower overall
yea, but they said how many knots got moved not necessarily the tail
what's a simd?
(at least I'm now familiar with the simd stuff in the rust stdlib, it feels quite user friendly)
single instruction multiple data
pretty sure how many knots is actually a easier problem than part 1 of the original
special instructions that can compute things in parallel
because you only need to track the head
ahh, I see
would this be considered fine or is it better syntactically to just do 4 if ... and ...s
if dx > 0:
if dy > 0:
tail += directions['RU']
tail += directions['RD']
else:
if dy > 0:
tail += directions['LU']
tail += directions['LD']
You know what my solution would actually handle this super easily
this seems wrong
sure
import numpy as np
def need_move(h, t):
#chebyshev distance
return np.linalg.norm(h - t, ord=np.inf) > 1
with open ("moves.txt", "r") as f:
moves = f.read().split('\n')
directions = {
'U': np.array([0,1]),
'D': np.array([0,-1]),
'R': np.array([1,0]),
'L': np.array([-1,0]),
'RU': np.array([1,1]),
'RD': np.array([1,-1]),
'LU': np.array([-1,1]),
'LD': np.array([-1,-1])
}
head = np.array([0,0])
tail = np.array([0,0])
for m in moves:
d, dis = m.split()
for _ in range(int(dis)):
head = head + directions[d]
if need_move(head, tail):
dx = head[0] - tail[0]
dy = head[1] - tail[1]
if (dx and not dy) or (dy and not dx):
tail += directions[d]
else:
if dx > 0:
if dy > 0:
tail += directions['RU']
tail += directions['RD']
else:
if dy > 0:
tail += directions['LU']
tail += directions['LD']
idk thats how i would move the tail
oh yeah that too
honestly im not even sure what that means
note that when you do need to move, you move in the directions of the dy , dx --- that is, you move in the sign of them
only x,y i think
Right I missed the need_move bit
Eniraa's suggestion is the right answer then
Composing the translations means check x movement, then check y movement, but they don't need to know about each other
my thinking was when you need to move in a cardinal direction you are moving in the same direction as the head just moved
so thats why i have += directions[d] which is what the head just did above
yep, but the sign of a dx or dy that is 0 will be 0 as well
good point didn't think about that
tail += np.sign(head - tail) is the one-liner for all that logic
This isn't true for
..^..
..H..
...1.
..2..
Im guessing thats some part 2 chicanery
1 goes up-left, 2 goes straight up
pretty sure, we're still on part 1
Oh lol
yeah
Mb
i already know what part 2 is at this point from watching the visualizations
for that entire if else i had?
yep, i think you can work it out
Yep, we take the difference of the head and the tail and the magnitude tells what direction to move
I feel stupid after writing all that and it’s a function already
that's numpy -- will continue to do that for you for years
for nim i had to define all my vector operations 😦
type vec2 = array[2, int]
using a, b: vec2
proc `+=`(a: var vec2, b) =
a[0] += b[0]
a[1] += b[1]
proc `-`(a, b): vec2 = [a[0] - b[0], a[1] - b[1]]
proc sign(n: int): int =
if n > 0: 1 elif n < 0: -1 else: 0
proc sign(a): vec2 = [a[0].sign, a[1].sign]
proc chebyshev(a): int = max(a[0].abs, a[1].abs)
proc simulate_rope(nknots: int): int =
var
rope = collect(for _ in 0..<nknots: [0, 0])
seen = toHashSet [[0, 0]]
for (dir, n) in commands:
for _ in 0..<n:
rope[0] += dir
for i in 0..<nknots - 1:
let delta = rope[i] - rope[i + 1]
if delta.chebyshev > 1: rope[i + 1] += delta.sign
seen.incl rope[^1]
seen.len
part 1: simulate_rope 2
you can just add random functions to existing types in nim?
so nim uses uniform function call syntax
right, I was about to ask
so the a.method(...) is the same as method(a, ...)
(these some people included Bjarne Stroustrup)
which is the my_proc argument no parens
if you mark all these procs with {.inline.} pragma, probably this is just as fast as c++
I mean, this is basic enough stuff where I hope that would be the case
does nim use llvm or do they do something custom?
can compile to c

using llvm or whatever
or nim -> c -> llvm rather, i guess. i don't know all the logistics here
right, that
llvm is the compiler backend for clang in this case
or you could decide to go with gcc at that point, for fun
but nim can also target js
whenever I use numpy I spend like 90% of my time just looking at the documentation
I search up like every single thing I want it to do
but it's nice because there's a function for everything you want it to do
is the thing you used to visualize usable in windows?
the preview is windows terminal
async centric means if i dont know async yet i shouldnt touch it?
oh lol its your own package
uhh, well there's at least one async function you have to implement, but you don't necessarily need tasks and awaits and everything
https://github.com/salt-die/Advent-of-Code/blob/main/2022/visuals/day_09/rope_bridge/__main__.py this is the source for the visual above, there's not really any async code
may i know all this
import App,Button;Slider,Widget
is it all done by yourself from scratch
or is it something that is from tkinter/pygt etc?
it's all from scratch
cool stuff then
alright so maybe this is a little cursed, but i append every tail into a list and numpy it so i can apply unique on the axis (cant use set) but it doesnt seem to give the intended result
history = []
for m in moves:
d, dis = m.split()
for _ in range(int(dis)):
head = head + directions[d]
if need_move(head, tail):
tail += np.sign(head - tail)
history.append(tail.copy())
np_history = np.array(history)
print(np_history)
print(np.unique(np_history, axis=0))
[[1 0]
[2 0]
[3 0]
[4 1]
[4 2]
[4 3]
[3 4]
[2 4]
[3 3]
[4 3]
[3 2]
[2 2]
[1 2]]
[[1 0]
[1 2]
[2 0]
[2 2]
[2 4]
[3 0]
[3 2]
[3 3]
[3 4]
[4 1]
[4 2]
[4 3]]
you can add the tail to a normal set if you turn it into a tuple first
my_set.add(tuple(tail))
it's almost time for yet another choke by me
for this day 9 I did test against the input but that didn't help much lol
so i'm still just commenting out the test file line
I think I forgot (0,0) so I was one short
Is there anything that i may be missing out from part 1 if the example works just fine?
The tail moves to all good positions but for my puzzle input i cant get good answer
send your code?
ye ok just a sec
"R": [0,1],
"L": [0,-1],
"U": [1,0],
"D": [-1,0]
}
# tells if tail has to go diagonal
def go_diagonal(head_pos, tail_pos):
if head_pos[0] != tail_pos[0] and head_pos[1] != tail_pos[1]:
return True
return False
with open("day 9/data.txt") as file:
lines = file.readlines()
head_pos = [0, 0] # x:y position of head atm
tail_pos = [0, 0] # x:y position of tail atm
visited = [[0, 0]]
last_pos = head_pos[:]
for line in lines:
motion = line.strip().split(" ") # line of code giving instructions
way = motion[0] # right, left, up, down
length = int(motion[1]) # how long move
for moves in range(length): # go step by step based on how big is length of move
last_pos = head_pos[:]
head_pos[0] += directions[way][0]
head_pos[1] += directions[way][1]
if abs(abs(head_pos[0]) - abs(tail_pos[0])) > 1 or abs(abs(head_pos[1]) - abs(tail_pos[1])) > 1:
if go_diagonal(head_pos, tail_pos):
tail_pos = last_pos[:]
else:
tail_pos[0] += directions[way][0]
tail_pos[1] += directions[way][1]
if tail_pos not in visited:
visited.append(tail_pos[:])
print(len(visited))
you're not keeping track of duplicates
i was debugging this on example from site
and tail seemed to move to all correct positions
I think your code might error if the head is on (0,0), tail on (0,-1) and the head moves R
because the tail should move R
but the way you have the condition set up, ||1|-|-1|| would be 0
you should just have abs(head pos - tail pos)
what do you mean by head_pos - tail_pos
i see what you mean what is working bad but dont get what you meant about the condition i should have
oh ok i get it
thanks a lot 😄
where will the starting position be
(n/2, n/2) or (0,n)
in my setup 0, 0
also why did you choose 200 just a guess or
but what if you had to imediately go UP 4 or something then woulldnt you go index out of bound
whoa
but I could have just shifted the start position
it's not in python 
aww man, sed
but it's nothing fancy
how do you shift?
do you dynamically increase the size of the 2d array if it goes out
oh wait nno you dont increase you shift my bad
hmh
what language did you write it in?
porting it roughly tp python
class Points:
def __init__(self, n):
self.n = n
self.visited = [[False]*(2*n+1) for _ in range(2*n+1)]
self.dupes = 0
def insert(self, x, y):
x += self.n
y += self.n
if not self.visited[x][y]:
self.dupes += 1
self.visited[x][y] = True
rust, I'm doing it in AoC to learn it
in hindsight just moving the start point would have been sensible
I sat down with a pen and paper and eventually managed to work out what was going on and get a part 1 solution, but now I'm getting brain-ache trying to work out the logic of extending it to part 2
def move_parser(move, head_pos, tail_pos, tail_table):
move_table = {
'[-1, 2]': [1, -1],
'[0, 2]': [0, -1],
'[1, 2]': [-1, -1],
'[-2, 1]': [1, -1],
'[2, 1]': [-1, -1],
'[-2, 0]': [1, 0],
'[2, 0]': [-1, 0],
'[-2, -1]': [1, 1],
'[2, -1]': [-1, 1],
'[-1, -2]': [1, 1],
'[0, -2]': [0, 1],
'[1, -2]': [-1, 1]
}
direction, extent = move.split()[0], int(move.split()[1])
for x in range(extent):
if direction == 'U':
head_pos[1] += 1
elif direction == 'D':
head_pos[1] -= 1
elif direction == 'R':
head_pos[0] += 1
elif direction == 'L':
head_pos[0] -= 1
relative_tail = [tail_pos[0] - head_pos[0], tail_pos[1] - head_pos[1]]
distance = [abs(tail_pos[0] - head_pos[0]), abs(tail_pos[1] - head_pos[1])]
if distance[0] > 1 or distance[1] > 1:
adj = move_table[str(relative_tail)]
tail_pos = [tail_pos[0] + adj[0], tail_pos[1] + adj[1]]
tail_table.append(tail_pos)
return head_pos, tail_pos, tail_table
with open("input_9.txt", "r+")as input_file:
head_pos = [0, 0]
tail_pos = [0, 0]
tail_table = [tail_pos]
for line in input_file:
head_pos, tail_pos, tail_table = move_parser(line, head_pos, tail_pos, tail_table)
print(len(set([str(x) for x in tail_table])))```
aaah, I don't understand this issue at all!
I wrote a new function, ran it, it did nothing. I thought it might be an issue iterating over the input so I changed how that works. Now both parts work...but the first part gives the wrong answer
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
any help would be much appreciated!
For part 2 can we just make a bunch of nodes and just have each node check the position of the one in front of it like we did for head tail and just update the history based on the last tail? I dont see how the logic would change
import numpy as np
def need_move(h, t):
#chebyshev distance
return np.linalg.norm(h - t, ord=np.inf) > 1
def part_two():
with open ("day9/moves.txt", "r") as f:
moves = f.read().split('\n')
directions = {
'U': np.array([0,1]),
'D': np.array([0,-1]),
'R': np.array([1,0]),
'L': np.array([-1,0])
}
knots = [[0,0] for _ in range(10)]
rope = np.array(knots)
history = set()
for m in moves:
d, dis = m.split()
for _ in range(int(dis)):
rope[0] += directions[d]
for knot in range (len(rope)-1):
if need_move(rope[knot], rope[knot+1]):
rope[knot+1] += np.sign(rope[knot] - rope[knot+1])
history.add(tuple(rope[-1]))
print(len(history))
part_two()
lets go
got it
just had to add like one line from my part 1
any1 can give me some tips on what im doing wrong it seems like it starts to screw up when they start to cover each other but i dont understand why (part 2)
https://paste.pythondiscord.com/herujileqi
Hey @undone apex!
You either uploaded a .txt file or entered a message that was too long. Please use our paste bin instead.
I've sorted out the input thing but the function isn't working and I can't tell why
Is this for part 1?
part 2
sorry can't help 😅
well i start to lose faith if i can complete it tbh
spent way too much time on this
and still cant figure this out ;/
I've got like that with previous days, so far I've done them all but it's been hairy
do you have any tips that may help when it comes to just thinking about this part 2
maybe sth that helped you solved it
what I'm trying to do is take my part 1 logic and apply it to each subsequent knot with a for loop
And looking at line by line outputs it seems to be working at first
but at some point things move too far apart and it breaks
just for bump so maybe some1 sees it
if knot[0] not in visited:
visited.append(knot[-1][:])
I don't know which way round your rope is supposed to be, and neither do these two lines of code
They should both be either [0] or [-1]
ye i fixed it
i noticed it a second ago but it didnt fix much hah
the problem is with how all the parts are following each other
pasted a changed version
Ah, so it works?
nah haha
just pasted a version without this mistake so people dont notice it as a mistake
I did part 1 quite easily but I'm struggling with part 2
I am doing the example to get the logic correct but I'm not sure how A) My tail jumps from one place to another and B) the diagram looks nothing like the example's "solution"lol

I just added some extra entries to my moves table and it works fine
for some reason in the second day the knots can move away from each other so they're seperated by a single diagonal space, like
...1
....
.2..
and in that case the knot needs to move diagonally like
...1
..2.
....
no idea why this is but when I changed my program to work like this it gave the correct answer immediately
Sure, give me a sec, I'll put it on my github
I'm not particularly clever at maths so I just used a table to move the knots
oh i see you hardcoded some points
I'm going to sleep now
the quadrants
um
oh yeah
I tototally didnt have day 8 opened
yes.
(and I did it in a really stupid way please don't point it out)
I just did
if abs(x-px) > 1 or abs(y-py) > 1:
...
2022/Python/day9.py line 46
if ((prev_knot.real - knot.real)**2 + (prev_knot.imag - knot.imag)**2)**0.5 <= ROOT_2:```
i think i did exactly what you did
why did you have to get the individual parts, subtract, then do **2 then **.5
except you used < and I used <=
sqrt(a^2 + b^2)
^
yes that's what abs(complex_number) does
I coded this 3 hours ago and its 6am for me
I'll leave the left upto you
ugh do you guys really want me to update it lol
done
now I could make my code better by using complex as key and not using phase at all, but am I going to? 
god what was I thinking a few hours ago
im off to sleep now
yo
--- -H-
-H- -> ---
--T --T
if this was the scenario where would you move the tail
directly up or diagonal
i'm pretty sure it said diagonal
my answer worked with example and is giving wring result for the actual input i dont even know where to start debugging
I DID IT I SOLVED PART 1 OF DAY 9
how did you do part 2? i have no clue what to do with the multi-knot logic
i tried iterating through each pair of knots (H&1, 1&2, 2&3, etc.) and applying the logic from part 1, but i cant seem to get the right answer...
class board():
def __init__(self, n):
self.size = n-1
self.grid = [[0 for i in range(n)] for i in range(n)]
self.count = 0
def display_board(self):
for row in self.grid:
for cell in row:
if cell: print('#', end='')
else: print('-', end='')
print('')
class node():
def __init__( self, x, y ):
self.x = x
self.y = y
b = board(500)
rope = []
rl = 10 # length of rope
for i in range(rl):
rope.append(node(int(b.size/2), int(b.size/2)))
direction_map = {
'L': (-1, 0),
'R': ( 1, 0),
'U': ( 0, -1),
'D': ( 0, 1),
}
def mark_map(dir, steps):
for _ in range(steps):
rope[0].x += direction_map[dir][0]
rope[0].y += direction_map[dir][1]
dist_x = rope[0].x-rope[1].x
dist_y = rope[0].y-rope[1].y
for i in range(1,rl):
if not((abs(dist_x) in [0,1]) and (abs(dist_y) in [0,1])):
# print("not connected")
if dist_x < 0: rope[i].x += -1
elif dist_x > 0: rope[i].x += 1
if dist_y < 0: rope[i].y += -1
elif dist_y > 0: rope[i].y += 1
if i != rl-1:
dist_x = rope[i].x-rope[i+1].x
dist_y = rope[i].y-rope[i+1].y
if not b.grid[rope[rl-1].y][rope[rl-1].x]:
b.grid[rope[rl-1].y][rope[rl-1].x] = 1
b.count += 1
with open('input.txt') as input_:
for i in input_:
dir, steps = i.split()
mark_map(dir, int(steps))
b.display_board()
print(b.count)
this is my solution
ok thx! lemme read it rq
ohh hooray our solutions are going to be fundamentally different lol, i used imaginary numbers
i created an array of and append n number of nodes
the 0th node -> head
the n-1th node -> tail
i update where the head is supposed to be in the mark_map function
rope[0].x += direction_map[dir][0]
rope[0].y += direction_map[dir][1]
and then update where the rest of the nodes are supposed to be except the last one and update the dist varable
you know whhat ill
share you the code that helped me figure out my solution
that was wasyy too clean and readable
and then update where the rest of the nodes are supposed to be
this is the problem, i dont understand the part 2 knot logic
59 votes and 975 comments so far on Reddit
thanks!
Visualization of https://adventofcode.com/2022/day/9 (⚠ spolier for part 2 ⚠)
00:00 Act |: Follow the Leader
00:34 Act II: Longer Faster Harder Smarter
01:41 Act III: Dial it up to 11385
https://paste.pythondiscord.com/odizepakiq.py day 9 solved with the complex plane :D
