tjf's recent activity
-
Comment on What programming/technical projects have you been working on? in ~comp
-
Comment on 2025 NFL Season 🏈 Weekly Discussion Thread – Week 1 in ~sports.american_football
tjf There's been a ton of RedZone discourse on the formerly-bird-themed social media this week, and apparently everyone in the world except me watches NFL sundays with two or more TVs, RedZone playing...There's been a ton of RedZone discourse on the formerly-bird-themed social media this week, and apparently everyone in the world except me watches NFL sundays with two or more TVs, RedZone playing on one of them. To me that just sounds so overstimulating and unenjoyable. I just have a main game on my single TV, and change channels to a second game during commercials.
-
Comment on Do we have enough NFL fans to warrant weekly discussions? in ~sports.american_football
tjf I'd pop in to comment on a weekly thread. This week 1 has been incredible, and my Vikes haven't even played yet. SNF alone saw over 80 total points, a comeback victory, a vintage Derrick Henry...I'd pop in to comment on a weekly thread. This week 1 has been incredible, and my Vikes haven't even played yet. SNF alone saw over 80 total points, a comeback victory, a vintage Derrick Henry performance, nearly a 1-point safety, and a scorigami!
-
Comment on What do you think about Medium nowadays? in ~tech
tjf I find Medium's UI very user-hostile, so I mostly avoid links to Medium posts unless the author or topic is particularly interesting to me. In that case, I use scribe.rip as a frontend.I find Medium's UI very user-hostile, so I mostly avoid links to Medium posts unless the author or topic is particularly interesting to me. In that case, I use scribe.rip as a frontend.
-
Comment on What games have you been playing, and what's your opinion on them? in ~games
tjf A few years ago I picked up Oblivion GOTY Edition on sale but never got around to playing it (or any of the other Elder Scrolls games). So instead of buying the remaster I took its release as a...A few years ago I picked up Oblivion GOTY Edition on sale but never got around to playing it (or any of the other Elder Scrolls games). So instead of buying the remaster I took its release as a sign to finally play the version collecting dust in my Steam library. I'm over 10 hours in and having lots of fun, taking my time with the main quest. I find the janky graphics and mechanics kind of charming. It's running very smoothly on my Intel IGPU via Proton on Linux, which I imagine would not be the case for the remaster.
-
Comment on I dont want Windows 11, how easy is it to use Linux? in ~tech
tjf Does Mint still support KDE? I'm not seeing it listed on their download page and Wikipedia suggests they dropped it a few years ago. The need to manually replace a desktop environment is not ideal...Does Mint still support KDE? I'm not seeing it listed on their download page and Wikipedia suggests they dropped it a few years ago. The need to manually replace a desktop environment is not ideal for attracting Windows users.
I personally use and love KDE but on a less beginner-friendly distribution. For what it's worth, the KDE project has its own recommendations.
-
Comment on What games have you been playing, and what's your opinion on them? in ~games
tjf Thanks for the heads up! I already had to go back for a few of the A-side crystal hearts to advance through Core, and I kind of like that we’re forced to revisit completed levels. Even though I’m...Thanks for the heads up! I already had to go back for a few of the A-side crystal hearts to advance through Core, and I kind of like that we’re forced to revisit completed levels. Even though I’m still not “good” at this game, I do feel more confident the second time around some rooms.
-
Comment on What games have you been playing, and what's your opinion on them? in ~games
tjf I'm nearly two decades late to the party, but I just played through Portal for the first time after my friend recommended it and I snagged it on a Steam sale. GLaDOS is the best, and I enjoyed the...I'm nearly two decades late to the party, but I just played through Portal for the first time after my friend recommended it and I snagged it on a Steam sale. GLaDOS is the best, and I enjoyed the puzzles so much. Wish it were longer, but I have the sequel queued up to start soon.
Aside from that, I've been slowly working through the B-sides in Celeste. After that I think I'll finally try the Farewell chapter!
-
Comment on Warner Bros negotiating big sale of shelved ‘Coyote Vs. Acme’ movie in ~movies
tjf Shelving it would allow WBD to write it off for tax purposes, something they did with the unreleased Batgirl movie a few years ago.Shelving it would allow WBD to write it off for tax purposes, something they did with the unreleased Batgirl movie a few years ago.
-
Comment on Midweek Movie Free Talk in ~movies
tjf Over the past week I watched: Megalopolis (2024) - Mostly a mess, which I think mostly boils down to the screenplay. It looked decent and the cast is loaded, but every line felt awkward and the...Over the past week I watched:
- Megalopolis (2024) - Mostly a mess, which I think mostly boils down to the screenplay. It looked decent and the cast is loaded, but every line felt awkward and the "New Rome" setting felt a little corny at times. Glad FFC got to make it on his terms, though.
- Queer (2024) - I knew nothing of Burroughs' life or works going in, but this adaptation piqued my interest. Daniel Craig's lead performance was especially moving. He captures the emotion of yearning so powerfully.
- Challengers (2024) - Rewatch. One of my favorites of last year. AMC put it back in theaters (ostensibly for Black History Month) and I was thrilled to finally see it on a big screen. That Reznor & Ross soundtrack has been dominating my music listening for a while now. I'm very disappointed that this didn't get a single Oscar nomination (nor Queer, also directed by Luca Guadagnino).
- The Big Lebowski (1998) - Rewatch. Threw this on with some friends on a Saturday night, and laughed for a solid two hours. Kind of a perfect movie.
Over the coming days I'll probably fill in a few Oscar nomination gaps before the ceremony on Sunday. I almost always disagree with their picks, but tune in year after year anyway.
-
Comment on Day 9: Disk Fragmenter in ~comp.advent_of_code
tjf Part 2 is slow, but I'm sick of thinking about disk defrag so I'm calling it a day. After doing some ahead-of-time indexing, I got it down to about 6s. My Python solutions: Part 1 import itertools...Part 2 is slow, but I'm sick of thinking about disk defrag so I'm calling it a day. After doing some ahead-of-time indexing, I got it down to about 6s. My Python solutions:
Part 1
import itertools import sys flatten = itertools.chain.from_iterable disk = list(flatten([i // 2] * int(char) if not i % 2 else [-1] * int(char) for i, char in enumerate(sys.stdin.read().strip()))) left = 0 right = len(disk) - 1 while left < right: if disk[left] < 0 and disk[right] >= 0: disk[left] = disk[right] disk[right] = -1 left += disk[left] >= 0 right -= disk[right] < 0 print(sum(i * int(char) for i, char in enumerate(disk) if char >= 0))
Part 2
from collections import Counter import itertools import sys def index_disk(disk: list[int]) -> tuple[dict[int, int], dict[int, int]]: free_blocks = {} free_len = 0 file_ids = {} for i in range(len(disk)): if disk[i] < 0: free_len += 1 continue if free_len > 0: free_blocks[i - free_len] = free_len free_len = 0 if disk[i] not in file_ids: file_ids[disk[i]] = i return free_blocks, file_ids def find_free_blocks(free_blocks: dict[int, int], file_idx: int, file_len: int) -> tuple[int, int]: for free_idx, free_len in sorted(free_blocks.items()): if free_idx < file_idx and free_len >= file_len: return free_idx, free_len return -1, 0 flatten = itertools.chain.from_iterable disk = list(flatten([i // 2] * int(char) if not i % 2 else [-1] * int(char) for i, char in enumerate(sys.stdin.read().strip()))) free_blocks, file_ids = index_disk(disk) file_lens = Counter(block for block in disk if block > 0) for file_id, file_idx in sorted(file_ids.items(), reverse=True): file_len = file_lens[file_id] free_idx, free_len = find_free_blocks(free_blocks, file_idx, file_len) if free_idx < 0: continue for i in range(file_len): disk[free_idx + i] = file_id disk[file_idx + i] = -1 del free_blocks[free_idx] if free_len > file_len: free_blocks[free_idx + file_len] = free_len - file_len print(sum(i * int(char) for i, char in enumerate(disk) if char >= 0))
-
Comment on Day 8: Resonant Collinearity in ~comp.advent_of_code
tjf I constructed complex-valued parametric equations for lines through antennae which is probably overthinking things, but it works. Python's itertools.combinations was also useful today. My Python...I constructed complex-valued parametric equations for lines through antennae which is probably overthinking things, but it works. Python's
itertools.combinations
was also useful today. My Python solutions:Part 1
from collections import defaultdict import itertools import sys def antinodes_in_bounds(w: int, h: int, z1: complex, z2: complex) -> list[complex]: a1 = 2 * z1 - z2 a2 = 2 * z2 - z1 return [a for a in (a1, a2) if 0 <= a.real < w and 0 >= a.imag > -h] w, h = 0, 0 freqs = defaultdict[str, list[complex]](list) for b, line in enumerate(sys.stdin): w = len(line.strip()) h += 1 for a, char in enumerate(line.strip()): if char != '.': freqs[char].append(complex(a, -b)) antinodes = set() for antennae in freqs.values(): for z1, z2 in itertools.combinations(antennae, 2): antinodes.update(antinodes_in_bounds(w, h, z1, z2)) print(len(antinodes))
Part 2
from collections import defaultdict import itertools import sys def antinodes_in_bounds(w: int, h: int, z1: complex, z2: complex) -> list[complex]: # place bounds on t for the parametrized line f(t) = (z2 - z1) * t + z1 top = -z1.imag / (z2 - z1).imag bottom = (-(h - 1) - z1.imag) / (z2 - z1).imag left = -z1.real / (z2 - z1).real right = ((w - 1) - z1.real) / (z2 - z1).real t1, t2 = map(int, sorted((top, bottom, left, right))[1:3]) # compute f(t) at integer values of t in [t1, t2] return [(z2 - z1) * t + z1 for t in range(t1, t2 + 1)] w, h = 0, 0 freqs = defaultdict[str, list[complex]](list) for b, line in enumerate(sys.stdin): w = len(line.strip()) h += 1 for a, char in enumerate(line.strip()): if char != '.': freqs[char].append(complex(a, -b)) antinodes = set() for antennae in freqs.values(): for z1, z2 in itertools.combinations(antennae, 2): antinodes.update(antinodes_in_bounds(w, h, z1, z2)) print(len(antinodes))
-
Comment on Day 6: Guard Gallivant in ~comp.advent_of_code
tjf Having the same frustration as basically everyone else here. Part 2 works, but it's slow (roughly 20 seconds for both CPython and PyPy). I would like to think about the problem more and come back...Having the same frustration as basically everyone else here. Part 2 works, but it's slow (roughly 20 seconds for both CPython and PyPy). I would like to think about the problem more and come back to it in a few hours, but future me might be too tired. I did get to use complex numbers again, though, which makes me happy. My Python solutions:
Part 1
import sys def move_guard(lab: dict[complex, str], pos: complex, heading: complex) -> tuple[complex, complex]: if lab[pos + heading] == '#': heading *= -1j else: pos += heading lab[pos] = 'X' return pos, heading lab = {} for b, line in enumerate(sys.stdin): for a, char in enumerate(line.strip()): if char == '^': startpos = complex(a, -b) char = 'X' lab[complex(a, -b)] = char pos = startpos heading = 1j while pos + heading in lab: pos, heading = move_guard(lab, pos, heading) print(sum(char == 'X' for char in lab.values()))
Part 2
from collections.abc import Iterator import sys def move_guard(lab: dict[complex, str], pos: complex, heading: complex) -> tuple[complex, complex]: if lab[pos + heading] == '#': heading *= -1j else: pos += heading lab[pos] = 'X' return pos, heading def obstruction_candidates(_lab: dict[complex, str], pos: complex, heading: complex) -> Iterator[complex]: lab = _lab.copy() while pos + heading in lab: oldchar = lab[pos + heading] pos, heading = move_guard(lab, pos, heading) if oldchar == '.': yield pos def would_loop(_lab: dict[complex, str], pos: complex, heading: complex, obstruction: complex) -> bool: lab = _lab.copy() lab[obstruction] = '#' history = set() while pos + heading in lab: if (pos, heading) in history: return True history.add((pos, heading)) pos, heading = move_guard(lab, pos, heading) return False lab = {} for b, line in enumerate(sys.stdin): for a, char in enumerate(line.strip()): if char == '^': startpos = complex(a, -b) char = 'X' lab[complex(a, -b)] = char print(sum(would_loop(lab, startpos, 1j, candidate) for candidate in obstruction_candidates(lab, startpos, 1j)))
-
Comment on Day 5: Print Queue in ~comp.advent_of_code
tjf A neat Python type you might like is collections.defaultdict. That will let you access and manipulate a dictionary without checking whether a key is already present. If it's not, it gets created...A neat Python type you might like is
collections.defaultdict
. That will let you access and manipulate a dictionary without checking whether a key is already present. If it's not, it gets created with some user-chosen default value (such as an empty set). E.g.ruleset = defaultdict(set)
will let you doruleset[after].add(before)
without having to checkif after not in ruleset
. -
Comment on Fitness Weekly Discussion in ~health
tjf I've been running 3-5 days per week since the spring, but now that it's getting cold here (Northeastern US) I'm finding it takes much more willpower to keep my streak. There's always the...I've been running 3-5 days per week since the spring, but now that it's getting cold here (Northeastern US) I'm finding it takes much more willpower to keep my streak. There's always the treadmill, but I much prefer the fresh air and scenery once I've started out. The earlier sunsets are also a bummer, but I'll either shift my runs to the morning or invest in a good headlamp and reflective clothing.
-
Comment on Day 5: Print Queue in ~comp.advent_of_code
tjf (edited )LinkOnce again, input sizes are small enough that I don't have to be clever. My Python solutions: Part 1 from collections import defaultdict import itertools import sys def is_correctly_ordered(rules:...Once again, input sizes are small enough that I don't have to be clever. My Python solutions:
Part 1
from collections import defaultdict import itertools import sys def is_correctly_ordered(rules: defaultdict[int, list[int]], update: list[int]) -> bool: for x, y in itertools.pairwise(update): if x in rules[y]: return False return True rules_input, updates_input = sys.stdin.read().split('\n\n') rules = defaultdict[int, list[int]](list) for line in rules_input.split('\n'): x, y = map(int, line.split('|')) rules[x].append(y) total = 0 for line in updates_input.strip().split('\n'): update = [*map(int, line.split(','))] if is_correctly_ordered(rules, update): total += update[len(update) // 2] print(total)
Part 2
from collections import defaultdict import functools import itertools import sys def is_correctly_ordered(rules: defaultdict[int, list[int]], update: list[int]) -> bool: for x, y in itertools.pairwise(update): if x in rules[y]: return False return True # XXX: relies on rules being global (cmp_to_key functions take exactly 2 args) def cmp(x: int, y: int) -> int: if x in rules[y]: return 1 elif y in rules[x]: return -1 else: return 0 rules_input, updates_input = sys.stdin.read().split('\n\n') rules = defaultdict[int, list[int]](list) for line in rules_input.split('\n'): x, y = map(int, line.split('|')) rules[x].append(y) total = 0 for line in updates_input.strip().split('\n'): update = [*map(int, line.split(','))] if not is_correctly_ordered(rules, update): fixed = sorted(update, key=functools.cmp_to_key(cmp)) total += fixed[len(fixed) // 2] print(total)
Edit: I went back and used a closure returning a lambda to clean up that awkward
cmp
function. Before it relied onrules
being global, sincefunctools.cmp_to_key
functions take exactly 2 arguments.def cmp_with_rules(rules: defaultdict[int, list[int]]) -> Callable[[int, int], int]: return lambda x, y: 1 if x in rules[y] else (-1 if y in rules[x] else 0)
-
Comment on Day 4: Ceres Search in ~comp.advent_of_code
tjf Brute force, but it worked well enough. I found complex numbers useful here for storing and shifting coordinates. My Python solutions: Part 1 import sys DIRECTIONS = ( (0, 1, 2, 3), # up (0, -1j,...Brute force, but it worked well enough. I found complex numbers useful here for storing and shifting coordinates. My Python solutions:
Part 1
import sys DIRECTIONS = ( (0, 1, 2, 3), # up (0, -1j, -2j, -3j), # down (0, -1, -2, -3), # left (0, 1j, 2j, 3j), # right (0, -1 - 1j, -2 - 2j, -3 - 3j), # diagonal down and left (0, -1 + 1j, -2 + 2j, -3 + 3j), # diagonal up and left (0, 1 + 1j, 2 + 2j, 3 + 3j), # diagonal up and right (0, 1 - 1j, 2 - 2j, 3 - 3j), # diagonal down and right ) grid = {} for a, line in enumerate(sys.stdin): for b, char in enumerate(line.strip()): grid[complex(a, b)] = char total = 0 for coord in grid: for direction in DIRECTIONS: if ''.join(grid.get(coord + d, '') for d in direction) == 'XMAS': total += 1 print(total)
Part 2
import sys DIRECTIONS = ( (-1 + 1j, 0, 1 - 1j), # diagonal upper left to lower right (-1 - 1j, 0, 1 + 1j), # diagonal lower left to upper right ) grid = {} for a, line in enumerate(sys.stdin): for b, char in enumerate(line.strip()): grid[complex(a, b)] = char total = 0 for coord in grid: diag1 = ''.join(grid.get(coord + d, '') for d in DIRECTIONS[0]) diag2 = ''.join(grid.get(coord + d, '') for d in DIRECTIONS[1]) if diag1 in ('MAS', 'SAM') and diag2 in ('MAS', 'SAM'): total += 1 print(total)
-
Comment on Day 3: Mull It Over in ~comp.advent_of_code
tjf I feared regex would only get me so far, but it worked for both parts quite well! My Python solutions: Part 1 import re import sys operands = re.findall(r'mul\((\d{1,3}),(\d{1,3})\)',...I feared regex would only get me so far, but it worked for both parts quite well! My Python solutions:
Part 1
import re import sys operands = re.findall(r'mul\((\d{1,3}),(\d{1,3})\)', sys.stdin.read()) print(sum(int(x) * int(y) for x, y in operands))
Part 2
import re import sys memory = re.sub(r"don't\(\).*?(do\(\)|$)", '', sys.stdin.read(), flags=re.DOTALL) operands = re.findall(r'mul\((\d{1,3}),(\d{1,3})\)', memory) print(sum(int(x) * int(y) for x, y in operands))
-
Comment on Day 2: Red-Nosed Reports in ~comp.advent_of_code
tjf Here are my Python solutions. I'm not doing them right at midnight this year, just when I have the time. Part 1 import sys def is_safe(report: list[int]) -> bool: diffs = [a - b for a, b in...Here are my Python solutions. I'm not doing them right at midnight this year, just when I have the time.
Part 1
import sys def is_safe(report: list[int]) -> bool: diffs = [a - b for a, b in zip(report, report[1:])] return is_strictly_monotone(diffs) and is_gradual(diffs) def is_strictly_monotone(diffs: list[int]) -> bool: return abs(sum(abs(d) // d if d else 0 for d in diffs)) == len(diffs) def is_gradual(diffs: list[int]) -> bool: return all(1 <= abs(d) <= 3 for d in diffs) safe = sum(is_safe([*map(int, line.split())]) for line in sys.stdin) print(safe)
Part 2
import sys def is_safe(report: list[int], tolerate: bool = True) -> bool: diffs = [a - b for a, b in zip(report, report[1:])] if is_strictly_monotone(diffs) and is_gradual(diffs): return True if tolerate: for i in range(len(report)): subreport = report[:i] + report[i + 1:] if is_safe(subreport, tolerate=False): return True return False def is_strictly_monotone(diffs: list[int]) -> bool: return abs(sum(abs(d) // d if d else 0 for d in diffs)) == len(diffs) def is_gradual(diffs: list[int]) -> bool: return all(1 <= abs(d) <= 3 for d in diffs) safe = sum(is_safe([*map(int, line.split())]) for line in sys.stdin) print(safe)
-
Comment on What are your ten favourite movies of all time? in ~movies
tjf 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...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.
- The Tree of Life (Malick, 2011)
- Synecdoche, New York (Kaufman, 2008)
- Chungking Express (Wong, 1994)
- Rushmore (W. Anderson, 1998)
- The Umbrellas of Cherbourg (Demy, 1964)
- Eternal Sunshine of the Spotless Mind (Gondry, 2004)
- Mirror (Tarkovsky, 1975)
- First Reformed (Schrader, 2017)
- Modern Times (Chaplin, 1936)
- Barry Lyndon (Kubrick, 1975)
I wrote an Atom feed generator for my alma mater's student newspaper, since they recently changed CMS and no longer provide their own RSS/Atom feed. Originally wrote it in Python with BeautifulSoup, but figured I'd give it a shot in Go to improve in that language a bit. I'd welcome any feedback. Here's the code.