Skip Navigation
Fastmail vs Proton Mail
  • I've used Fastmail with a custom domain for a few years now... (5+?) and have been really happy with it. I wish it was a bit cheaper (or had a better family plan), but it works well with my terminal email client (mutt).

    The web client is pretty quick and I use the calendar there all the time. Fastmail supports all the normal standards such as CalDAV, so you can use it with third party applications.

  • COSMIC Epoch: Terminal Tabs & Stacks (workflow)
  • Another video: COSMIC Epoch - Tiling per workspace demonstrating per workspace tiling/floating.

  • > A workflow video using Cosmic Epoch.

    1
    www.aquasec.com Snap Trap: The Hidden Dangers Within Ubuntu's Package Suggestion System

    Aqua Nautilus researchers have identified a security issue that arises from the interaction between Ubuntu’s command-not-found package and the snap package repository. While command-not-found serves as a convenient tool for suggesting installations for uninstalled commands, it can be inadvertently m...

    Snap Trap: The Hidden Dangers Within Ubuntu's Package Suggestion System

    > Aqua Nautilus researchers have identified a security issue that arises from the interaction between Ubuntu’s command-not-found package and the snap package repository. While command-not-found serves as a convenient tool for suggesting installations for uninstalled commands, it can be inadvertently manipulated by attackers through the snap repository, leading to deceptive recommendations of malicious packages.

    30
    Ubuntu Core Desktop Delayed, Won't Be Released in April
    www.omgubuntu.co.uk Ubuntu Core Desktop Delayed, Won't Be Released in April - OMG! Ubuntu

    Ubuntu Core Desktop will not be released alongside Ubuntu 24.04 LTS in April, as originally hoped. For those thinking "wait, what?!" — last year Canonical

    Ubuntu Core Desktop Delayed, Won't Be Released in April - OMG! Ubuntu

    > Ubuntu Core Desktop will not be released alongside Ubuntu 24.04 LTS in April, as originally hoped.

    > Canonical doesn’t go into details about what specific issues need resolving. One imagines, given that the first Ubuntu Core Desktop release was going to be a preview and not a recommended download, it’s a myriad bugs/difficulties — ones not easily sorted.

    30
    Mozilla names new CEO as it pivots to data privacy
    fortune.com Exclusive: Mozilla names new CEO as it pivots to data privacy

    Laura Chambers will step in as interim CEO to run operations until a permanent replacement is found.

    Exclusive: Mozilla names new CEO as it pivots to data privacy

    > Mozilla Corp., which manages the open-source Firefox browser, announced today that Mitchell Baker is stepping down as CEO to focus on AI and internet safety as chair of the nonprofit foundation. Laura Chambers, a Mozilla board member and entrepreneur with experience at Airbnb, PayPal, and eBay, will step in as interim CEO to run operations until a permanent replacement is found.

    https://archive.is/rmMEb

    Official Blog Post: A New Chapter for Mozilla: Focused Execution and an Expanded Role in Charting the Internet’s Future

    90
    Tech over Tea #205 CEO And Founder Of System76 | Carl Richell

    > Today we have the one of the 2 founders of System76 and current CEO of the company Carl Richell on the show to chat about the history of the company, how we got here and some of the cool stuff they've got in store.

    1
    Pop!_OS begins building packages for 24.04 (Noble Numbat)
    github.com Noble by jackpot51 · Pull Request #312 · pop-os/repo-release

    Updated jammy packages in 9a007f7 Released noble packages in 74709a7 Adjusted build script to release noble but ignore missing arm64 and i386 packages (on noble only)

    Noble by jackpot51 · Pull Request #312 · pop-os/repo-release

    With today's repo-release PR #312, Pop!_OS is now building packages for 24.04 (Noble Numbat).

    While this doesn't mean a release will happen any time soon, it does mean they are now beginning the process of testing and packaging for 24.04. In particular, they are now building many of the COSMIC DE components for 24.04 with this pull request.

    4

    > The New DSL 2024 has been reborn as a compact Linux distribution tailored for low-spec x86 computers. It packs a lot of applications into a small package. All the applications are chosen for their functionality, small size, and low dependencies. DSL 2024 also has many text-based applications that make it handy to use in a term window or TTY.

    > The new goal of DSL is to pack as much usable desktop distribution into an image small enough to fit on a single CD, or a hard limit of 700MB. This project is meant to service older computers and have them continue to be useful far into the future. Such a notion sits well with my values. I think of this project as my way of keeping otherwise usable hardware out of landfills.

    > As with most things in the GNU/Linux community, this project continues to stand on the shoulders of giants. I am just one guy without a CS degree, so for now, this project is based on antiX 23 i386. AntiX is a fantastic distribution that I think shares much of the same spirit as the original DSL project. AntiX shares pedigree with MEPIS and also leans heavily on the geniuses at Debian. So, this project stands on the shoulders of giants. In other words, DSL 2024 is a humble little project!

    10
    Canonical's Steam Snap is Causing Headaches for Valve
    www.omgubuntu.co.uk Canonical's Steam Snap is Causing Headaches for Valve - OMG! Ubuntu

    Canonical may be ramping up its efforts to improve the Ubuntu gaming experience — yasss — but it seems their Steam snap package is causing a few headaches

    Canonical's Steam Snap is Causing Headaches for Valve - OMG! Ubuntu

    > Timothée Besset, a software engineer who works on the Steam client for Valve, took to Mastodon this week to reveal: “Valve is seeing an increasing number of bug reports for issues caused by Canonical’s repackaging of the Steam client through snap”.

    > “We are not involved with the snap repackaging. It has a lot of issues”, Besset adds, noting that “the best way to install Steam on Debian and derivative operating systems is to […] use the official .deb”.

    > Those who don’t want to use the official Deb package are instead asked to ‘consider the Flatpak version’ — though like Canonical’s Steam snap the Steam Flatpak is also unofficial, and no directly supported by Valve.

    201
    www.theregister.com What comes after open source? Bruce Perens is working on it

    'Our licenses aren't working anymore,' says free software pioneer

    What comes after open source? Bruce Perens is working on it

    > Perens says there are several pressing problems that the open source community needs to address.

    > "First of all, our licenses aren't working anymore," he said. "We've had enough time that businesses have found all of the loopholes and thus we need to do something new. The GPL is not acting the way the GPL should have done when one-third of all paid-for Linux systems are sold with a GPL circumvention. That's RHEL."

    > Another straw burdening the Open Source camel, Perens writes, "is that Open Source has completely failed to serve the common person. For the most part, if they use us at all they do so through a proprietary software company's systems, like Apple iOS or Google Android, both of which use Open Source for infrastructure but the apps are mostly proprietary. The common person doesn't know about Open Source, they don't know about the freedoms we promote which are increasingly in their interest. Indeed, Open Source is used today to surveil and even oppress them."

    > Post-Open, as he describes it, is a bit more involved than Open Source. It would define the corporate relationship with developers to ensure companies paid a fair amount for the benefits they receive. It would remain free for individuals and non-profit, and would entail just one license.

    > Whether it can or not, Perens argues that the GPL isn't enough. "The GPL is designed not as a contract but as a license. What Richard Stallman was thinking was he didn't want to take away anyone's rights. He only wanted to grant rights. So it's not a contract. It's a license. Well, we can't do that anymore. We need enforceable contract terms."

    25
    Only Person Here @ MLM
  • Which world?

  • Wayland really breaks things… Just for now?

    > This post is in part a response to an aspect of Nate’s post “Does Wayland really break everything?“, but also my reflection on discussing Wayland protocol additions, a unique pleasure that I have been involved with for the past months.

    > Before I start I want to make a few things clear: The Linux desktop will be moving to Wayland – this is a fact at this point (and has been for a while), sticking to X11 makes no sense for future projects.

    > By switching to Wayland compositors, we are already forcing a lot of porting work onto toolkit developers and application developers. This is annoying, but just work that has to be done. It becomes frustrating though if Wayland provides toolkits with absolutely no way to reach their goal in any reasonable way.

    > Many missing bits or altered behavior are just papercuts, but those add up. And if users will have a worse experience, this will translate to more support work, or people not wanting to use the software on the respective platform.

    > What’s missing?

    > 1. Window positioning > 2. Window position restoration > 3. Window icons > 4. Limited window abilities requiring specialized protocols > 5. Automated GUI testing / accessibility / automation

    > I spent probably way too much time looking into how to get applications cross-platform and running on Linux, often talking to vendors (FLOSS and proprietary) as well. Wayland limitations aren’t the biggest issue by far, but they do start to come come up now, especially in the scientific space with Ubuntu having switched to Wayland by default. For application authors there is often no way to address these issues.

    35
    Solus 4.5 Released
    getsol.us Solus 4.5 Released | Solus

    The Solus team is proud to announce the release of Solus 4.5 Resilience. This release brings updated applications and kernels, refreshed sof

    > The Solus team is proud to announce the release of Solus 4.5 Resilience. This release brings updated applications and kernels, refreshed software stacks, a new installer, and a new ISO edition featuring the XFCE desktop environment.

    Highlights:

    • Calamares installer
    • Pipewire by default
    • ROCM support for AMD hardware
    • Hardware and kernel enablement

    > Behind the scenes, we made many improvements in 2023 to our tooling and infrastructure. While end users don’t see most of this directly, it means we are able to maintain and update Solus more efficiently. What end users will see is overall improvement in packages being kept up to date. Packagers and developers will notice many quality of life improvements, which enable our plans to migrate to Serpent tooling in 5.0. One of our future blog posts will summarize these changes.

    2
    The 6.7 kernel has been released

    > Some of the headline features in this release are: the removal of support for the Itanium architecture, the first part of the futex2 API, futex support in io_uring, the BPF exceptions mechanism, the bcachefs filesystem, the TCP authentication option, the kernel samepage merging smart scan mode, and networking support for the Landlock security module. See the LWN merge-window summaries (part 1, part 2) and the (in-progress) KernelNewbies 6.7 page for more information.

    Mailing List Announcement

    21
    Gentoo goes Binary (packages)
    www.gentoo.org Gentoo goes Binary! – Gentoo Linux

    News and information from Gentoo Linux

    Gentoo goes Binary! – Gentoo Linux

    > To speed up working with slow hardware and for overall convenience, we’re now also offering binary packages for download and direct installation! For most architectures, this is limited to the core system and weekly updates - not so for amd64 and arm64 however. There we’ve got a stunning >20 GByte of packages on our mirrors, from LibreOffice to KDE Plasma and from Gnome to Docker. Gentoo stable, updated daily. Enjoy! And read on for more details!

    62

    > Gaming on Linux is easier now than ever before. Though many PC games are still developed with Windows in mind, the emergence of Valve’s Proton and proceeding contributions from the open source community have made Linux gaming into a first-class experience.

    0

    > Well known KDE developer Nate Graham is out with a blog post today outlining his latest Wayland thoughts, how X11 is a bad platform, and the recent topic of "Wayland breaking everything" isn't really accurate.

    > "In this context, “breaking everything” is another perhaps less accurate way of saying “not everything is fully ported yet”. This porting is necessary because Wayland is designed to target a future that doesn’t include 100% drop-in compatibility with everything we did in the past, because it turns out that a lot of those things don’t make sense anymore. For the ones that do, a compatibility layer (XWayland) is already provided, and anything needing deeper system integration generally has a path forward (Portals and Wayland protocols and PipeWire) or is being actively worked on. It’s all happening!"

    Nate's Original Blog Post

    162
    Cosmic Terminal
    fosstodon.org Jeremy Soller 🦀 (@soller@fosstodon.org)

    This is cosmic-term, a very WIP project that takes the alacritty_terminal crate providing the majority of terminal code but rewrites the renderer to support additional features such as bidirectional text and ligatures. It will support both software and GPU rendering, and will have additional UI suga...

    > This is cosmic-term, a very WIP project that takes the alacritty_terminal crate providing the majority of terminal code but rewrites the renderer to support additional features such as bidirectional text and ligatures. It will support both software and GPU rendering, and will have additional UI sugar provided by libcosmic as they are implemented.

    cosmic-term

    1

    > For those that were interested in the openSUSE logo contest, the voting wrapped up on Tuesday and the results of this logo contest for new openSUSE branding have been selected.

    47
    Performance engineering on Ubuntu leaps forward with frame pointers by default in Ubuntu 24.04 LTS

    > In collaboration with Polar Signals we have committed that beginning with Ubuntu 24.04 LTS, our GNU Compiler Collection (GCC) package will enable frame pointers by default for 64-bit platforms. All packages in Ubuntu, with very few exceptions, will be rebuilt with frame pointers enabled, making them easier to profile and subsequently optimise.

    > There is a small performance penalty associated with the change, but in cases where the impact is high (such as the Python interpreter), we’ll continue to omit frame pointers until this is addressed. Our analysis suggests that the penalty on 64-bit architectures is between 1-2% in most cases. We will not make this change on 32-bit architectures where the penalty is higher.

    > Canonical, and the Ubuntu community, can make use of this improved debugging information to diagnose and target performance issues that are orders of magnitude more impactful than the 1-2% upfront cost.

    1
    makealinux.app Make a Linux App

    Make a Linux app. Stop making distributions.

    > Make a Linux app. Stop making distributions.

    48
    🦌 - 2023 DAY 9 SOLUTIONS -🦌
  • Language: Python

    Part 1

    Pretty straightforward. Took advantage of itertools.pairwise.

    def predict(history: list[int]) -> int:
        sequences = [history]
        while len(set(sequences[-1])) > 1:
            sequences.append([b - a for a, b in itertools.pairwise(sequences[-1])])
        return sum(sequence[-1] for sequence in sequences)
    
    def main(stream=sys.stdin) -> None:
        histories   = [list(map(int, line.split())) for line in stream]
        predictions = [predict(history) for history in histories]
        print(sum(predictions))
    
    Part 2

    Only thing that changed from the first part was that I used functools.reduce to take the differences of the first elements of the generated sequences (rather than the sum of the last elements for Part 1).

    def predict(history: list[int]) -> int:
        sequences = [history]
        while len(set(sequences[-1])) > 1:
            sequences.append([b - a for a, b in itertools.pairwise(sequences[-1])])
        return functools.reduce(
            lambda a, b: b - a, [sequence[0] for sequence in reversed(sequences)]
        )
    
    def main(stream=sys.stdin) -> None:
        histories   = [list(map(int, line.split())) for line in stream]
        predictions = [predict(history) for history in histories]
        print(sum(predictions))
    

    GitHub Repo

  • 🎄 - 2023 DAY 8 SOLUTIONS -🎄
  • Language: Python

    Part 1

    First part was very straight-forward: read the input in and simulate the navigation. Taking advantage of itertools.cycle made cycling through the instructions very easy :]

    Network = dict[str, dict[str, str]]
    
    def read_instructions(stream=sys.stdin) -> Iterator[str]:
        return itertools.cycle(stream.readline().strip())
    
    def read_network(stream=sys.stdin) -> Network:
        network = defaultdict(dict)
    
        for line in map(str.strip, stream):
            if not line:
                continue
    
            source, target_l, target_r = re.findall('[A-Z]{3}', line)
            network[source] = {
                'L': target_l,
                'R': target_r,
            }
    
        return network
    
    def navigate(instructions: Iterator[str], network: Network) -> int:
        count  = 0
        source = 'AAA'
        target = 'ZZZ'
    
        while source != target:
            source = network[source][next(instructions)]
            count += 1
    
        return count
    
    def main(stream=sys.stdin) -> None:
        instructions = read_instructions(stream)
        network      = read_network(stream)
        print(navigate(instructions, network))
    
    Part 2

    The second part was also straight-forward: locate the ghosts, and then navigate from each ghost until you hit an element that ends with a 'Z'. The trick to avoid brute-forcing is to realize that the ghosts will cycle (ie. repeat the same paths) if they all don't land on an element that ends with a 'Z' at the same time. To solve the problem, you just ened to calculate the steps for each ghost to reach an endpoint and then compute the lowest common multiple of those counts. Fortunately, Python has math.lcm, so not much code was needed!

    def navigate(instructions: Iterator[str], network: Network, element: str) -> int:
        count = 0
    
        while not element.endswith('Z'):
            element = network[element][next(instructions)]
            count += 1
    
        return count
    
    def locate_ghosts(network: Network) -> list[str]:
        return [element for element in network if element.endswith('A')]
    
    def main(stream=sys.stdin) -> None:
        instructions = read_instructions(stream)
        network      = read_network(stream)
        ghosts       = locate_ghosts(network)
        counts       = [navigate(cycle(instructions), network, ghost) for ghost in ghosts]
        print(math.lcm(*counts))
    

    GitHub Repo

  • 🍪 - 2023 DAY 7 SOLUTIONS -🍪
  • Language: Python

    This was fun. More enjoyable than I initially thought (though I've done card sorting code before).

    Part 1

    This was pretty straightforward: create a histogram of the cards in each hand to determine their type, and if there is a tie-breaker, compare each card pairwise. I use the Counter class from collections to do the counting, and then had a dictionary/table to convert labels to numeric values for comparison. I used a very OOP approach and wrote a magic method for comparing hands and used that with Python's builtin sort. I even got to use Enum!

    LABELS = {l: v for v, l in enumerate('23456789TJQKA', 2)}
    
    class HandType(IntEnum):
        FIVE_OF_A_KIND  = 6
        FOUR_OF_A_KIND  = 5
        FULL_HOUSE      = 4
        THREE_OF_A_KIND = 3
        TWO_PAIR        = 2
        ONE_PAIR        = 1
        HIGH_CARD       = 0
    
    class Hand:
        def __init__(self, cards=str, bid=str):
            self.cards  = cards
            self.bid    = int(bid)
            counts      = Counter(self.cards)
            self.type   = (
                HandType.FIVE_OF_A_KIND  if len(counts) == 1 else
                HandType.FOUR_OF_A_KIND  if len(counts) == 2 and any(l for l, count in counts.items() if count == 4) else
                HandType.FULL_HOUSE      if len(counts) == 2 and any(l for l, count in counts.items() if count == 3) else
                HandType.THREE_OF_A_KIND if len(counts) == 3 and any(l for l, count in counts.items() if count == 3) else
                HandType.TWO_PAIR        if len(counts) == 3 and any(l for l, count in counts.items() if count == 2) else
                HandType.ONE_PAIR        if len(counts) == 4 and any(l for l, count in counts.items() if count == 2) else
                HandType.HIGH_CARD
            )
    
        def __lt__(self, other):
            if self.type == other.type:
                for s_label, o_label in zip(self.cards, other.cards):
                    if LABELS[s_label] == LABELS[o_label]:
                        continue
                    return LABELS[s_label] < LABELS[o_label]
                return False
            return self.type < other.type
    
        def __repr__(self):
            return f'Hand(cards={self.cards},bid={self.bid},type={self.type})'
    
    def read_hands(stream=sys.stdin) -> list[Hand]:
        return [Hand(*line.split()) for line in stream]
    
    def main(stream=sys.stdin) -> None:
        hands    = sorted(read_hands(stream))
        winnings = sum(rank * hand.bid for rank, hand in enumerate(hands, 1))
        print(winnings)
    
    Part 2

    For the second part, I just had to add some post-processing code to convert the jokers into actual cards. The key insight is to find the highest and most numerous non-Joker card and convert all the Jokers to that card label.

    This had two edge cases that tripped me up:

    1. 'JJJJJ': There is no other non-Joker here, so I messed up and ranked this the lowest because I ended up removing all counts.

    2. 'JJJ12': This also messed me up b/c the Joker was the most numerous card, and I didn't handle that properly.

    Once I fixed the post-processing code though, everything else remained the same. Below, I only show the parts that changed from Part A.

    LABELS = {l: v for v, l in enumerate('J23456789TQKA', 1)}
    
    ...
    
    class Hand:
        def __init__(self, cards=str, bid=str):
            self.cards  = cards
            self.bid    = int(bid)
            counts      = Counter(self.cards)
    
            if 'J' in counts and len(counts) > 1:
                max_label = max(set(counts) - {'J'}, key=lambda l: (counts[l], LABELS[l]))
                counts[max_label] += counts['J']
                del counts['J']
    
            self.type   = (...)
    

    GitHub Repo

  • 🌟 - 2023 DAY 6 SOLUTIONS -🌟
  • Language: Python

    Part 1

    Not much to say... this was pretty straightforward.

    def race(charge_time: int, total_time: int) -> int:
        return charge_time * (total_time - charge_time)
    
    def main(stream=sys.stdin) -> None:
        times     = [int(t) for t in stream.readline().split(':')[-1].split()]
        distances = [int(d) for d in stream.readline().split(':')[-1].split()]
        product   = 1
    
        for time, distance in zip(times, distances):
            ways     = [c for c in range(1, time + 1) if race(c, time) > distance]
            product *= len(ways)
    
        print(product)
    
    Part 2

    Probably could have done some math, but didn't need to :]

    def race(charge_time: int, total_time: int):
        return charge_time * (total_time - charge_time)
    
    def main(stream=sys.stdin) -> None:
        time     = int(''.join(stream.readline().split(':')[-1].split()))
        distance = int(''.join(stream.readline().split(':')[-1].split()))
        ways     = [c for c in range(1, time + 1) if race(c, time) > distance]
        print(len(ways))
    

    GitHub Repo

  • 🎁 - 2023 DAY 5 SOLUTIONS -🎁
  • Language: Python

    Part 1

    The first part wasn't too bad... once I realized you should store the ranges and not actually generate all the numbers o_O.

    Seeds = list[int]
    Map   = tuple[int, int, int]
    Maps  = list[Map]
    
    def read_almanac(stream=sys.stdin) -> tuple[Seeds, list[Map]]:
        seeds: Seeds = [int(seed) for seed in stream.readline().split(':')[-1].split()]
        maps:  Maps  = []
    
        for line in map(str.strip, stream):
            if line.endswith('map:'):
                maps.append([])
                continue
                
            try:
                dst, src, length = map(int, line.split())
            except ValueError:
                continue
                
            maps[-1].append((dst, src, length))
    
        return seeds, maps
    
    def locate_seed(seed: int, maps: list[Map]) -> int:
        location = seed
        for amap in maps:
            for dst, src, length in amap:
                if src <= location < src + length:
                    location = dst + (location - src)
                    break
        return location
    
    def main(stream=sys.stdin) -> None:
        seeds, maps = read_almanac(stream)
        locations   = [locate_seed(seed, maps) for seed in seeds]
        print(min(locations))
    
    Part 2

    This part took me forever, mostly to actually run, but also to fix a few off-by-one errors :|

    Fortunately, I was able to re-use most of my code in Part A and just add a new function to search the range of seeds.

    Even with concurrent.futures and a 24-core machine, it still took me about 30 - 45 minutes to complete with Python (that said, there were only 10 seed range s in the input, so I could only use 10 cores, and of those 5 of the ranges appeared to be really large, leading to a long tail effect).

    def locate_seeds(srange: Range, maps: list[Map]={}) -> int:
        seeds   = range(srange[0], srange[0] + srange[1])
        locator = functools.partial(locate_seed, maps=maps)
        return min(map(locator, seeds))
    
    # Main Execution
    
    def main(stream=sys.stdin) -> None:
        seeds, maps = read_almanac(stream)
        locator     = functools.partial(locate_seeds, maps=maps)
    
        with concurrent.futures.ProcessPoolExecutor() as executor:
            locations = executor.map(locator, batched(seeds, 2))
    
        print(min(locations))
    

    GitHub Repo

  • ☃️ - 2023 DAY 4 SOLUTIONS -☃️
  • Language: Python

    Part 1

    Sets really came in handy for this challenge, as did recognizing that you can use powers of two to compute the points for each card. I tried using a regular expression to parse each card, but ended up just doing it manually with split :|

    Numbers = set[int]
    Card    = list[Numbers]
    
    def read_cards(stream=sys.stdin) -> Iterator[Card]:
        for line in stream:
            yield [set(map(int, n.split())) for n in line.split(':')[-1].split('|')]
    
    def main(stream=sys.stdin) -> None:
        cards  = [numbers & winning for winning, numbers in read_cards(stream)]
        points = sum(2**(len(card)-1) for card in cards if card)
        print(points)
    
    Part 2

    This took me longer than I wished... I had to think about it carefully before seeing how you can just keep track of the counts of each card, and then when you get to that card, you add to its copies your current count.

    def main(stream=sys.stdin) -> None:
        cards  = [numbers & winning for winning, numbers in read_cards(stream)]
        counts = defaultdict(int)
    
        for index, card in enumerate(cards, 1):
            counts[index] += 1
            for copy in range(index + 1, index + len(card) + 1):
                counts[copy] += counts[index]
    
        print(sum(counts.values()))
    

    GitHub Repo

  • ❄️ - 2023 DAY 3 SOLUTIONS -❄️
  • Language: Python

    Classic AoC grid problem... Tedious as usual, but very doable. Took my time and I'm pretty happy with the result. :]

    Part 1

    For the first part, I decided to break the problem into: 1. Reading the schematic, 2. Finding the numbers, 3. Finding the parts. This was useful for Part 2 as I could re-use my read_schematic and find_numbers functions.

    Two things I typically do for grid problems:

    1. Pad the grid so you can avoid annoying boundary checks.
    2. I have a DIRECTIONS list I loop through so I can check easily check the neighbors.
    Schematic  = list[str]
    Number     = tuple[int, int, int]
    
    DIRECTIONS = (
        (-1, -1),
        (-1,  0),
        (-1,  1),
        ( 0, -1),
        ( 0,  1),
        ( 1, -1),
        ( 1,  0),
        ( 1,  1),
    )
    
    def read_schematic(stream=sys.stdin) -> Schematic:
        schematic = [line.strip() for line in stream]
        columns   = len(schematic[0]) + 2
        return [
            '.'*columns,
            *['.' + line + '.' for line in schematic],
            '.'*columns,
        ]
    
    def is_symbol(s: str) -> bool:
        return not (s.isdigit() or s == '.')
    
    def find_numbers(schematic: Schematic) -> Iterator[Number]:
        rows    = len(schematic)
        columns = len(schematic[0])
    
        for r in range(1, rows):
            for number in re.finditer(r'[0-9]+', schematic[r]):
                yield (r, *number.span())
    
    def find_parts(schematic: Schematic, numbers: Iterator[Number]) -> Iterator[int]:
        for r, c_head, c_tail in numbers:
            part = int(schematic[r][c_head:c_tail])
            for c in range(c_head, c_tail):
                neighbors = (schematic[r + dr][c + dc] for dr, dc in DIRECTIONS)
                if any(is_symbol(neighbor) for neighbor in neighbors):
                    yield part
                    break
    
    def main(stream=sys.stdin) -> None:
        schematic = read_schematic(stream)
        numbers   = find_numbers(schematic)
        parts     = find_parts(schematic, numbers)
        print(sum(parts))
    
    Part 2

    For the second part, I just found the stars, and then I found the gears by checking if the stars are next to two numbers (which I had found previously).

    def find_stars(schematic: Schematic) -> Iterator[Star]:
        rows    = len(schematic)
        columns = len(schematic[0])
    
        for r in range(1, rows):
            for c in range(1, columns):
                token = schematic[r][c]
                if token == '*':
                    yield (r, c)
    
    def find_gears(schematic: Schematic, stars: Iterator[Star], numbers: list[Number]) -> Iterator[int]:
        for star_r, star_c in stars:
            gears = [                                                                                                                      
                int(schematic[number_r][number_c_head:number_c_tail])
                for number_r, number_c_head, number_c_tail in numbers
                if any(star_r + dr == number_r and number_c_head <= (star_c + dc) < number_c_tail for dr, dc in DIRECTIONS)
            ]
            if len(gears) == 2:
                yield gears[0] * gears[1]
    
    def main(stream=sys.stdin) -> None:
        schematic = read_schematic(stream)
        numbers   = find_numbers(schematic)
        stars     = find_stars(schematic)
        gears     = find_gears(schematic, stars, list(numbers))
        print(sum(gears))
    

    GitHub Repo

  • 🦌 - 2023 DAY 2 SOLUTIONS -🦌
  • This was mostly straightforward... basically just parsing input. Here are my condensed solutions in Python

    Part 1
    Game = dict[str, int]
    
    RED_MAX   = 12
    GREEN_MAX = 13
    BLUE_MAX  = 14
    
    def read_game(stream=sys.stdin) -> Game:
        try:
            game_string, cubes_string = stream.readline().split(':')
        except ValueError:
            return {}
    
        game: Game = defaultdict(int)
        game['id'] = int(game_string.split()[-1])
    
        for cubes in cubes_string.split(';'):
            for cube in cubes.split(','):
                count, color = cube.split()
                game[color] = max(game[color], int(count))
    
        return game
    
    def read_games(stream=sys.stdin) -> Iterator[Game]:
        while game := read_game(stream):
            yield game
    
    def is_valid_game(game: Game) -> bool:
        return all([
            game['red']   <= RED_MAX,
            game['green'] <= GREEN_MAX,
            game['blue']  <= BLUE_MAX,
        ])
    
    def main(stream=sys.stdin) -> None:
        valid_games = filter(is_valid_game, read_games(stream))
        sum_of_ids  = sum(game['id'] for game in valid_games)
        print(sum_of_ids)
    
    Part 2

    For the second part, the main parsing remainded the same. I just had to change what I did with the games I read.

    def power(game: Game) -> int:
        return game['red'] * game['green'] * game['blue']
    
    def main(stream=sys.stdin) -> None:
        sum_of_sets = sum(power(game) for game in read_games(stream))
        print(sum_of_sets)
    

    GitHub Repo

  • 🎄 - 2023 DAY 1 SOLUTIONS -🎄
  • Part-A in Python: https://github.com/pbui/advent-of-code-2023/blob/master/day01/day01-A.py

    Was able to use a list comprehension to read the input.

    Part-B in Python: https://github.com/pbui/advent-of-code-2023/blob/master/day01/day01-B.py

    This was trickier...

    Hint

    You have to account for overlapping words such as: eightwo. This actually simplifies things as you just need to go letter by letter and check if it is a digit or one of the words.

    Update: Modified Part 2 to be more functional again by using a map before I filter

  • [Game Thread] #17 Notre Dame @ Stanford - November 25, 2023 at 7:00 PM ET
  • Despite the turnovers, we are still winning... that's good I guess.

  • [Game Thread] #17 Notre Dame @ Stanford - November 25, 2023 at 7:00 PM ET
  • Just feed Audric. That's all we need to do.

  • Humble Book Bundle: The John Scalzi Collection
  • Wow, thanks for sharing. I've already read the Old Man's War series, but now I can finish the Collapsing Empire series (only read the first book).

  • Are libreddit frontends for reddit already non functional?
  • You can self-host libreddit, which is what I do, and it will still continue to work. That said, it is on borrowed time as development has mostly stopped.

    All the public instances are unusable b/c of the rate-limits, unfortunately.

  • How a kernel developer made my styluses work again on newer kernels!
  • And that's exactly what happened in your case David. Which is why I'm so happy (also because I fixed the tools from an author I like and already had the books at home :-P):

    Really detailed and cool response from the kernel developer. I also found the use of the recent BPF feature to provide a workaround until a proper kernel fix lands really interesting.

  • pnutzh4x0r pnutzh4x0r @lemmy.ndlug.org
    Posts 177
    Comments 257