tjf's recent activity
-
Comment on What are your ten favourite movies of all time? in ~movies
-
Comment on Zig: The small language (2022) in ~comp
tjf My only significant use of Zig so far is as a nicer way to interface with C libraries, specifically the forthcoming SDL3. And for that it's kind of perfect. The build system is so much better than...My only significant use of Zig so far is as a nicer way to interface with C libraries, specifically the forthcoming SDL3. And for that it's kind of perfect. The build system is so much better than writing janky makefiles and I appreciate the compiler's sensible defaults. I also like that the language itself is documented on a single big web page. Zig feels a lot more approachable to me than some of the other C replacement languages out there.
-
Comment on Announcing the Ladybird Browser Initiative in ~tech
tjf Totally agree re: the new website design. The old site (archive) was way more charming and less sterile. Though I do like the switch from .dev to .org.Totally agree re: the new website design. The old site (archive) was way more charming and less sterile. Though I do like the switch from .dev to .org.
-
Comment on What email client do you use? in ~tech
tjf On my Linux desktops I use aerc, a terminal-based client. I use its maildir backend, so everything feels snappy. Syncing between my hard drives and the IMAP server is handled by a cronjob running...On my Linux desktops I use aerc, a terminal-based client. I use its maildir backend, so everything feels snappy. Syncing between my hard drives and the IMAP server is handled by a cronjob running mbsync. Prior to aerc I used mutt (specifically neomutt).
On my iPhone I use plain old mail.app. It’s fine, and I do most of email work on desktop anyway. The major third-party mail apps on iOS all seem to have something fundamentally unacceptable to me, such as mail provider lock-in, non-native UI, annoying AI integrations, and other privacy nightmares like fetching your mail onto their own servers. So stock mail.app it is for me.
-
Comment on Best foreign films and TV shows? in ~tv
tjf I love Drive My Car, so I'll chime in to recommend Wheel of Fortune and Fantasy (TMDB, IMDb, Letterboxd) which is also directed by Ryusuke Hamaguchi (and released in the same year!). It's an...I love Drive My Car, so I'll chime in to recommend Wheel of Fortune and Fantasy (TMDB, IMDb, Letterboxd) which is also directed by Ryusuke Hamaguchi (and released in the same year!). It's an anthology film in three parts, a structure I wish more filmmakers played with.
-
Comment on When do you listen to podcasts? in ~life
tjf As others have mentioned, I like to listen to podcasts while doing household chores (washing dishes, folding laundry, cooking dinner). But I'm careful to give my ears a break sometimes, e.g. when...As others have mentioned, I like to listen to podcasts while doing household chores (washing dishes, folding laundry, cooking dinner). But I'm careful to give my ears a break sometimes, e.g. when I go for a walk or take a shower or fall asleep. I find I have my best thoughts and ideas in these settings, and my mind can't wander and pay attention to a podcast at the same time.
-
Comment on LeBron James' path to 40,000 career regular-season points in ~sports.basketball
tjf Last night LeBron James hit the 40k mark. This is a cool timeline charting his career leading up to that moment. Ironically the Lakers ended up losing the game, which also happened last season...Last night LeBron James hit the 40k mark. This is a cool timeline charting his career leading up to that moment. Ironically the Lakers ended up losing the game, which also happened last season when LeBron broke Kareem's record to become the all-time leading scorer. It's hard to see anyone catching this record.
-
LeBron James' path to 40,000 career regular-season points
7 votes -
Comment on 50 Algorithms Every Programmer Should Know (Second Edition) in ~comp
tjf I wouldn't be surprised if this book was itself written by an LLM, seeing how clickbaity that sounds.I wouldn't be surprised if this book was itself written by an LLM, seeing how clickbaity that sounds.
-
Comment on Day 10: Pipe Maze in ~comp.advent_of_code
tjf I can confirm my input had this property too, and I am thankful for it.I can confirm my input had this property too, and I am thankful for it.
-
Comment on Day 10: Pipe Maze in ~comp.advent_of_code
tjf Nice ramp up in difficulty from part one to two, which I always appreciate. Seems like there were a couple different ways to solve this, and I'm not convinced mine is a great one. But it works!...Nice ramp up in difficulty from part one to two, which I always appreciate.
Seems like there were a couple different ways to solve this, and I'm not
convinced mine is a great one. But it works! And it's fast enough to not care
too much.Here are my Python solutions, albeit messy ones:
Part 1
Not too bad for a part one. Walk the graph from S all the way back to itself,
keeping track of steps travelled. Then it works out that the farthest piece
in the loop from S is half that total loop length.#!/usr/bin/env python3 from collections import defaultdict DIRECTIONS = (-1 + 0j, 1 + 0j, 0 - 1j, 0 + 1j) CONNECTIONS = {'|': (-1 + 0j, 1 + 0j), '-': (0 - 1j, 0 + 1j), 'L': (-1 + 0j, 0 + 1j), 'J': (-1 + 0j, 0 - 1j), '7': (0 - 1j, 1 + 0j), 'F': (0 + 1j, 1 + 0j), '.': (), ' ': ()} # now that we know the coordinates of 'S', replace it with the pipe that fits def normalize_start(g, start): v = tuple(d for d in DIRECTIONS if any(start + d + conn == start for conn in CONNECTIONS[g[start + d]])) pipe = next(k for k in CONNECTIONS if set(CONNECTIONS[k]) == set(v)) g[start] = pipe # walk around the main loop, return number of steps to make it all the way around def walk_loop(g, start): steps = 2 visited = set([start]) start_conns = tuple(start + conn for conn in CONNECTIONS[g[start]]) here = start_conns[0] while here != start_conns[1]: visited.add(here) here = next(filter(lambda pos: pos not in visited, (here + conn for conn in CONNECTIONS[g[here]]))) steps += 1 return steps G = defaultdict(lambda: '.') for r, line in enumerate(open(0)): for c, char in enumerate(line.strip()): G[complex(r, c)] = char start = next(pos for pos, char in G.items() if char == 'S') normalize_start(G, start) steps_around = walk_loop(G, start) print(steps_around // 2)
Part 2
The trickiest part in part two was the notion that tiles could squeeze between
pipes to be considered non-enclosed. My idea to attack this is to "zoom in" on
the graph, effectively putting empty spaces between every tile in the original
graph. Because I used complex coordinates, this was as easy as multiplying
coordinates by 2 and then filling in vertical and horizontal pipe pieces that
belonged in the newly created empty spaces. I also turned all non-mainloop pipes
into ground pipes (dots) to make things easier. Then I used flood fill on the
new zoomed graph on the exterior of the loop found the difference between all
ground tiles and these exterior ground tiles to end up with the final answer.#!/usr/bin/env python3 from collections import defaultdict, deque DIRECTIONS = (-1 + 0j, 1 + 0j, 0 - 1j, 0 + 1j) CONNECTIONS = {'|': (-1 + 0j, 1 + 0j), '-': (0 - 1j, 0 + 1j), 'L': (-1 + 0j, 0 + 1j), 'J': (-1 + 0j, 0 - 1j), '7': (0 - 1j, 1 + 0j), 'F': (0 + 1j, 1 + 0j), '.': (), ' ': ()} # now that we know the coordinates of 'S', replace it with the pipe that fits def normalize_start(g, start): v = tuple(d for d in DIRECTIONS if any(start + d + conn == start for conn in CONNECTIONS[g[start + d]])) pipe = next(k for k in CONNECTIONS if set(CONNECTIONS[k]) == set(v)) g[start] = pipe # walk around the main loop, return a set containing the coords of all its pieces def walk_loop(g, start): visited = set([start]) start_conns = tuple(start + conn for conn in CONNECTIONS[g[start]]) here = start_conns[0] while here != start_conns[1]: visited.add(here) here = next(filter(lambda pos: pos not in visited, (here + conn for conn in CONNECTIONS[g[here]]))) visited.add(here) return visited # returns a "zoomed in" graph with empty space between the original's tiles def zoom(g, loop, factor=2): zg = defaultdict(lambda: ' ') for pos, char in g.items(): zg[pos * factor] = char zloop = {pos * factor for pos in loop} for pos, char in zg.items(): if char in ('|-LJ7F') and pos not in zloop: zg[pos] = '.' # set all extra pipes to ground tiles max_r = int(max(zg, key=lambda pos: pos.real).real) max_c = int(max(zg, key=lambda pos: pos.imag).imag) for r in range(0, max_r + 1): for c in range(0, max_c + 1): pos = complex(r, c) if zg[pos] == ' ': up, down, left, right = (pos + d for d in DIRECTIONS) if zg[up] in ('|7F') or zg[down] in ('|JL'): zg[pos] = '|' # fill in vertical pipe gaps elif zg[left] in ('-LF') or zg[right] in ('-J7'): zg[pos] = '-' # fill in horizontal pipe gaps return zg # starting from outside of the main loop, flood tiles around the loop # return a set containing the coords of all outside tiles def flood(g): min_pos = complex(min(g, key=lambda pos: pos.real).real - 1, min(g, key=lambda pos: pos.imag).imag - 1) max_pos = complex(max(g, key=lambda pos: pos.real).real + 1, max(g, key=lambda pos: pos.imag).imag + 1) start = min_pos outside = set() todo = deque([start]) while todo: here = todo.popleft() if here in outside: continue outside.add(here) for d in DIRECTIONS: pos = here + d if ((min_pos.real <= pos.real <= max_pos.real) and (min_pos.imag <= pos.imag <= max_pos.imag) and g[pos] in ('.', ' ')): todo.append(pos) return outside G = defaultdict(lambda: '.') for r, line in enumerate(open(0)): for c, char in enumerate(line.strip()): G[complex(r, c)] = char start = next(pos for pos, char in G.items() if char == 'S') normalize_start(G, start) loop = walk_loop(G, start) ZG = zoom(G, loop) outside = flood(ZG) num_enclosed = sum(pos not in outside and char == '.' for pos, char in ZG.items()) print(num_enclosed)
All in all I liked today's problem, as I often do with graph-type problems.
Also this is my first day this year that I chose to use complex numbers for
coordinates, which I think worked out well. -
Comment on Day 9: Mirage Maintenance in ~comp.advent_of_code
tjf Simple day 9, so I decided to do a little recursion (with memoization) since most solutions here are iterative. My Python: Part 1 #!/usr/bin/env pypy3 def kth_difference(a, k, n, c={}): if (k, n)...Simple day 9, so I decided to do a little recursion (with memoization) since most solutions here are iterative. My Python:
Part 1
#!/usr/bin/env pypy3 def kth_difference(a, k, n, c={}): if (k, n) not in c: if k == 1: c[(k, n)] = a[n] - a[n - 1] else: c[(k, n)] = kth_difference(a, k - 1, n, c) - kth_difference(a, k - 1, n - 1, c) return c[(k, n)] def predict(a): k = 1 c = {} prediction = a[-1] while not all(kth_difference(a, k, i, c) == 0 for i in range(k, len(a))): prediction += kth_difference(a, k, len(a) - 1, c) k += 1 return prediction print(sum(predict(tuple(map(int, line.split()))) for line in open(0)))
Part 2
Same as part one but reverse the sequence first.
#!/usr/bin/env pypy3 def kth_difference(a, k, n, c={}): if (k, n) not in c: if k == 1: c[(k, n)] = a[n] - a[n - 1] else: c[(k, n)] = kth_difference(a, k - 1, n, c) - kth_difference(a, k - 1, n - 1, c) return c[(k, n)] def predict(a): k = 1 c = {} prediction = a[-1] while not all(kth_difference(a, k, i, c) == 0 for i in range(k, len(a))): prediction += kth_difference(a, k, len(a) - 1, c) k += 1 return prediction print(sum(predict(tuple(map(int, line.split()))[::-1]) for line in open(0)))
-
Comment on Day 5: If You Give A Seed A Fertilizer in ~comp.advent_of_code
tjf Posting this a few days late because I waited to sit down and think through a non-bruteforce part two strategy. Not super clean but it does the thing (and doesn't take 45 minutes with 4 cores!)....Posting this a few days late because I waited to sit down and think through a non-bruteforce part two strategy. Not super clean but it does the thing (and doesn't take 45 minutes with 4 cores!). Here are my Python solutions:
Part 1
#!/usr/bin/env pypy3 import math import sys seeds = map(int, sys.stdin.readline().split(':')[1].split()) mappings = [[((src, src + length), dst - src) for dst, src, length in (map(int, m.split()) for m in mapgroup.split('\n') if m and m[0].isdigit())] for mapgroup in sys.stdin.read().split('\n\n')] lowest = math.inf for s in seeds: for mapgroup in mappings: for m, shift in mapgroup: if m[0] <= s < m[1]: s += shift break lowest = min(lowest, s) print(lowest)
Part 2
#!/usr/bin/env pypy3 import sys def compare_intervals(a, b): left, right = (a[0], a[0]), (a[1], a[1]) if (a[0] <= a[1] < b[0]) or (b[1] < a[0] <= a[1]): overlap = (0, 0) return left, overlap, right if a[0] < b[0]: left = (a[0], b[0]) if a[1] > b[1]: right = (b[1], a[1]) overlap = (left[1], right[0]) return left, overlap, right def is_nonempty_interval(a): return a[0] != a[1] def shift_interval(a, shift): return (a[0] + shift, a[1] + shift) def update_interval(r, mapgroup): unmapped = set([r]) mapped = set() while unmapped: rr = unmapped.pop() maps_to_self = True for m, shift in mapgroup: left, overlap, right = compare_intervals(rr, m) if is_nonempty_interval(overlap): maps_to_self = False mapped.add(shift_interval(overlap, shift)) unmapped.add(left) unmapped.add(right) if maps_to_self: mapped.add(rr) return filter(is_nonempty_interval, mapped) def flatten(x): return [z for y in x for z in y] starts_and_lengths = map(int, sys.stdin.readline().split(':')[1].split()) seed_ranges = [(start, start + next(starts_and_lengths)) for start in starts_and_lengths] mappings = [[((src, src + length), dst - src) for dst, src, length in (map(int, m.split()) for m in mapgroup.split('\n') if m and m[0].isdigit())] for mapgroup in sys.stdin.read().split('\n\n')] ranges = seed_ranges for mapgroup in mappings: ranges = flatten(update_interval(r, mapgroup) for r in ranges) print(min(ranges)[0])
-
Comment on Day 7: Camel Cards in ~comp.advent_of_code
tjf (edited )Link ParentVery OOP I love it! I chose to write a cmp-style comparison function instead of operator overloading. I see we both thought to use collections.Counter.Very OOP I love it! I chose to write a cmp-style comparison function instead of operator overloading. I see we both thought to use
collections.Counter
. -
Comment on Day 7: Camel Cards in ~comp.advent_of_code
tjf Posting this a day late, but here are my Python solutions. Got to use some stdlib goodies like functools.cmp_to_key and collections.Counter! Part 1 #!/usr/bin/env pypy3 from collections import...Posting this a day late, but here are my Python solutions. Got to use some stdlib goodies like
functools.cmp_to_key
andcollections.Counter
!Part 1
#!/usr/bin/env pypy3 from collections import Counter from functools import cmp_to_key CARDS = '23456789TJQKA' HAND_TYPES = ([1, 1, 1, 1, 1], [1, 1, 1, 2], [1, 2, 2], [1, 1, 3], [2, 3], [1, 4], [5]) def hand_type(h): c = Counter(h) return HAND_TYPES.index(sorted(c.values())) def cmp_hands(h1, h2): if hand_type(h1) < hand_type(h2): return -1 if hand_type(h1) > hand_type(h2): return 1 for c1, c2 in zip(h1, h2): v1 = CARDS.index(c1) v2 = CARDS.index(c2) if v1 < v2: return -1 if v1 > v2: return 1 return 0 def cmp_handbids(hb1, hb2): return cmp_hands(hb1[0], hb2[0]) ranked = sorted(map(str.split, open(0)), key=cmp_to_key(cmp_handbids)) winnings = sum(rank * int(bid) for rank, (_, bid) in enumerate(ranked, 1)) print(winnings)
Part 2
#!/usr/bin/env pypy3 from collections import Counter from functools import cmp_to_key CARDS = 'J23456789TQKA' HAND_TYPES = ([1, 1, 1, 1, 1], [1, 1, 1, 2], [1, 2, 2], [1, 1, 3], [2, 3], [1, 4], [5]) def hand_type(h): c = Counter(h) jcount = c['J'] del c['J'] c[max(c, key=c.get) if c else 'J'] += jcount return HAND_TYPES.index(sorted(c.values())) def cmp_hands(h1, h2): if hand_type(h1) < hand_type(h2): return -1 if hand_type(h1) > hand_type(h2): return 1 for c1, c2 in zip(h1, h2): v1 = CARDS.index(c1) v2 = CARDS.index(c2) if v1 < v2: return -1 if v1 > v2: return 1 return 0 def cmp_handbids(hb1, hb2): return cmp_hands(hb1[0], hb2[0]) ranked = sorted(map(str.split, open(0)), key=cmp_to_key(cmp_handbids)) winnings = sum(rank * int(bid) for rank, (_, bid) in enumerate(ranked, 1)) print(winnings)
-
Comment on Day 8: Haunted Wasteland in ~comp.advent_of_code
tjf Didn't realize that was missing from the stdlib for so long, but thankfully the version of PyPy I'm using has it. Edit: Are the 2000 and 2000000 multipliers you use just arbitrary large numbers?...Didn't realize that was missing from the stdlib for so long, but thankfully the version of PyPy I'm using has it.
Edit: Are the
2000
and2000000
multipliers you use just arbitrary large numbers? If you look at my solutions there's a neat Python-specific trick for this. -
Comment on Day 8: Haunted Wasteland in ~comp.advent_of_code
tjf Today's problem reminded me of 2022's Day 17, which was a good one! Here are my Python solutions: Part 1 Python's itertools.cycle is really useful here. #!/usr/bin/env pypy3 import itertools...Today's problem reminded me of 2022's Day 17, which was a good one! Here are my Python solutions:
Part 1
Python's
itertools.cycle
is really useful here.#!/usr/bin/env pypy3 import itertools import sys seq = tuple(int(c == 'R') for c in sys.stdin.readline().strip()) sys.stdin.readline() moves = {} for line in sys.stdin: k, v = map(str.strip, line.split('=')) moves[k] = tuple(side.strip('()') for side in v.split(', ')) position = 'AAA' steps = 0 for move in itertools.cycle(seq): if position == 'ZZZ': break position = moves[position][move] steps += 1 print(steps)
Let my CPU cook for a few minutes before turning on my brain and thinking. I got to break out my main for loop into a generator function which is always nice.
Part 2
#!/usr/bin/env pypy3 import itertools import math import sys def nsteps(seq, moves, positions): for p in positions: steps = 0 for move in itertools.cycle(seq): if p[-1] == 'Z': break p = moves[p][move] steps += 1 yield steps seq = [int(c == 'R') for c in sys.stdin.readline().strip()] sys.stdin.readline() moves = {} for line in sys.stdin: k, v = map(str.strip, line.split('=')) moves[k] = tuple(side.strip('()') for side in v.split(', ')) positions = (p for p in moves if p[-1] == 'A') steps = nsteps(seq, moves, positions) print(math.lcm(*steps))
-
Comment on Day 6: Wait For It in ~comp.advent_of_code
tjf Suspiciously easy day, but I'm not complaining. Also I do like days where the input is short enough to enter manually into my source code while first solving. Anyway here are my Python solutions:...Suspiciously easy day, but I'm not complaining. Also I do like days where the input is short enough to enter manually into my source code while first solving. Anyway here are my Python solutions:
Part 1
#!/usr/bin/env pypy3 product = 1 for t, d in zip(*(map(int, line.split(':')[1].split()) for line in open(0))): product *= sum(x * t - x * x > d for x in range(1, t)) print(product)
Part 2
No change in computation for part two, only parsing the input:#!/usr/bin/env pypy3 t, d = map(int, (line.split(':')[1].replace(' ', '') for line in open(0))) print(sum(x * t - x * x > d for x in range(1, t)))
Are we in for something nasty tomorrow?
-
Comment on Day 4: Scratchcards in ~comp.advent_of_code
tjf Nice! The most annoying bugs are always in the parsing...Nice! The most annoying bugs are always in the parsing...
-
Comment on Day 3: Gear Ratios in ~comp.advent_of_code
I wasn't going to comment since it's such a huge thread, but I noticed a lack of my favorite filmmakers (Terrence Malick, Wong Kar-wai, Charlie Kaufman). So here is my list, limited to one movie per director. If you asked me again in a month it will have changed a bit, since I will have changed a bit myself.