tjf's recent activity

  1. Comment on What programming/technical projects have you been working on? in ~comp

    tjf
    Link
    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...

    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.

    2 votes
  2. Comment on 2025 NFL Season 🏈 Weekly Discussion Thread – Week 1 in ~sports.american_football

    tjf
    Link Parent
    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.

    1 vote
  3. Comment on Do we have enough NFL fans to warrant weekly discussions? in ~sports.american_football

    tjf
    Link
    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!

    6 votes
  4. Comment on What do you think about Medium nowadays? in ~tech

    tjf
    Link
    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.

    27 votes
  5. Comment on What games have you been playing, and what's your opinion on them? in ~games

    tjf
    Link Parent
    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.

  6. Comment on I dont want Windows 11, how easy is it to use Linux? in ~tech

    tjf
    Link Parent
    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.

    3 votes
  7. Comment on What games have you been playing, and what's your opinion on them? in ~games

    tjf
    Link Parent
    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.

    1 vote
  8. Comment on What games have you been playing, and what's your opinion on them? in ~games

    tjf
    Link
    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!

    17 votes
  9. Comment on Warner Bros negotiating big sale of shelved ‘Coyote Vs. Acme’ movie in ~movies

    tjf
    Link Parent
    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.

    5 votes
  10. Comment on Midweek Movie Free Talk in ~movies

    tjf
    Link
    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.

    4 votes
  11. Comment on Day 9: Disk Fragmenter in ~comp.advent_of_code

    tjf
    Link
    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))
    
    1 vote
  12. Comment on Day 8: Resonant Collinearity in ~comp.advent_of_code

    tjf
    Link
    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))
    
    1 vote
  13. Comment on Day 6: Guard Gallivant in ~comp.advent_of_code

    tjf
    Link
    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)))
    
    1 vote
  14. Comment on Day 5: Print Queue in ~comp.advent_of_code

    tjf
    Link Parent
    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 do ruleset[after].add(before) without having to check if after not in ruleset.

    3 votes
  15. Comment on Fitness Weekly Discussion in ~health

    tjf
    Link
    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.

    4 votes
  16. Comment on Day 5: Print Queue in ~comp.advent_of_code

    tjf
    (edited )
    Link
    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:...

    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 on rules being global, since functools.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)
    
    1 vote
  17. Comment on Day 4: Ceres Search in ~comp.advent_of_code

    tjf
    Link
    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)
    
    3 votes
  18. Comment on Day 3: Mull It Over in ~comp.advent_of_code

    tjf
    Link
    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))
    
    3 votes
  19. Comment on Day 2: Red-Nosed Reports in ~comp.advent_of_code

    tjf
    Link
    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)
    
    2 votes
  20. Comment on What are your ten favourite movies of all time? in ~movies

    tjf
    Link
    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.

    1. The Tree of Life (Malick, 2011)
    2. Synecdoche, New York (Kaufman, 2008)
    3. Chungking Express (Wong, 1994)
    4. Rushmore (W. Anderson, 1998)
    5. The Umbrellas of Cherbourg (Demy, 1964)
    6. Eternal Sunshine of the Spotless Mind (Gondry, 2004)
    7. Mirror (Tarkovsky, 1975)
    8. First Reformed (Schrader, 2017)
    9. Modern Times (Chaplin, 1936)
    10. Barry Lyndon (Kubrick, 1975)
    6 votes