<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/assets/full_style.xslt" type="text/xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Daniel Nitsikopoulos</title>
    <description>The personal blog of Daniel Nitsikopoulos, software engineer from Canberra, ACT</description>
    <link>https://dnitza.com</link>
    <atom:link href="https://dnitza.com/feeds/blog_rss" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Tue, 21 Apr 2026 11:42:53 +0000</lastBuildDate>
    <pubDate>Tue, 21 Apr 2026 11:42:53 +0000</pubDate>
    <ttl>1800</ttl>
    <alternate_feed>
      <link>/feeds/rss</link>
      <title>Main feed</title>
      <description>All the content published to this blog.</description>
    </alternate_feed>
    <alternate_feed>
      <link>/feeds/blog_rss</link>
      <title>Blog feed</title>
      <description>Only longer form text posts</description>
    </alternate_feed>
    <alternate_feed>
      <link>/feeds/bookmarks_rss</link>
      <title>Bookmarks</title>
      <description>Only bookmarks</description>
    </alternate_feed>
    <item>
      <title>Decay and demolish</title>
      <description>
        <![CDATA[<p>It is a Sunday afternoon and I’m quickly doing the dishes while my son is briefly enthralled by his play kitchen. It’s a typical day in Canberra, the sky is clear, the birds are singing to their mates, and a paper wasp is building a nest up in the eaves of my house.</p>

<p>This isn’t the first nest to be built there. Years ago, I saw a different family of wasps build their nest a few rafters over. That nest lasted a season, bore a few wasps and was swiftly abandoned.</p>

<p>I’m done washing now, and we head over to the couch. Hanging just outside the back window is a planter that is tipped on its side. Devoid of plant life, it has become the home of many a generation of Spotted Pardalote. Each generation tirelessly excavating a burrow to call home for the season. Adorning the insides with fine grass and feathers to make a home. Eggs are laid, fledglings emerge, and so the nest is abandoned. Until next year, when another spot is excavated and turned in to a safe space to protect new life.</p>

<p>The cycle of build, inhabit, departure, decay continues.</p>

<p>We humans build houses, mega structures, cities to achieve similar goals. Provide shelter and safety — a place for life to take place, but also to manufacture for profit and advance humanity.</p>

<p>We’ve built entire cities whose soul purpose was to provide shelter or manufacture cars — and when those things are no longer needed or financially feasible, we depart and let entropy take over.</p>

<p>We build homes in the suburbs and raise families, sometimes for several generations, but ultimately, those homes are abandoned and passed on to other people. In this case though, the structure itself is still used and cared for.</p>

<p>When a structure ultimately serves its purpose, however, we, much like the animal kingdom defaults to departure rather than demolition. I have no answer as to why, but departure feels more like a release. It feels more respectful to the places that sheltered us and helped us live our lives. It feels like we’re leaving behind a reminder, to ourselves and future generations or archeologists, that life happened here. That we briefly defied entropy, ordered something chaotic and did something beautiful with the result. And to let order naturally return to chaos is to let something in the universe slowly exhale. Unlike the abrupt and violent severance of demolition.</p>

<p>The same can be said for life itself. When our time comes, and our bodies are abandoned, we usually see the same processes take place. A natural decay, a return to the earth to feed other life and continue the cycle of impermanence. Even the seeming annihilation of cremation is just accelerated decay.</p>

<p>I watch my son at his pile of blocks now, placing wooden shapes down around his other toys. He’s building something too, in his way — temporary worlds, brief architectures of imagination that will be abandoned the moment something else catches his eye. No ceremony, no destruction, just departure.</p>

<p>Perhaps this is what we’re all doing. The wasp in the eaves, the pardalotes returning to burrow anew each season, the families who locked their doors one last time — we build, we inhabit, we leave. And in leaving rather than destroying, we acknowledge something true: that the structure was never really ours to unmake. We borrowed materials, borrowed time, created temporary pockets of order. But the materials themselves want to return, and maybe our departure is simply honouring and enabling that want.</p>

<p>The old pardalote nest from last season is still visible in the planter if you look closely — a small cave of earth and dried grass, already beginning to collapse in to the soil around it. The wasp nest from years ago has gone papery and gray, dissolving slowly into the eaves. These aren’t monuments to failure or loss. They’re evidence. Life happened here. Order briefly prevailed. And now the universe gets to slowly breathe it all back in.</p>

<p>I wonder if this is the real legacy we leave behind — not the structures themselves, but the pattern. The knowledge that it’s okay to build something beautiful, live fully and completely within it, and then simply walk away.</p>
]]>
      </description>
      <link>https://dnitza.com/posts/decay-and-demolish</link>
      <guid isPermaLink="true">https://dnitza.com/posts/decay-and-demolish</guid>
      <pubDate>Sat, 07 Feb 2026 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Centrefall — a small PlayDate game</title>
      <description>
        <![CDATA[<div><figure class="attachment attachment--preview"><a href="https://nitza.grouse.site/images/grouse-blog-1/01/01/2026/9428ee43-1e26-42c5-bcae-c417c4857d2e.gif"><img src="https://nitza.grouse.site/images/grouse-blog-1/01/01/2026/9428ee43-1e26-42c5-bcae-c417c4857d2e.gif" width="400" height="240"><figcaption class="attachment__caption"></figcaption></a></figure><br>I had a shower thought of a fun PlayDate game idea the other day and have been spending the last few evenings getting the basics working.<br><br>The idea is a legally distinct tetromino block stacker, but the base is. 4x4 platform that can be rotated with the PlayDate’s crank.<br><br>So far, it’s pretty fun! There are some bugs that I think might be fun features (like blocks getting smeared across the board if you rotate the platform just as they’re locking in to place — could be a neat trick if you know it, or an infuriating bug otherwise).<br><br>I think I’ll keep at it, build out some difficulty progression (even if it’s just the blocks falling faster as your score increases) and see where experimentation takes me!<br><br>Got a PlayDate? <a href="https://nitza.itch.io/centerfall">Give it a spin</a><br><br></div>
]]>
      </description>
      <link>https://dnitza.com/posts/centrefall-a-small-playdate-game</link>
      <guid isPermaLink="true">https://dnitza.com/posts/centrefall-a-small-playdate-game</guid>
      <pubDate>Thu, 01 Jan 2026 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Genuary: Day 1</title>
      <description>
        <![CDATA[<div><img src='https://nitza.grouse.site/images/grouse-blog-1/27/12/2025/23d5838a-ae4d-4d2c-a739-71f5f79d4a55.gif'/> <div>&nbsp;<a href="https://genuary.art/prompts">Prompt: one colour, one shape</a></div>
</div>]]>
      </description>
      <link>https://dnitza.com/posts/genuary-day-1</link>
      <guid isPermaLink="true">https://dnitza.com/posts/genuary-day-1</guid>
      <pubDate>Thu, 01 Jan 2026 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Media I've enjoyed in 2025</title>
      <description>
        <![CDATA[<h3>Games</h3>
<ul>
<li>ARC Raiders</li>
<li>Split Fiction</li>
<li>Blue Prince</li>
<li>Hollow Knight: Silksong</li>
<li>Death Stranding 2</li>
</ul>
<h3>Music</h3>
<ul>
<li><a href="https://music.apple.com/au/album/even-in-arcadia/1800532611">Sleep Token — Even in Arcadia</a></li>
<li><a href="https://music.apple.com/au/album/starship-syncopation/1755304423">Starship Syncopation — Cory Wong</a></li>
<li><a href="https://music.apple.com/au/album/heavy-metal/1766984761">Heavy Metal —&nbsp;Cameron Winter</a></li>
<li><a href="https://music.apple.com/au/album/dont-tap-the-glass/1827715784">DON'T TAP THE GLASS — Tyler, The Creator</a></li>
<li><a href="https://music.apple.com/au/album/gnx/1781270319">GNX — Kendrick Lamar</a></li>
<li><a href="https://music.apple.com/au/album/usb/1859959566">USB — Fred Again..</a></li>
</ul>
<h3>Live Sets</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=c0-hvjV2A5Y">Fred Again.. | Boiler Room</a></li>
<li><a href="https://www.youtube.com/watch?v=TEp-Uk3Dfx4">Caribou, Floating Points &amp; Fred Again.. | Drumless Mix</a></li>
<li><a href="https://www.youtube.com/watch?v=hFSwC8DfUyI">King Gizzard &amp; The Lizard Wizard — Live in Athens (all of the nights are amazing)</a></li>
<li><a href="https://www.youtube.com/watch?v=rAOHJqJMYDA">Underworld | Boiler Room: London</a></li>
</ul>
<h3>Books</h3>
<ul>
<li><a href="https://app.thestorygraph.com/books/b50a6f84-f3f1-41d0-9281-a03d64e43ab5">Kafka On The Shore —&nbsp;Haruki Murakami</a></li>
<li><a href="https://app.thestorygraph.com/books/fb678a3a-e3f9-4ece-b97e-ef92b4e7e6ad">The Wind-Up Bird Chronicle — Haruki Murakami</a></li>
<li><a href="https://app.thestorygraph.com/books/32ce1591-714c-47e6-afbb-93f0e8d618f5">Hard-Boiled Wonderland and the End of the World — Haruki Murakami</a></li>
<li><a href="https://app.thestorygraph.com/books/92d28fae-1e3d-43ec-9702-15ee11ffc43d">The Peripheral — William Gibson</a></li>
</ul>
<h3>TV Shows / Movies</h3>
<ul>
<li><a href="https://letterboxd.com/dnitza/film/the-naked-gun/"><strong>The Naked Gun</strong></a></li>
<li><a href="https://letterboxd.com/dnitza/film/alien-romulus/"><strong>Alien: Romulus</strong></a></li>
<li><a href="https://letterboxd.com/dnitza/film/omar-and-cedric-if-this-ever-gets-weird/"><strong>Omar and Cedric: If This Ever Gets Weird</strong></a></li>
</ul>
]]>
      </description>
      <link>https://dnitza.com/posts/d0ceef55-b0df-45d8-bf72-cc78c9cdd3c3</link>
      <guid isPermaLink="true">https://dnitza.com/posts/d0ceef55-b0df-45d8-bf72-cc78c9cdd3c3</guid>
      <pubDate>Thu, 25 Dec 2025 02:52:39 +0000</pubDate>
    </item>
    <item>
      <title>Week 88 — Dogs, birthdays and raiders</title>
      <description>
        <![CDATA[<p></p><ul><li>It's been a while! The last few months have brought many a change. I won't go in to details but it has been a busy few months. Despite everything, the world keeps on spinning and so another week passes! Let's get in to what happened;</li><li>Last week of parental leave before getting back on the wheel! I am quite excited to get back and catch up with work peeps again!</li><li>Big week for the dogs. Barkly has a lump on his spleen that needs to come off as it's teetering on too-big-to-leave. He also has some liver enzyme issues that I tried to get some insight in to a while ago, to no avail. On the plus side, I might be able to get some answers while the spleen-related surgery is happening so I can better treat the little boy.</li><li>On Sunday, both Barkly and Crumpet got in to the nappy bin, tore apart some nappies and ate some of the absorbent material. So that was a fun trip to the vet to get them to bring the nappy parts up! What made it even better was that the folks were understaffed (boo) and I had to manage the spewing dogs, trying to catch whatever they were bringing up. Thankfully, it was all pretty quick and I was out of there with some lighter, and happier dogs.</li><li>I had Sebastian's first birthday at the zoo! Can't believe the little man is already 1! The day itself was a little rainy, so we didn't get to wander too much, but the little guy had his first babychino and a play around on the playground / swings &lt;3</li><li>I've been trying to collect some of the poems that have been on my mind lately at <a href="https://dnitza.com/poems">/poems</a>.</li><li>I've been playing some paper Magic online with <a href="https://spelltable.wizards.com/">Spelltable</a> and it has been surprisingly effective! Even when a card looks illegible on the video I am seeing, the software is still able to parse it just fine. I reckon there must be some shenanigans happening that has the card recognition happening locally on the other machine before the video is compressed and sent to me when I click a region of the table on my screen. If not, then suuuuper impressed at how it's able to recognise cards that look blurry on my end.</li><li>I've been playing ARC Raiders a bit this week and I've really enjoyed my time with it. I'd never played extraction shooters, but I totally get the hype about the genre now. For those unfamiliar, an extraction shooter has you load in to a map with other humans and computer players. The humans can choose to be friendly and team up to take on the computer players, or be hostile and take more rewards for themselves. You load in to each map with a selection of things that you've collected in previous matches, and if you don't make it out safely, you lose everything. As a chronic hoarder of in-game items (you know, in case I need to use that potion later), it took a bit to get over using all the things I have collected and risking my good things™ but after losing stuff once or twice, the fear of loss has subsided, and the game is just a joy now.</li><li>I've been continuing my journey through the works of Haruki Murakami, now about 1/3 of the way through <em>The Wind-Up Bird Chronicle</em>. So far a slow start, but there has been plenty of time to build such a rich and interesting set of characters and world. I'm digging it.</li></ul><div><br></div><p></p>
]]>
      </description>
      <link>https://dnitza.com/posts/week-88-dogs-birthdays-and-raiders</link>
      <guid isPermaLink="true">https://dnitza.com/posts/week-88-dogs-birthdays-and-raiders</guid>
      <pubDate>Sun, 02 Nov 2025 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Visiting James Turrell's "Within Without" Skyspace</title>
      <description>
        <![CDATA[<div><img src='https://nitza.grouse.site/images/grouse-blog-1/22/10/2025/ab8ab7b9-5b77-4bdb-b153-b21f837cd0fa.jpeg'/><img src='https://nitza.grouse.site/images/grouse-blog-1/22/10/2025/a1f41923-7eb3-43c3-ae84-2981197f9d17.jpeg'/><img src='https://nitza.grouse.site/images/grouse-blog-1/22/10/2025/09388fde-25d2-4057-9b7c-c3b238837178.jpeg'/><img src='https://nitza.grouse.site/images/grouse-blog-1/22/10/2025/38ce24b7-274d-43ab-b354-f51ddcdb2704.jpeg'/><img src='https://nitza.grouse.site/images/grouse-blog-1/22/10/2025/47a70d9d-9586-4205-8062-120a849740b5.jpeg'/><img src='https://nitza.grouse.site/images/grouse-blog-1/22/10/2025/613f4e32-0253-4788-a8f0-0177d62e72be.jpeg'/> </div>]]>
      </description>
      <link>https://dnitza.com/posts/visiting-james-turrells-within-without-skyspace</link>
      <guid isPermaLink="true">https://dnitza.com/posts/visiting-james-turrells-within-without-skyspace</guid>
      <pubDate>Fri, 24 Oct 2025 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Might be a Raiders fan yet!</title>
      <description>
        <![CDATA[<div><img src='https://nitza.grouse.site/images/grouse-blog-1/19/07/2025/a0e8ccfe-fc3c-443e-ac3b-5ea0933ab471.jpeg'/><img src='https://nitza.grouse.site/images/grouse-blog-1/19/07/2025/8d1933c3-0edc-4814-bbac-23612ae390f7.jpeg'/> </div>]]>
      </description>
      <link>https://dnitza.com/posts/might-be-a-raiders-fan-yet</link>
      <guid isPermaLink="true">https://dnitza.com/posts/might-be-a-raiders-fan-yet</guid>
      <pubDate>Sat, 19 Jul 2025 18:50:23 +0000</pubDate>
    </item>
    <item>
      <title>Week 87 — Free ride</title>
      <description>
        <![CDATA[<p></p><ul><li>Sebastian is really close to crawling. He can plank forever, and gets straight on to his knees when I put him down.</li><li>I've been working from a co-working space in Weston Creek for the last few months. Several times a week, the bus's MyWay+ reader is borked, which means a free fare in to work — and usually a free fare home too! I do feel for the folks who have to maintain the system, and I just hope this wasn't a case of ship the thing before it's ready because we have to hit some unrealistic deadline (though I bet that was it).</li><li>I have given up on Skull and Bones — I think I did 3 different mission types in the 10 hours I played. It feels very odd that you can dress up your pirate, you can walk them around in a town, explore islands with them — but that's it? Would have been cool to have some level of combat. Maybe some cool caves to explore on an island, or some stakes when boarding an enemy ship. Oh well — it was very pretty, and if its live-service-ness sustains it, then maybe I'll check back in in a year or two and see what kind of pirate-shenanigans I can get up to.</li><li>Most of my game time is now back on Diablo IV. It was free on PSN this month, so I'm back in with a new Season 9 character. It's kinda nice playing it on a machine that can consistently push 60 FPS (the Mac does most of the time, but also gets kinda warm). I am also keen to see how the couch co-op goes!</li><li>I went to the NMA this weekend for a free community day at the <a href="https://www.nma.gov.au/whats-on/fairfax-discovery-centre">discovery centre</a>. Sebastian loved it! There were so many new things to touch and see, new faces to smile at too! I think he got a bit frustrated (or overwhelmed) after a while though as there was so much to look at, and so many new noises that we bailed after about 45 mins. The centre itself looks pretty neat! I just wish it had more natural light — even I was feeling the effects of the artificial lights after our time there!</li></ul><div><br></div><p></p>
]]>
      </description>
      <link>https://dnitza.com/posts/week-87-free-ride</link>
      <guid isPermaLink="true">https://dnitza.com/posts/week-87-free-ride</guid>
      <pubDate>Sun, 06 Jul 2025 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Week 86 — Dad days</title>
      <description>
        <![CDATA[<ul><li>I’ve been playing Skull and Bones on and off over the last few weeks. It’s an extremely pretty game. The character models look weird, but the world is gorgeous. I think I’m still in tutorial land, because the missions I am doing are still basically fetch quests. I hope there is some more variety as the game opens up because the ship-to-ship combat is heaps of fun, and I would like to do more of it.</li><li>I caught up with some Dads from a Dad group I went to the other week. The weather on Saturday was incredible — freezing, but the sun was out which made hanging out at the Capital Brewery’s outdoor space so joyful. It was nice to meet the other bubs as well! Sebastian was fascinated by one of the other kiddo’s jumper — reaching out to grab it whenever she got close. He also had a pretty nice time, watching all the goings on and smiling at everyone.</li><li>Last night I went to trivia at the pub in Kingston. It was great fun! I had some mates and their kids join us — with the kids nailing so many of the questions! Sebastian also came along — staying up way past his bed time, which made it a bit of a challenge to re-regulate him. But we got there in the end and he slept ok-ish for the rest of the night!</li></ul>
]]>
      </description>
      <link>https://dnitza.com/posts/week-86-dad</link>
      <guid isPermaLink="true">https://dnitza.com/posts/week-86-dad</guid>
      <pubDate>Mon, 30 Jun 2025 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Week 85 — livin’ la beta loca</title>
      <description>
        <![CDATA[<p></p><ul><li>3rd and final short week in a row this week 😅 who knows how I am going to go with working 5 day weeks after all this! I had been working half-ish days on Sundays over those 3 weeks to do some maintenance things at work, so kinda balances out, I guess.</li><li>I have been using the watch and iOS 26 betas over the last week or so. I knew going in they would be super rough (and they are). I am super interested to see <em>how</em> they polish this release, which parts they dial back, which parts they pull. Life is also too short to not play with the cool things (even if they are half baked), the battery drain and sluggish performance will be long forgotten by the time GM 1 comes around. The glass material is really nice, but I am way more drawn to it than the content it is over. Maybe that will fade when it is no longer new and interesting, but right now, I am spending way too much time slowly moving content below the buttons to see the effects of the material.</li><li>I started Crumpet on seisure medication a couple of weeks back because her seisures were becoming more and more frequent (one every 5 weeks or so). It has been a while now, and she has been faring pretty well. She hasn’t had any of the side effects of the medication which is heartening and her liver function seems to have remained the same.</li><li>Sebastian has started saying “mama” and “baba” which melts my heart every damn time. I am not sure how much intention there is behind it at 8 months, but if I go to him / pay attention to him whenever he says it, maybe that’ll build a link?</li></ul><div><br></div><p></p>
]]>
      </description>
      <link>https://dnitza.com/posts/week-85-livin-la-beta-loca</link>
      <guid isPermaLink="true">https://dnitza.com/posts/week-85-livin-la-beta-loca</guid>
      <pubDate>Sun, 22 Jun 2025 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Week 84 — Winter has come</title>
      <description>
        <![CDATA[<p><ul><li>The last 3 weekends have been long weekends for me. Reconciliation Day, The King&#39;s Birthday (still weird to write) and some time in lieu (still hard to write). Who knows how I will cope going back to 5 day weeks 😂. Granted, I was working each of the Sundays so it kinda worked out well.</li><li>Winter is well and truly here. We&#39;ve had to put on the big heater because the small space heater was struggling to keep the room within 5 degrees of its setting. Cranking it to 22, but shivering in 17 degrees is no way to live! Though, nor is paying stupidly high gas bills.</li><li>Our new house has been insulated ready to be plastered. I took a walk through and even with a bunch of holes still, it feels incredibly cosy and sound proof. Double glazed windows that <em>actually seal </em>do wonders. Who knew. Let&#39;s see how it goes in the Canberra winter!</li><li>WWDC was on earlier this week. I always look forward to this time of year. The excitement of new Apple things is still strong in this one, but, for some reason this year&#39;s WWDC felt just a little off. I am not sure if it was the underlying environment that Apple is operating in. Or if a new UI being the headline thing isn&#39;t as exciting. Nevertheless, I was mad enough to install beta 1 on my phone and reealllly like the Liquid Glass UI (even if it is buggy AF at the moment). I am excited to see what they do with it.</li><li>Sebastian got his first tooth this week — on the lower left. We&#39;ll see how he goes chomping food now (we might need to be more vigilant if he can break off chunks of things 😆). Also, must be weird to have a bottom tooth but no top teeth to stop you from biting your gums.</li></ul><div><br></div></p>
]]>
      </description>
      <link>https://dnitza.com/posts/week-84</link>
      <guid isPermaLink="true">https://dnitza.com/posts/week-84</guid>
      <pubDate>Mon, 16 Jun 2025 10:25:12 +0000</pubDate>
    </item>
    <item>
      <title>Week 83 — Window seat</title>
      <description>
        <![CDATA[<ul><li>Regular length week this week, but 2 long weekends coming up ;)</li><li>A pretty eventful week at work this week! There's a carpark that adjoins to the office building via a walkway on the level bellow ours. On Wednesday a handful of youths decided the best thing to do was climb on to the roof of the carpark and hang out there. A few people noticed, and security was called, and this is where things always get interesting. No one is super excited when security exacts justice, but it is always satisfying to see people doing the wrong thing get apprehended. Kinda like seeing someone get pulled over — no one wants to see someone's shitty driving go un-punished, but no one wants to see the police win either.</li><li>Speaking of, a vacancy came up on a window seat in the co-working space and so guess who has two-thumbs and a view? This guy! The view is of the neighbouring carpark and the pub on the ground floor, so nothing fancy, but I should probably start keeping tabs on how many people are solo-drinking-and-working on a Friday afternoon, because the numbers will shock you!</li><li>Anyone following along from last week will be pleased to know that Dishy lives on, still :)</li><li>My iPhone, watch and MacBook batteries have been toast this week — I have no idea why. Nothing is coming up in the battery page with high usage. Though, I did start using the photos wallpaper on my watch to get some pics of the family through the day, so I wonder if there is a runaway process somewhere trying to pick photos for the pool? But that wouldn't make sense as to why it is all the devices.</li><li>I have been reading The City and Its Uncertain Walls after finishing Hardboiled Wonderland and the End of the World the other week. I think I prefer this telling of the story. While I did enjoy the sci-fi / cyberpunk vibe of the first version, it didn't really add all that much (other than ground the story in some reality that makes it believable that the protagonist's abilities are legit). I'm only 10 or so chapters in, and the Town part of the story is set in is already feeling more likeable.</li><li>It was a big library book week this week, with 2 reservations coming in. The City and its Uncertain Walls was one, and Caliban's War (no. 2 in The Expanse series) dropped. So please, send me all your free time.</li><li>I also added a couple of features to <a href="https://grouse.site">https://grouse.site</a> that make uploading multiple images and creating posts from uploaded images easier. I asked Zed's AI thing to help, because I didn't have time to read the Alpine docs and surprisingly it got it working in 1 go! The code was terrible, so I read the docs for the thing it suggested and made it nicer, but still, impressive to get it going with very little effort!</li></ul>
]]>
      </description>
      <link>https://dnitza.com/posts/week-83-window-seat</link>
      <guid isPermaLink="true">https://dnitza.com/posts/week-83-window-seat</guid>
      <pubDate>Sun, 01 Jun 2025 19:27:35 +0000</pubDate>
    </item>
    <item>
      <title>Week 82 — The many lives of Dishy</title>
      <description>
        <![CDATA[<p></p><ul><li>No public holidays or long weekends this week ;)</li><li>My dishwasher kicked the bucket over the previous weekend. This meany washing dishes by hand and trying everything under the sun to bring “Dishy” (as it’s known) back to life. The error code I got from the machine was super unhelpful — it could have been one of; the hose is too high, too low, or has a kink in it. After several attempts at trying to get the hose at the right level and several runs that sounded promising, Dishy was still in the dishwasher afterlife. At this point, I was so close to calling a plumber — but maybe just a few more tries. Yesterday morning I sent some vinegar and bicarbonate down the drain and wiggled the drain pipe furiously and much to my surprise, I heard the drain-gurgle or life! Dishy lives! And now think of all the things I can get it to wash!</li><li>Finished Split Fiction last night. What a joyfully imaginative game that was. The platforming was challenging but doable, the boss fights were more a test of memory than skill, and the story was not there to be taken seriously. Each vignette of the main game was really well thought out — none of them felt like the same thing just re-skinned as sci-fi or fantasy. Even the side quests were super fun and playful. But the ending sequence was the icing on top. Both a technical work of art and a delightful time. 100% would recommend especially if playing local co-op.</li><li>Speaking of finishing things, I finished reading Hardboiled Wonderland and the End Of The World this week. Only my 2nd book by Haruki Murakami, but two things stand out. His prose is so easy to read, and (maybe this ship has sailed) he needs an editor that knows how to write female characters. In general, it was a really fun read. Many authors have written dream sequences that are hammed up to feel like a dream, but the End Of The World sections of the book put them all to shame. It’s such a natural an effortless dream-like world. That said, once I kinda knew where things were going, this was the section I skimmed through to get back to the action and exposition in Hardboiled Wonderland. All in all the story was a fun exploration of the conscious and subconscious mind, asking some interesting questions and not leaving everything tied off neatly. I might have to revisit this one again in a few years.</li></ul><div><br></div><p></p>
]]>
      </description>
      <link>https://dnitza.com/posts/week-82-the-many-lives-of-dishy</link>
      <guid isPermaLink="true">https://dnitza.com/posts/week-82-the-many-lives-of-dishy</guid>
      <pubDate>Sun, 25 May 2025 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Bonsais at the Arboretum</title>
      <description>
        <![CDATA[<div><img src='https://nitza.grouse.site/images/grouse-blog-1/18/05/2025/8aad7a74-bcd3-4b29-a52b-72dad9430506/.jpeg'/><img src='https://nitza.grouse.site/images/grouse-blog-1/18/05/2025/11913d6e-c7a1-43e5-bb6f-ba2a85c3a3b7/.jpeg'/><img src='https://nitza.grouse.site/images/grouse-blog-1/18/05/2025/68f59dc2-a953-4b3e-b897-ad7b438e45fe/.jpeg'/><img src='https://nitza.grouse.site/images/grouse-blog-1/18/05/2025/94bd9d3a-bf1e-4003-b457-3993db9338f4/.jpeg'/><img src='https://nitza.grouse.site/images/grouse-blog-1/18/05/2025/b6fedf5b-2f14-4aeb-a70c-d47372901577/.jpeg'/><img src='https://nitza.grouse.site/images/grouse-blog-1/18/05/2025/87e167b5-2cef-4cce-b941-9c13d55b031d/.jpeg'/><img src='https://nitza.grouse.site/images/grouse-blog-1/18/05/2025/d48ba983-0223-455c-b7aa-d4876f22dc5e/.jpeg'/><img src='https://nitza.grouse.site/images/grouse-blog-1/18/05/2025/3e426fe1-e072-473b-a808-31cdee95c5a2/.jpeg'/> <div>Some of the cutest trees I’ve ever seen!</div>
</div>]]>
      </description>
      <link>https://dnitza.com/posts/bonsais-at-the-arboretum</link>
      <guid isPermaLink="true">https://dnitza.com/posts/bonsais-at-the-arboretum</guid>
      <pubDate>Sun, 18 May 2025 21:23:21 +0000</pubDate>
    </item>
    <item>
      <title>A day at the zoo</title>
      <description>
        <![CDATA[<div><figure data-trix-attachment="{&quot;contentType&quot;:&quot;image/png&quot;,&quot;filename&quot;:&quot;caption&quot;,&quot;height&quot;:4032,&quot;url&quot;:&quot;https://nitza.grouse.site/images/grouse-blog-1/17/05/2025/6fd48d88-570a-4d9d-bda8-510c6d8858a9/.jpeg&quot;,&quot;width&quot;:3024}" data-trix-content-type="image/png" data-trix-attributes="{&quot;caption&quot;:&quot;A giraffe&quot;,&quot;presentation&quot;:&quot;gallery&quot;}" class="attachment attachment--preview"><img src="https://nitza.grouse.site/images/grouse-blog-1/17/05/2025/6fd48d88-570a-4d9d-bda8-510c6d8858a9/.jpeg" width="3024" height="4032"><figcaption class="attachment__caption attachment__caption--edited">A giraffe</figcaption></figure><br><br><figure data-trix-attachment="{&quot;contentType&quot;:&quot;image/png&quot;,&quot;filename&quot;:&quot;caption&quot;,&quot;height&quot;:4032,&quot;url&quot;:&quot;https://nitza.grouse.site/images/grouse-blog-1/17/05/2025/5fe3e7a1-1847-4bbb-87da-747194f8159c/.jpeg&quot;,&quot;width&quot;:3024}" data-trix-content-type="image/png" data-trix-attributes="{&quot;caption&quot;:&quot;A meerkat digging&quot;,&quot;presentation&quot;:&quot;gallery&quot;}" class="attachment attachment--preview"><img src="https://nitza.grouse.site/images/grouse-blog-1/17/05/2025/5fe3e7a1-1847-4bbb-87da-747194f8159c/.jpeg" width="3024" height="4032"><figcaption class="attachment__caption attachment__caption--edited">A meerkat digging</figcaption></figure><br><br><figure data-trix-attachment="{&quot;contentType&quot;:&quot;image/png&quot;,&quot;filename&quot;:&quot;caption&quot;,&quot;height&quot;:4032,&quot;url&quot;:&quot;https://nitza.grouse.site/images/grouse-blog-1/17/05/2025/6d9514eb-0be0-4e1e-b699-1ef5fc15166e/.jpeg&quot;,&quot;width&quot;:3024}" data-trix-content-type="image/png" data-trix-attributes="{&quot;caption&quot;:&quot;A meerkat pancaking in the sun&quot;,&quot;presentation&quot;:&quot;gallery&quot;}" class="attachment attachment--preview"><img src="https://nitza.grouse.site/images/grouse-blog-1/17/05/2025/6d9514eb-0be0-4e1e-b699-1ef5fc15166e/.jpeg" width="3024" height="4032"><figcaption class="attachment__caption attachment__caption--edited">A meerkat pancaking in the sun</figcaption></figure><br><figure data-trix-attachment="{&quot;contentType&quot;:&quot;image/png&quot;,&quot;filename&quot;:&quot;caption&quot;,&quot;height&quot;:3113,&quot;url&quot;:&quot;https://nitza.grouse.site/images/grouse-blog-1/17/05/2025/7d3d69e5-6a6a-4daa-ab51-77917285a6c8/.jpeg&quot;,&quot;width&quot;:2335}" data-trix-content-type="image/png" data-trix-attributes="{&quot;caption&quot;:&quot;A meerkat on guard duty&quot;,&quot;presentation&quot;:&quot;gallery&quot;}" class="attachment attachment--preview"><img src="https://nitza.grouse.site/images/grouse-blog-1/17/05/2025/7d3d69e5-6a6a-4daa-ab51-77917285a6c8/.jpeg" width="2335" height="3113"><figcaption class="attachment__caption attachment__caption--edited">A meerkat on guard duty</figcaption></figure><br><br></div>
]]>
      </description>
      <link>https://dnitza.com/posts/a-day-at-the-zoo</link>
      <guid isPermaLink="true">https://dnitza.com/posts/a-day-at-the-zoo</guid>
      <pubDate>Sat, 17 May 2025 17:06:54 +0000</pubDate>
    </item>
    <item>
      <title>No wait, don’t tell me</title>
      <description>
        <![CDATA[<div>We’ve been catching up on <a href="https://en.m.wikipedia.org/wiki/Severance_(TV_series)"><em>Severance</em></a><em>&nbsp;</em>this week.<br><br>One of the things that I have been enjoying about season 2 is that there are just some out there episodes. It feels like any time we leave the confines of Lumon, we’re in for a heck of a romp.<br><br>Episodes 7 and 8 of season 2 were a great example of this. We saw some pretty effecting vignettes of Mark and Gemma’s lives pre-severance and Ms. Cobel digging through her family history. Throughout these ~2 hours of TV I found myself reflecting that fewer episodes were alike, and that a lot of the fun in the experience is speculating what everything means. I am of course happy to get an answer at some point (though these things rarely live up to our own hype), but just imagining the many worlds that could exist has increased my enjoyment of this particular show significantly!<br><br>Much like Twin Peaks, I hope we don’t get any definitive answers, and that a vibrant corpus of speculative fiction springs up with everyone’s view of the world.</div>
]]>
      </description>
      <link>https://dnitza.com/posts/no-wait-dont-tell-me</link>
      <guid isPermaLink="true">https://dnitza.com/posts/no-wait-dont-tell-me</guid>
      <pubDate>Tue, 25 Mar 2025 17:27:42 +0000</pubDate>
    </item>
    <item>
      <title>reduce, each_with_object, performance and you</title>
      <description>
        <![CDATA[<div>I was debugging an oddly slow bit of code today at work. The code in question was fine for smaller collections but <em>egregiously</em> slow as soon as the collection was even medium sized.<br><br>After a bit of digging, I was able to pin down one of the bits of code (at least) that was contributing to our performance issues.<br><br>It looked like this:</div><pre>class MyThing
  def call
    data = list.reduce({}) { |data, option|
      data.merge(option.id.to_s =&gt; option.value)
    }
  end
end</pre><div>Which, on the surface looks pretty harmless, but after some more digging and some measuring, it looks like `reduce`&nbsp; allocates a new object (and then some) during <em>each</em> iteration!<br><br>Let's take a look:</div><pre>def measure
  start = Time.now
  x = GC.stat :total_allocated_objects
  yield
  end_time = Time.now
  [GC.stat(:total_allocated_objects) - x, end_time - start]
end

class MyTest
  def call
    big_list = 20_000.times.map do
      OpenStruct.new(id: Random.rand(10_000), value: "Option #{Random.rand(10_000)}")
    end

    measure {
      data = big_list.reduce({}) { |data, option|
        data.merge(option.id =&gt; option.value)
      }
    }
  end
end
# =&gt; [60,004, 1.134663]</pre><div>Watch as we turn 20,000 objects in to 60,004!!<br><br><a href="https://apidock.com/ruby/Enumerable/reduce">Looking at the source for reduce / inject</a> we can see that towards the bottom, we're allocating once to keep track of the current iteration, and potentially again when rb_block_call is called (if the block would allocate, which it does since Hash#merge will return a new copy of the hash).&nbsp;<br><br>Compared to `each_with_object` which&nbsp; mutates the <em>same</em> object:</div><pre>class MyTest
  def call
    big_list = 20_000.times.map do
      OpenStruct.new(id: Random.rand(10_000), value: "Option #{Random.rand(10_000)}")
    end

    measure {
      data = big_list.each_with_object({}) { |option, data|
        data[option.id] = option.value
      }
    }
  end
end

# =&gt; [3, 0.029746]</pre><div>Here we only allocate <em>3</em> objects and are done <strong>~50 times faster</strong> than when we used `reduce`.<br><br>So, given we're not trying to transform our list in to a single value, but rather map our list in to a different shape, `each_with_object` is definitely the way to go both from a semantic point of view, but also for performance.</div>
]]>
      </description>
      <link>https://dnitza.com/posts/reduce-eachwithobject-performance-and-you</link>
      <guid isPermaLink="true">https://dnitza.com/posts/reduce-eachwithobject-performance-and-you</guid>
      <pubDate>Thu, 13 Feb 2025 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>2024 — Year in review</title>
      <description>
        <![CDATA[<div>Another year in the books, another chance to look back at the highlights and reflect!</div><h3>Projects</h3><div>I've been working on a handful of projects over the course of the year:</div><ul><li>Since March, we've been renovating our new house. In the early days this took the form of me going in and removing many of the fixtures that we didn't want anymore (<em>carpets</em>, wardrobes, cabinets). But then quickly grew in scope as we discovered things like lead paint in the walls and no insulation in <em>anything!</em> So as most projects go, we estimated a few months™ to get it all done, but we are just now getting the framing for the new bits in (and it looks amazing).<figure data-trix-attachment="{&quot;contentType&quot;:&quot;image&quot;,&quot;height&quot;:1024,&quot;url&quot;:&quot;https://grouse.hel1.your-objectstorage.com/grouse-blog-1/28/12/2024/cfdfda79-a5aa-4aa1-9b88-836d16d5cac8.jpeg&quot;,&quot;width&quot;:768}" data-trix-content-type="image" class="attachment attachment--preview"><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/28/12/2024/cfdfda79-a5aa-4aa1-9b88-836d16d5cac8.jpeg" width="768" height="1024"><figcaption class="attachment__caption"></figcaption></figure><figure data-trix-attachment="{&quot;contentType&quot;:&quot;image&quot;,&quot;height&quot;:1024,&quot;url&quot;:&quot;https://grouse.hel1.your-objectstorage.com/grouse-blog-1/29/12/2024/6f698e5f-3d38-48b5-942a-acbda4460762.jpg&quot;,&quot;width&quot;:768}" data-trix-content-type="image" class="attachment attachment--preview"><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/29/12/2024/6f698e5f-3d38-48b5-942a-acbda4460762.jpg" width="768" height="1024"><figcaption class="attachment__caption"></figcaption></figure></li><li>I also built a small (now retired) Rails project to watch any RSS feed and publish new entries to anywhere else around the web (like Mastodon, Bluesky, as a webhook, or as a HTTP call). I mainly wanted to see what Rails was like in 2024 — I'll save you the project, it's still hard to break free of the Rails Way, but things like Stimulus and Turbo are nice additions.</li><li>To help my partner stay on top of her ever-growing sewing pattern collection, I built her <a href="https://apps.apple.com/au/app/patternmate/id6464105499">PatternMate</a>. It really only does the things she wants and works how she does with pattern organisation, but we figured there's likely at least one other person out there that works the same, so it might be of benefit to release it — and we avoid the 90 day ticking clock of internal TestFlight releases!</li><li>Lastly, over the last few weeks, I have been rebuilding the backend of my blog (the one you're reading now) with the plans of making it available to the public as <a href="https://grouse.site">grouse.site</a>! Thankfully, Hanami 2 has made this super easy. I can just pull out the <a href="https://github.com/dNitza/adamantium?tab=readme-ov-file#the-micropub-slice">core slice</a> and build an admin UI and any public facing template bits around it! The main thrust around keeping the site compatible with the micropub spec, is that I really like writing in markdown for a number of reasons. I have a local backup, and I can use whichever native writing tool that I feel like. But I still wanted a stellar admin UI to be able to make smaller edits, or write shorter-form posts. Anyway, there will be a more detailed post on this in the future, so stay tuned!</li></ul><div><br></div><h3>Media</h3><div>2024 has been a good year for music!<br><br></div><div><a href="https://kinggizzardandthelizardwizard.com">King Gizzard and the Lizard Wizard</a> released <em>another</em> new album, and also live streamed a bunch of shows on their North America tour. Not all of them were recorded, <a href="https://www.youtube.com/watch?v=D9nb90noRVY">but this one was an absolute banger</a> — if you have a spare 3 hours.</div><div>"Tyler, The Creator", "The Cure", "Jack White" and "Jamie xx" have all released amazing albums that have remained in my heavy rotation this year.</div><div>A new Diablo IV expansion released, which was fun to play through, but became repetitive and not super rewarding once the end-game began.<br><br></div><div><a href="https://www.riseofthegoldenidol.com">The Rise of Golden Idol</a> is probably my game of the year. I really enjoyed the first one, and it turns out that deductive problem solving is kinda my jam, so more of that is always welcome!<br><br></div><div>Of the films I released this year, a couple stood out:&nbsp;<br><br></div><ul><li><a href="https://letterboxd.com/film/dune-part-two/">Dune: Part Two</a> — Who doesn't love a good space opera? </li><li><a href="https://letterboxd.com/film/the-boy-and-the-heron/">The Boy and the Heron</a> — The latest (and last?) from Hayao Miyazaki. This has all the trappings of a Ghibli film — ✅ World war, ✅ quaint rural setting, ✅ sick / dead mother, ✅✅✅ a heartwarming story that is beautifully animated and told. </li><li><a href="https://letterboxd.com/film/the-iron-claw-2023/">The Iron Claw</a> — technically a 2023 release, but we only got around to watching it a few months ago, and it needs to be called out. You'll laugh, you'll cry, it'll change your life.</li></ul><div><br></div><h3>Work</h3><div>This year I spent the first 6-8 months as a Team Lead (Engineering Manager in the common parlance), leading a team to stream (in as close to realtime as we can manage) the core bits of data that our product offers to other parts of the org. Up until now, things had been largely trapped in a monolith's datastore, meaning people had to come to us and connect to our database to get the things they wanted. This made upgrades and schema changes tricky. But with a shiny new data product in play, we can now start the work transitioning other teams in to the new world and free up our poor database!&nbsp;</div><div>During those 6-8 months, I realised that Team Leading wasn't entirely for me (I am squarely pointing the finger at Jira for making this an absolutely soul sucking experience). I love managing and supporting the people in my team and helping to set the direction we take to tackle the problems at hand, but making sure that Jira <em>always</em> reflects the current state of play was taking away my will to live. So I made the move back to an individual contributor role (whatever that means) — and I still mentor and manage the people on my team, but have someone else doing the Jira wrangling. This also means I get a decent number of hours each week to also get on the tools and write code again :D</div><div><br></div><h3>Family</h3><div><br>A massive year for our little family this year! We grew from 2 humans and 2 dogs, to <em>3 humans</em> and 2 dogs. We finally outnumber them!</div><div><br>Late in October, we welcomed Sebastian in to our lives, and he has been a napping, smiling, farting pile of joy since then! The ~3am wake-ups are all worth it when you get to see this mug staring back at you.<figure data-trix-attachment="{&quot;contentType&quot;:&quot;image&quot;,&quot;height&quot;:1024,&quot;url&quot;:&quot;https://grouse.hel1.your-objectstorage.com/grouse-blog-1/29/12/2024/57c7f983-ce02-4c03-8310-ff72141f3088.jpg&quot;,&quot;width&quot;:768}" data-trix-content-type="image" class="attachment attachment--preview"><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/29/12/2024/57c7f983-ce02-4c03-8310-ff72141f3088.jpg" width="768" height="1024"><figcaption class="attachment__caption"></figcaption></figure> I had initially planned to take leave from his due date (of 21 November) until Jan 6 '25, but due to <em>complications</em> and his early arrival, I have been off since October 29, which has been amazing. After 4 weeks, I was struggling to see how I would have gone back to work <em>and</em> been available to support Abby as we work things out (without dying of lack of sleep). So I'm glad that I did get ~2 months of leave to be able to really help set a foundation for our routines in his first few months. Also, it's been a joy watching him grow every day!</div><div><br></div><h3>Misc</h3><div><br>I wrote a while ago <a href="https://dnitza.com/posts/switching-up-some-of-the-tools-i-use-daily">about some of the tools I have been trying out this year</a>. Some of those have stuck, other have not.</div><div><br>I have gone back to Safari, because I just can't live without iCloud tabs.</div><div><br>I have also been trying out <a href="https://wezfurlong.org/wezterm/">Wezterm</a> over Alacritty, but now that <a href="http://ghostty.org/">Ghostty</a> has hit 1.0, I'll be giving that a whirl!</div><div><br><a href="https://zed.dev">Zed</a> has remained as my editor of choice, especially now that the ruby-lsp works really well.</div><div><br>My main computer is now just my M3 Max MacBook Pro — I haven't needed to turn on my gaming PC in over a year! The GPU in this thing has more than enough power for the games I am looking to play.<br><br>I've been really enjoying the workouts that <a href="https://fitbod.me">Fitbod</a> puts together. That in combination with some of Apple's cardio workouts has been keeping me active (aside from the dog walks) while we pack up our old place and head to a place closer to a gym!</div>
]]>
      </description>
      <link>https://dnitza.com/posts/2024-year-in-review</link>
      <guid isPermaLink="true">https://dnitza.com/posts/2024-year-in-review</guid>
      <pubDate>Sun, 29 Dec 2024 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Advent of Code '24 — Day 2</title>
      <description>
        <![CDATA[<p>Day two! And things are already tricksy!</p>

<p>Today&#39;s puzzle was about deciphering reports, to determine if each level in the report is deemed safe.</p>

<p>A safe &quot;level&quot; is on that each number in the row increases or decreases only, and only by a maximum of 3.</p>

<p><a href="https://adventofcode.com/2024/day/2">Link to puzzle</a></p>

<h2>Part 1</h2>

<p>In the first part, we needed to work out how many levels were safe.</p>

<p>E.g. given:</p>

<pre><code>7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9
</code></pre>

<p>Only the first and last reports are safe as they&#39;re the only ones that follow the 2 rules.</p>

<p>A simple enough solution for this one:</p>

<pre><code>def part1(input)
    input.map do |line|
      dir = nil
      vals = line.split(&quot; &quot;).each_cons(2).map do |first, second|
        next_dir = if Integer(first) &gt; Integer(second)
          :increase
        elsif Integer(first) &lt; Integer(second)
          :decrease
        else
          :no_change
        end
        dir = next_dir if dir.nil?
        (Integer(first) - Integer(second)).abs &gt; 3 || dir != next_dir
      end

      vals.all?(false) ? 1 : 0
    end.sum
  end
</code></pre>

<p>First, mapping over each line, checking items in pairs of 2 and checking if we&#39;re constantly increasing or decreasing, or if we&#39;re stepping in increments greater than 3.</p>

<p>⭐️ Success! Albeit a little verbose!</p>

<h2>Part 2</h2>

<p>The second part was tricky!</p>

<p>My solution to the first part wasn&#39;t really flexible enough to accommodate the second part, so a rewrite was required.</p>

<p>In part 2, we learned that some levels can be made safe, by <em>removing</em> an entry in the level. I totally misread this, and thought that only otherwise <em>unsafe</em> entries could be removed. So made a number of wrong entries before realising that <em>any</em> entry could be removed to make the level safe.</p>

<p>So here&#39;s my brute-force attempt after banging my head against it for a bit:</p>

<pre><code>def part2(input)
  input.count { |levels|
    levels = levels.split(&quot; &quot;)
    safe?(levels) || safe_by_removing_level?(levels)
  }
end

def safe?(report_result)
  res = report_result.each_cons(2).map do |first, second|
    if Integer(first) &lt; Integer(second) &amp;&amp; Integer(second) - Integer(first) &lt;= 3
      :increase
    elsif Integer(first) &gt; Integer(second) &amp;&amp; Integer(first) - Integer(second) &lt;= 3
      :decrease
    else
      :bad_step
    end
  end

  res.all? { |x| x == :increase } || res.all? { |x| x == :decrease }
end

def safe_by_removing_level?(report)
  0.upto(report.size).find do |i|
    sub = report.dup
    sub.delete_at(i)
    safe?(sub)
  end
end
</code></pre>

<p>This time, we still check that every level is safe, but if it&#39;s not, we then remove one entry from the level at a time to see if it <em>would</em> be safe.</p>

<p>⭐️ Success!</p>

<h2>Performance</h2>

<p>Even though this was brute-forced, it still performed OK. Though each level only had ~6 entries to loop through, so not a huge set of data to work with.</p>

<pre><code>day 02
├─ part 1 — 0.002105s
╰─ part 2 — 0.008360s
</code></pre>

<p>Day two: https://github.com/dNitza/advent-of-code/tree/main/2024/02</p>
]]>
      </description>
      <link>https://dnitza.com/posts/advent-of-code-24-day-2</link>
      <guid isPermaLink="true">https://dnitza.com/posts/advent-of-code-24-day-2</guid>
      <pubDate>Mon, 02 Dec 2024 19:35:17 +0000</pubDate>
    </item>
    <item>
      <title>Advent of Code '24 — Day 1</title>
      <description>
        <![CDATA[<div>We're back again with <a href="https://adventofcode.com">Advent of Code</a> — one of my favourite times of year. Partly for the puzzles, and partly because it's a sign the year is winding up. Like <a href="https://24ways.org">24 ways to impress your friends</a> and <a href="https://www.uxmas.com">UXmas</a> before it, Advent of Code is an advent calendar, where each day is a pair of code challenges. There's usually some kind of narrative that ties the puzzles together, which ends up adding a fun christmas-y spin to the whole thing.</div><div>Usually, I try and do it in a programming language I am not super familiar with, but this year I'm going to stick to Ruby ~~(mainly because I am out of ideas for languages to try 😆)~~ (mainly because we have a newborn on our hands and my brain power is limited!).</div><div><a href="https://adventofcode.com/2024/day/1">Link to puzzle</a><br><br></div><div><strong>Part 1</strong></div><div>The first part was nice and simple. The challenge was to arrange two lists of numbers, find the difference between lowest number in each list, then the next lowest, then the next lowest (and so on). Then sum the differences between them to get your answer</div><div>E.g. given:</div><pre>3 4
4 3
2 5
1 3
3 9
3 3</pre><div><br></div><div>The smallest number on the left list is 1, and on the right it is 3. Then 2 and 3 and so on — eventually giving <strong>11</strong></div><div>My solution was as follows</div><pre>list = {left: [], right: []}
    input
      .map { |line| line.split(" ") }
      .each_with_object(list) { |item, memo|
        memo[:left] &lt;&lt; Integer(item[0])
        memo[:right] &lt;&lt; Integer(item[1])
      }
    sorted_left = list[:left].sort
    sorted_right = list[:right].sort

    sorted_left.each_with_index.map do |left, idx|
      [left, sorted_right[idx]].sort.reverse.inject(:-)
    end.inject(:+)
lang-ruby</pre><div>First creating the left and right lists as arrays, then sorting them, and then finally iterating over the left list to find the corresponding lowest number in the right list and doing the math™.</div><div>⭐️ Success!<br><br></div><div><strong>Part 2</strong></div><div>The second part was just as simple.</div><div>We needed to find a "similarity score" between the two lists. This was done by multiplying each number in the <strong>left</strong> list, by the number of times it appears in the <strong>right</strong> list. Then summing all of those numbers.</div><div>Thankfully, Ruby's <strong>tally</strong> makes it easy to build a histogram given some array of input.</div><div>Here's my solution for part 2:</div><pre>list = {left: [], right: []}
    input
      .map { |line| line.split(" ") }
      .each_with_object(list) { |item, memo|
        memo[:left] &lt;&lt; Integer(item[0])
        memo[:right] &lt;&lt; Integer(item[1])
      }

right_frequency = list[:right].tally

list[:left].map do |left|
      (right_frequency[left] || 0) * left
    end.inject(:+)
lang-ruby</pre><div>Building up a list again as step one, then calculating the frequency of each number in step 2.</div><div>Before finally doing the math across each number in the left list.</div><div>⭐️ Success!<br><br></div><div><strong>Performance</strong></div><div>Pretty quick for a simple puzzle — though I can see some optimisations in my solution (like not needing the initial hash of array), but I'll save that golfing for future puzzles that are bound to take minutes or hours ;)</div><pre>day 01
├─ part 1 — 0.000542s
╰─ part 2 — 0.000324s</pre><div><br></div><div>Day one: <a href="https://github.com/dNitza/advent-of-code/tree/main/2024/01">https://github.com/dNitza/advent-of-code/tree/main/2024/01</a></div>
]]>
      </description>
      <link>https://dnitza.com/posts/advent-of-code-24-day-1</link>
      <guid isPermaLink="true">https://dnitza.com/posts/advent-of-code-24-day-1</guid>
      <pubDate>Sun, 01 Dec 2024 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>The majesty of Elden Ring</title>
      <description>
        <![CDATA[<ul>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/230260e3-3070-4af5-99b4-8710c1e29965.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/ffac3796-b33d-4fa0-9a93-bc1a2b1b12be.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/16bae942-0100-411e-9e94-7218be11f419.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/5ff65944-789a-4e53-8e70-1d105080cbda.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/7388a4d1-35f0-4ce9-9bc2-b45991970de2.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/1501282f-b8fe-459c-98c2-6e0a628c654c.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/d837109a-e7fe-4142-badb-439c5b7ff494.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/21399786-d501-4010-acb1-84c059f2f4d3.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/6777216f-8113-476d-a67d-5e30d253eed1.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/df757d15-4b58-468a-9352-ad87c35de1e5.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/974a09eb-2914-497f-aafa-ec335b893f95.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/6054b5b6-1265-4b7f-9356-6f01fd50e4a8.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/391b891a-da1e-455a-90a4-4195184ad90a.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/a5c3b182-d637-4cbb-83ac-1a567c6073e8.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/1754da87-5b00-4cb1-ab6f-561b92ea95ee.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/7bf0c0e9-d9ca-4a49-8443-6d396fd368f8.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/e56abb8a-7571-42fb-bf1e-fbf0dd88941e.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/eca22c98-0b70-4753-b68b-f53bdafcd3d9.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/89e69617-f0b2-46a6-8c9c-6b85dd24cb40.jpeg" alt=""></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/51b1cbba-79f0-4f5f-ad14-f558875836a9.jpeg" alt=""></li>
</ul>
]]>
      </description>
      <link>https://dnitza.com/posts/the-majesty-of-elden-ring</link>
      <guid isPermaLink="true">https://dnitza.com/posts/the-majesty-of-elden-ring</guid>
      <pubDate>Mon, 24 Jun 2024 18:38:39 +0000</pubDate>
    </item>
    <item>
      <title>debug_postgres_index.md</title>
      <description>
        <![CDATA[<h3>Debugging index concurrent creation in Postgres.</h3>

<p>Check if an index is still being created.</p>

<p>The following should return something if an index is still being created on another process.</p>

<pre><code>SELECT 
  a.datname, 
  l.relation::regclass, 
  l.transactionid, 
  l.mode, 
  l.GRANTED, 
  a.usename, 
  a.query, 
  a.query_start, 
  age(now(), a.query_start) AS &quot;age&quot;, 
  a.pid 
FROM pg_stat_activity a 
JOIN pg_locks l 
ON l.pid = a.pid
WHERE mode = &#39;ShareUpdateExclusiveLock&#39; 
ORDER BY a.query_start
</code></pre>

<p>If the result is empty (and the index still isn&#39;t there), then it&#39;s likely that the index creation failed.</p>

<p>Check for invalid indexes in pg_index</p>

<pre><code>SELECT * 
FROM pg_class, pg_index 
WHERE pg_index.indisvalid = false 
  AND pg_index.indexrelid = pg_class.oid;
</code></pre>

<p>This might return something like:</p>

<pre><code>| oid | relname | relnamespace | .... | 
|-----|---------|--------------|------| 
| 123 | my_index| 1234 | .... |
</code></pre>

<p>In this case, you can tell postgres to reindex this index to make it valid:</p>

<p><code>REINDEX INDEX CONCURRENTLY my_index;</code></p>

<p>Docs: https://www.postgresql.org/docs/current/sql-reindex.html</p>
]]>
      </description>
      <link>https://dnitza.com/posts/debug_postgres_indexmd</link>
      <guid isPermaLink="true">https://dnitza.com/posts/debug_postgres_indexmd</guid>
      <pubDate>Wed, 19 Jun 2024 09:25:55 +0000</pubDate>
    </item>
    <item>
      <title>On "Turning the Tables on AI"</title>
      <description>
        <![CDATA[<p>Link: <a href="https://ia.net/topics/turning-the-tables-on-ai">https://ia.net/topics/turning-the-tables-on-ai</a></p>

<p>Some solid takes on the common use-cases for AI &quot;stuff&quot;. It&#39;s refreshing to see some well thought out use cases for generative AI that don&#39;t result in lifting the output and passing it off as ones own.</p>

<p>More of an AI mentor or colleague than an assistant.</p>

<p>Take aways —</p>

<ol>
<li>Wouldn&#39;t it be awesome if AI could use its context to ask leading questions to help us think more deeply about something?</li>
<li>If AI does generate something (especially text) that is useful or good, cite it as such. </li>
<li>Ask AI to be the critic of your writing that humans can sometimes shy away from — but then challenge what it suggests.</li>
</ol>

<p>Critical thinking can be a practice that is easily lost without regular use. Offloading it to AI would be a terrible way to give up the opportunity to grow and reflect.</p>
]]>
      </description>
      <link>https://dnitza.com/posts/on-turning-the-tables-on-ai</link>
      <guid isPermaLink="true">https://dnitza.com/posts/on-turning-the-tables-on-ai</guid>
      <pubDate>Mon, 17 Jun 2024 19:25:35 +0000</pubDate>
    </item>
    <item>
      <title>Best, First, Favourite — Alexisonfire</title>
      <description>
        <![CDATA[<p>Inspired by <a href="https://www.relay.fm/rd/221">this episode</a> of <em>Reconcilable Differences</em> where John and Merlin introduce the concept of &quot;Best, First, Favourite&quot;, I thought I&#39;d do a version for <a href="https://theonlybandever.com/">the only band ever</a></p>

<p><strong>Best</strong> : <a href="https://music.apple.com/au/album/boiled-frogs/1367728960?i=1367729808">Boiled Frogs</a></p>

<p><strong>First</strong> : <a href="https://music.apple.com/au/album/accidents/1367684173?i=1367684181">Accidents</a></p>

<p><strong>Favourite</strong> : <a href="https://music.apple.com/au/album/happiness-by-the-kilowatt/1367684173?i=1367685083">Happiness by the Kilowatt</a> — inspired by the <a href="https://archive.org/details/kurt-vonnegutthe-euphio-question">Euphio Question</a> by Kurt Vonnegut, this song is an absolute journey. Best experienced live, but a <a href="https://youtu.be/khnAAX5sybw?feature=shared">live recording is also solid</a>.</p>
]]>
      </description>
      <link>https://dnitza.com/posts/best-first-favourite-alexisonfire</link>
      <guid isPermaLink="true">https://dnitza.com/posts/best-first-favourite-alexisonfire</guid>
      <pubDate>Fri, 07 Jun 2024 18:09:03 +0000</pubDate>
    </item>
    <item>
      <title>Switching up some of the tools I use daily</title>
      <description>
        <![CDATA[<p>I&#39;ve been trying to shake things up a bit in terms of my go-to tools in my work / computer life. Mainly out of trying to understand what else is out there, but also to see if I can bring back any ideas to the tools I use daily from other places.</p>

<h4>Instead of Safari</h4>

<p>I have been trying <a href="https://arc.net">Arc</a> out. I <em>love</em> the sense of style it has. You can paint the darn thing any colour you want and it&#39;s just so much fun! Which is an odd thing to praise a browser for, but so much of our software has lost its whimsy in an effort to appeal to as many people as possible. There are some other neat things, too. Like, you can split view multiple tabs directly in the browser. I&#39;ve found this useful for reading the orange website, and opening comments on posts next to the post itself. The <em>last</em> thing that I will call out, is the tab system. I didn&#39;t think I&#39;d dig tabs in the sidebar, but now I don&#39;t really notice them. Arc&#39;s command palette is so good that I rarely every look to the left to pick a tab, not when I can hit a shortcut and quickly type a few letters. Oh, and you can set tabs to close after a given period of time. I&#39;ve set my &quot;close tab&quot; timeout to its max, as I generally <em>do</em> close tabs I am no longer actually using and keep things open that I actually go back to.</p>

<h4>Instead of LaunchBar</h4>

<p>I&#39;ve been trying out <a href="https://www.raycast.com">Raycast</a>. I had <em>minimally</em> extended LaunchBar with my own extensions over the last, <em>checks notes</em>, 9 years! Mainly for things like opening certain apps, and creating shortcuts to search carious websites. In all cases, LaunchBar was a wrapper around an AppleScript, so thankfully, these were a cinch to port over to Raycast. So far the other things that Raycast has going for it over LaunchBar are a bunch more builtin extensions (for things like searching screenshots, and window management), as well as being able to do some handy things with numbers and conversions. Also, just before I switch to Raycast, LaunchBar was doing some weird things with its index. It would constantly want to open up a file instead of Apple Music, or take me in to an iCloud directory for an app instead of opening the app itself. I ended up disabling files and folders from the index to rectify this, but that was an annoying bandaid to have to place.</p>

<h4>Web frameworks</h4>

<p>Ok, this one&#39;s a bit of a gimme because I use Rails almost every day at work, but it has been a long time since I have started a new Rails project with all the modern trimmings. I think the last time I did that, was Rails 5? I have to say, Rails has come a long way since then. I still don&#39;t <em>love</em> the MVC pattern or ActiveRecord, and Stimulus is still a bit too magical (especially as compared to <a href="https://github.com/bensmithett/viewloader">ViewLoader</a>, <a href="https://github.com/makenosound/defo">Defo</a> or <a href="https://alpinejs.dev">Alpine</a> / <a href="https://htmx.org">HTMX</a>), but my little side project did come together <em>very</em> quickly. Though, now I want to port it to Hanami 2 😉.</p>

<h4>Instead of iTerm2</h4>

<p>I&#39;ve been trying <a href="http://alacritty.org/">Alacritty</a> out. And, well it works really nicely, it doesn&#39;t have a million and one options, but so far that&#39;s working out well for me! I really only used the theming / font settings anyway, so we&#39;ll see how continued use goes and if anything comes up as a deal breaker of a feature.</p>
]]>
      </description>
      <link>https://dnitza.com/posts/switching-up-some-of-the-tools-i-use-daily</link>
      <guid isPermaLink="true">https://dnitza.com/posts/switching-up-some-of-the-tools-i-use-daily</guid>
      <pubDate>Sat, 25 May 2024 19:01:30 +0000</pubDate>
    </item>
    <item>
      <title>RSpec bisect saves the day!</title>
      <description>
        <![CDATA[<p>Over the last few days, our test suite at work had a pretty gnarly run of test failures. We run almost all of our Ruby tests through RSpec, and group our feature specs to be run separately to our unit specs.</p>

<p>We usually expect some flake in our suite, as it&#39;s gigantic, and it&#39;s not impossible to write a test that doesn&#39;t completely isolate its state. Usually how this presents, though, is one spec failing sporadically so it&#39;s easy to know which file is the culprit and thus easy to fix the problem.</p>

<p>The last few days were different, though. Instead of the same spec failing, we saw multiple different and unrelated specs failing. Well, unrelated in that they were all in different parts of our application, but they all failed in the same way! Some string value we expected to be there, was <code>nil</code>!</p>

<p>But why? What was causing this behaviour in our specs? We also observed that sometimes, we just got lucky, and our suite passed without issue. So it was looking more definite that the order of our specs had a big part to play in the flake.</p>

<p>Enter <code>rspec --bisect</code>.</p>

<p>RSpec can be run with an additional flag, which, (when given a failing group of specs), will work out the smallest sets of specs needed to reproduce the failure. This can then be run locally, and with some educated guessing or trial and error, be used to narrow down what state may be leaking between specs.</p>

<p>If you notice flake in your CI/CD pipeline, you should be able to take the exact specs that were run and retry those with <code>--bisect</code>.</p>

<p>For example, if something like this is run on CI:<code>
bin/rspec foo_spec.rb bar_spec.rb baz_spec.rb
</code></p>

<p>It can be updated to the following to run in <code>bisect</code> mode.<code>
bin/rspec --bisect foo_spec.rb bar_spec.rb baz_spec.rb
</code></p>

<p>If successful, something like the following should be printed:<code>
bar_spec.rb[1:2] foo_spec.rb[1:1]
</code></p>

<p>If the specs above are run in the order printed, a failure should consistently present itself. The numbers in the square brackets are the index or path of the specs that are run in each file —&nbsp;so <code>[1:2]</code> would be the second spec in the first group.</p>

<p>In our case, the tests that were failing were all Rails controller specs. We also rely on some I18n magic to persist a user&#39;s preferred language between requests (once set). A new controller spec had been introduced that must have been the first to exercise this logic - or at least exercise the logic without cleaning up its state. And so, where we were expecting some strings to exist in one language, they were coming back as <code>nil</code>, because they were not present for the language being leaked between specs. 🙌🏽</p>
]]>
      </description>
      <link>https://dnitza.com/posts/rspec-bisect-saves-the-day</link>
      <guid isPermaLink="true">https://dnitza.com/posts/rspec-bisect-saves-the-day</guid>
      <pubDate>Sat, 25 May 2024 12:13:58 +0000</pubDate>
    </item>
    <item>
      <title>My Home Screen for March '24</title>
      <description>
        <![CDATA[<p>My Home Screen for March &#39;24 Shrinking down the music widget and bringing in a couple more apps — specifically Mail. I tried opening it with a swipe down &amp; search, but I have been using it more and more this last month, so it gets a spot on the Home Screen! I used to have a 4x2 Siri widget on the bottom row, maybe I&#39;ll try that next month and see if it pulls in the right apps. <img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/d7805ec8-2e60-47c4-88de-501a55f4fcdd.jpeg" alt="an iOS Home Screen"></p>
]]>
      </description>
      <link>https://dnitza.com/posts/my-home-screen-for-march-24</link>
      <guid isPermaLink="true">https://dnitza.com/posts/my-home-screen-for-march-24</guid>
      <pubDate>Fri, 22 Mar 2024 08:59:27 +0000</pubDate>
    </item>
    <item>
      <title>My Home Screen for Feb '24</title>
      <description>
        <![CDATA[<p>My Home Screen for February&#39;24</p>

<p>(Ignore the unreads — I <em>will</em> reply to them at some point!)</p>

<ul>
<li>Obsidian is a staple, notes used to be my go to, but I&#39;ve found so much more utility with Obsidian. </li>
<li>A couple of shortcuts for <a href="https://dnitza.com/post/how-i-write-this-blog-on-an-ipad">posting to this blog</a> </li>
<li>And of course Ivory and NetNewsWire! </li>
</ul>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/19351ff8-6a2f-4534-aa7c-41e869b35d01.jpeg" alt="An iPhone Home Screen"></p>
]]>
      </description>
      <link>https://dnitza.com/posts/my-home-screen-for-feb-24</link>
      <guid isPermaLink="true">https://dnitza.com/posts/my-home-screen-for-feb-24</guid>
      <pubDate>Mon, 12 Feb 2024 20:11:44 +0000</pubDate>
    </item>
    <item>
      <title>TIL: Testing actions in Hanami 2</title>
      <description>
        <![CDATA[<p>I was pairing with a colleague this afternoon at work on trying to test an action in Hanami 2 where we explicitly needed to <em>read</em> the body of the request.</p>

<p>The action looked something like this:</p>

<pre><code>class MyAction &lt; Action
    before_action :some_validation

    def handle(req, res)
        event_id = req.params[:event_id]
        ticket_id = req.params[:ticket_id]

        # do things with params here
    end

    private

    def some_validation(req, res)
        body = req.body.read
        request_url = &quot;...&quot;
        # compare the SHA of body + some other things against a header in the request
    end
end
</code></pre>

<p>Generally, the approach I would take for testing an action like this is to create a new instance of the action, and then <code>call</code> it with some params.</p>

<pre><code>let(:subject) { MyAction.new }

it &quot;works&quot; do
    subject.call({event_id: &quot;123&quot;, ticket_id: &quot;246&quot;})
end
</code></pre>

<p>However, that doesn&#39;t exactly work when trying to <code>read</code> the body off the request.</p>

<p>When calling <code>req.body.read</code>, we get no method <code>read</code> error for <code>nil</code>.</p>

<p>To get that to work, we need to call the action with an object that loosely resembles the <code>env</code> object of a Rack request, specifically an object with a <code>rack.input</code> key. This is what Rack uses to build the <a href="https://github.com/rack/rack/blob/3897649e8740e560a5fa142f972121a119b26b5c/lib/rack/request.rb#L190">request body</a>.</p>

<p>For example:</p>

<pre><code>params = StringIO.new(JSON.generate({event_id: &quot;123&quot;, ticket_id: &quot;246&quot;}))

subject.call({&quot;rack.input&quot; =&gt; params})
</code></pre>

<p>Success! <code>req.body.read</code> is now accessible in our action under test!</p>
]]>
      </description>
      <link>https://dnitza.com/posts/til-testing-actions-in-hanami-2</link>
      <guid isPermaLink="true">https://dnitza.com/posts/til-testing-actions-in-hanami-2</guid>
      <pubDate>Mon, 22 Jan 2024 18:36:12 +0000</pubDate>
    </item>
    <item>
      <title>2023 Tunes!</title>
      <description>
        <![CDATA[<p><div>This year I tried my darndest to Scrobble more to <a href="https://last.fm">last.fm</a> like it was 2008. This was made difficult by how tricky scrobbling from modern streaming services is. If anything is to count as a scrobble, it needs to be in the your &quot;library&quot;. This means anything new usually wont be included until it makes it in to my library. Which is not the best experience, because I don&#39;t really want a library full of random things, but yay for 40m+ songs I guess.</div><div>In any case, this is what <a href="https://anhuynh.github.io/last-history/?username=dNitza">last-history</a> has in store for 2023.</div><div>Top tracks</div><div>Earlier this year I saw Alexisonfire live in Melbourne. Their performance of <em>Sans Soleil</em> must have really stuck with me because it was this year&#39;s top track.</div><ol><li>Sans Soleil by Alexisonfire — 25 Scrobbles</li><li>Sweet Dreams of Otherness by Alexisonfire — 18 Scrobbles</li><li>Following Eyes by Soccer Mommy — 18 Scrobbles</li><li>Bones by Soccer Mommy — 16 Scrobbles</li><li>Blue Spade by Alexisonfire — 15 Scrobbles</li><li>Stillness (Way Beyond) — 14 Scrobbles</li><li>Boiled Frogs by Alexisonfire — 12 Scrobbles</li><li>Conditional Love by Alexisonfire — 12 Scrobbles</li><li>Dark Night of the Soul — 12 Scrobbles</li><li>Happiness by the Kilowatt — 12 Scrobbles<figure data-trix-attachment="{&quot;contentType&quot;:&quot;image&quot;,&quot;height&quot;:469,&quot;url&quot;:&quot;https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/8ca498f1-3ed3-458a-accc-edefcbdac29d.png&quot;,&quot;width&quot;:1024}" data-trix-content-type="image" class="attachment attachment--preview"><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/8ca498f1-3ed3-458a-accc-edefcbdac29d.png" width="1024" height="469"><figcaption class="attachment__caption"></figcaption></figure></li></ol><div>Top albums</div><div>In April, I started playing <a href="https://dnitza.com/tagged/tunic">TUNIC</a>, an isometric Zelda-like from the folks at Finji studios. It was a super chill experience with some amazing puzzles and an awesome soundtrack by <a href="http://lifeformed.com">Lifeformed</a>. It&#39;s rare that I&#39;ll play a game for a decent amount of time these days since most will start to pad for time with repetitive content just for the sake of it. TUNIC was not like that at all, each puzzle was unique and left me wanting to discover more about this mystery world! I probably put it down after finishing the last puzzle in about June, but still think about it every day.</div><ol><li>TUNIC (Original Game Soundtrack) by Lifeformed — 144 Scrobbles</li><li>RARITIES by Lorn — 143 Scrobbles</li><li>Otherness by Alexisonfire — 123 Scrobbles</li><li>Sometimes, Forever by Soccer Mommy — 119 Scrobbles</li><li>Nothing Else by Lorn — 78 Scrobbles</li><li>Crisis by Alexisonfore — 66 Scrobbles</li><li>Diplomatic Immunity by Client Liaison — 64 Scrobbles</li><li>Carnavas by Silversun Pickups — 56 Scrobbles</li><li>One Step More You Die by Mono — 55 Scrobbles</li><li>Ask The Dust by Lorn — 53 Scrobbles<figure data-trix-attachment="{&quot;contentType&quot;:&quot;image&quot;,&quot;height&quot;:469,&quot;url&quot;:&quot;https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/676b5258-32e3-4fa4-bd1a-e101b7e6c063.png&quot;,&quot;width&quot;:1024}" data-trix-content-type="image" class="attachment attachment--preview"><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/676b5258-32e3-4fa4-bd1a-e101b7e6c063.png" width="1024" height="469"><figcaption class="attachment__caption"></figcaption></figure></li></ol><div>Top artists &amp; Genres</div><div>Not as exciting as the tracks and albums, but the artists that got me through 2023 are;</div><ol><li>Lorn</li><li>Alexisonfore</li><li>Silversun Pickups</li><li>Lifeformed</li><li>Soccer Mommy</li><li>The Mars Volta</li><li>King Gizzard &amp; The Lizard Wizard — this one should have been higher, but so many albums weren&#39;t in my library.</li><li>City and Colour</li><li>GUNSHIP</li><li>Wilco<figure data-trix-attachment="{&quot;contentType&quot;:&quot;image&quot;,&quot;height&quot;:469,&quot;url&quot;:&quot;https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/cbdc907c-3174-4a92-ac24-7dca4b4a69ac.png&quot;,&quot;width&quot;:1024}" data-trix-content-type="image" class="attachment attachment--preview"><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/cbdc907c-3174-4a92-ac24-7dca4b4a69ac.png" width="1024" height="469"><figcaption class="attachment__caption"></figcaption></figure></li><li>Alternative</li><li>Rock</li><li>Indie Rock</li><li>Post Hardcore</li><li>Indie<figure data-trix-attachment="{&quot;contentType&quot;:&quot;image&quot;,&quot;height&quot;:647,&quot;url&quot;:&quot;https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/5f116302-513b-4f11-b171-1a55e817d0dd.png&quot;,&quot;width&quot;:1024}" data-trix-content-type="image" class="attachment attachment--preview"><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/5f116302-513b-4f11-b171-1a55e817d0dd.png" width="1024" height="647"><figcaption class="attachment__caption"></figcaption></figure></li></ol></p>
]]>
      </description>
      <link>https://dnitza.com/posts/2023-tunes</link>
      <guid isPermaLink="true">https://dnitza.com/posts/2023-tunes</guid>
      <pubDate>Thu, 21 Dec 2023 22:13:51 +0000</pubDate>
    </item>
    <item>
      <title>Advent of Code 2023 — Day 9</title>
      <description>
        <![CDATA[<p>More camels and oasis-es, less cards.</p>

<p>In this puzzle, we&#39;re using our trusty <em>Oasis And Sand Instability Sensor</em> —&nbsp;OASIS — to analyse our surroundings.</p>

<p>The OASIS produces a history for each value, and we need to extrapolate the <em>next</em> value from the current set of values.</p>

<h2>Part 1</h2>

<p>Our input looks like this:</p>

<pre><code>0 3 6 9 12 15
1 3 6 10 15 21
10 13 16 21 30 45
</code></pre>

<p>Each row represents the history of a value, with the earliest entry on the left.</p>

<p>The way we extrapolate a value is by finding the difference between the first and second values, the second and third, and so on. This will produce <em>another</em> set of values — this set will be 1 shorter than the initial set. We then repeat the process on the new set until all values are zero.</p>

<p>This is how the above process can be visualised.</p>

<pre><code>0 3 6 9 12 15
  3 3 3 3 3
    0 0 0 0
</code></pre>

<p>Once we&#39;re at this stage, we can (starting from the array of all 0s), add the last element of the last array to the last element of the previous array until we&#39;re back at the top — the result will be the extrapolated item.</p>

<p>So in the above example, <code>0 + 3 = 3</code>, <code>3 + 15 = 18</code>, <code>18</code> is our extrapolated value.</p>

<pre><code>def part1(input)  
  extrapolated_values = input.map do |line|  
    nums = [line.split(&quot; &quot;).map(&amp;:to_i)]  

    while !nums.last.all?(&amp;:zero?) do  
      nums &lt;&lt; next_in_series(nums)  
    end  

    nums.reverse.inject(0) do |sum, numbers|  
      sum + numbers.last  
    end  
  end  
  extrapolated_values.sum  
end

def next_in_series(numbers)  
  numbers.last.map.with_index(1) do |n, i|  
    next if i+1 &gt; numbers.last.count  

    numbers.last[i] - n  
  end.compact  
end
</code></pre>

<p>Here&#39;s all my code for the above algorithm.</p>

<p>The first piece maps over our input, creates the first set of numbers <code>[[0, 3, 6, 9, 12, 15]]</code>, then, while the last set of numbers isn&#39;t all <code>0</code>, calculates the next set.</p>

<p>Then, starting from the array of <code>0s</code>, it adds the last number of each array to the next until we loop through each item and come up with our next number in the set!</p>

<p>⭐️ Success!</p>

<h2>Part 2</h2>

<p>Part 2 asked us to extrapolate in the other direction — what&#39;s the number that comes before <code>0</code> in this case?</p>

<p>Once we get that number for each row, we can again sum everything up.</p>

<p>This was almost identical to part 1, except the starting set of numbers is reversed:</p>

<p><code>nums = [line.split(&quot; &quot;).map(&amp;:to_i).reverse]</code></p>

<p>⭐️ Success!</p>

<p>Code: <a href="https://github.com/dNitza/advent-of-code/tree/main/2023/09">https://github.com/dNitza/advent-of-code/tree/main/2023/09</a></p>

<h2>Performance</h2>

<p>Pretty snappy today — I was looking to see if there were any extrapolation algorithms that could reduce the time here, but my searching didn&#39;t produce much.</p>

<pre><code>day 09
├─ part 1 — 0.003131s
╰─ part 2 — 0.00299s
</code></pre>
]]>
      </description>
      <link>https://dnitza.com/posts/advent-of-code-2023-day-9</link>
      <guid isPermaLink="true">https://dnitza.com/posts/advent-of-code-2023-day-9</guid>
      <pubDate>Wed, 13 Dec 2023 20:53:32 +0000</pubDate>
    </item>
    <item>
      <title>Advent of Code 2023 — Day 8</title>
      <description>
        <![CDATA[<p>Another fun one!</p>

<p>Today&#39;s puzzle had us navigating a <a href="https://www.youtube.com/watch?v=y6120QOlsfU">sandstorm</a>. Our map, a set of directions, through what looked like a graph.</p>

<p>Starting at <code>AAA</code>, we want to get to <code>ZZZ</code> following the <code>R</code>, then <code>L</code> item each iteration — starting from <code>R</code> again if we&#39;ve landed on a node that is not <code>ZZZ</code>.</p>

<pre><code>RL

AAA = (BBB, CCC)
BBB = (DDD, EEE)
CCC = (ZZZ, GGG)
DDD = (DDD, DDD)
EEE = (EEE, EEE)
GGG = (GGG, GGG)
ZZZ = (ZZZ, ZZZ)
</code></pre>

<h2>Part 1</h2>

<p>Given the above input, if we start at <code>AAA</code>, then follow the right-most node, we land on <code>CCC</code>. From <code>CCC</code> , we need to follow the left-most node, which happens to be <code>ZZZ</code>. So we&#39;re able to complete this in <code>2</code> steps.</p>

<p>Our puzzle&#39;s test input does have needing to restart the loop of directions, as the first pass only gets us to <code>BBB</code>, but a 2nd pass gets us to <code>ZZZ</code>.</p>

<pre><code>LLR  

AAA = (BBB, BBB)  
BBB = (AAA, ZZZ)  
ZZZ = (ZZZ, ZZZ)
</code></pre>

<p>This puzzle has graphs written all over it. I didn&#39;t build a full graph class, but a tiny implementation using a Hash:</p>

<pre><code>def build_graph(nodes)  
  nodes.each do |text_node|  
    node, left, right = text_node.scan(/(\w{3})/).flatten  

    insert_node(node)  
    add_edge(node, left)  
    add_edge(node, right)  
  end  
end  

def insert_node(node)  
  @nodes[node] = []  
end  

def add_edge(node1, node2)  
  @nodes[node1] &lt;&lt; node2  
end  

def neighbors(node)  
  @nodes[node]  
end
</code></pre>

<p>Then, we can prepare our graph from the puzzle input like so:</p>

<pre><code>@nodes = {}  

directions = input[0]  

text_nodes = input.slice(2..-1)  

build_graph(text_nodes)
</code></pre>

<p>This results in something like from our test input:</p>

<pre><code>{
  &quot;AAA&quot;=&gt;[&quot;BBB&quot;, &quot;BBB&quot;], 
  &quot;BBB&quot;=&gt;[&quot;AAA&quot;, &quot;ZZZ&quot;], 
  &quot;ZZZ&quot;=&gt;[&quot;ZZZ&quot;, &quot;ZZZ&quot;]
}
</code></pre>

<p>With the <code>0th</code> element being the left, and the <code>1st</code> being the right.</p>

<p>Next, we want to loop over our directions until we land on <code>ZZZ</code>.</p>

<pre><code>current_node = &quot;AAA&quot;  
hops = 0

while current_node != &quot;ZZZ&quot; do  
  directions.chars.each do |direction|  
    current_node = direction == &quot;L&quot; ? neighbors(current_node)[0] : neighbors(current_node)[1]  
    hops += 1  
  end  
end

hops
</code></pre>

<p>To do this, we take each character, and find the next node based on if the character is an <code>L</code> or an <code>R</code>. We then repeat infinitely or until we land on <code>ZZZ</code>, incrementing the number of hops with each loop.</p>

<p>This gets us our first star!</p>

<p>⭐️ Success!</p>

<h2>Part 2</h2>

<p>Again, we take a closer look at our input.</p>

<p>This time, we find out that its meant for ghosts! And since ghosts don&#39;t follow any rules of physics, they&#39;re able to traverse multiple paths at once. What we want to know now is, starting from all nodes ending in <code>A</code> , and travelling to the next node simultaneously, how many moves would need to be made before our ecoplasmic friends landed on all nodes ending in <code>Z</code>.</p>

<p>This one seemed too easy, like there would be a gotcha waiting for us the moment we tried to run it.</p>

<p>I adjusted my solution slightly to start with <code>current_nodes</code> (all nodes ending in <code>A</code>) and map over them all, progressing the list of current nodes, then checking if all nodes ended in <code>Z</code>.</p>

<p>Tests passed, first try — woo!</p>

<p>But the puzzle calculation was running for a while, and it kept running. I stopped it, added some logging to print the number of moves, and started again. For 10 mins, it was running, and got to a count of <code>~450,000,000</code>. At this point I stopped and decided to rethink my solution.</p>

<p>In <em>theory</em>, once a ghost starts at say <code>11A</code>, and ends on <code>22Z</code>, the length of that path is known. When we start our loop again, we start at the start of our direction list, even if we ended on <code>22Z</code> part way through our direction list.</p>

<p>So, given our test input:</p>

<pre><code>LR  

11A = (11B, XXX)  
11B = (XXX, 11Z)  
11Z = (11B, XXX)  
22A = (22B, XXX)  
22B = (22C, 22C)  
22C = (22Z, 22Z)  
22Z = (22B, 22B)  
XXX = (XXX, XXX)
</code></pre>

<p><code>11A</code> -&gt; <code>L</code> -&gt; <code>11B</code> -&gt; <code>R</code> -&gt; <code>11Z</code> (2)<code>22A</code> -&gt; <code>L</code> -&gt; <code>22B</code> -&gt; <code>R</code> -&gt; <code>22C</code> -&gt; <code>L</code> -&gt; <code>22Z</code> (3)</p>

<p>We find that following directions from <code>11A</code> gets us to a node ending with <code>Z</code> in 2 steps, and starting at <code>22A</code> gets us to a node ending with <code>Z</code> in 3 steps.</p>

<p>If we were to look at multiples for these numbers, we would find they intersect at <code>6</code>:</p>

<pre><code>2: 2, 4, 6, 8, ...
3: 3, 6, 9, 12, ...
</code></pre>

<p>This should mean that we would need to make 6 steps through our graph with each ghost to have them land on <code>11Z</code> and <code>22Z</code> at the same time. Let&#39;s try it!</p>

<p><code>11A</code><code>L</code> -&gt; <code>11B</code><code>R</code> -&gt; <code>11Z</code> <code>11A</code><code>L</code> -&gt; <code>11B</code><code>R</code> -&gt; <code>11Z</code><code>11A</code><code>L</code> -&gt; <code>11B</code><code>R</code> -&gt; <code>11Z</code> (6)</p>

<p><code>22A</code><code>L</code> -&gt; <code>22B</code><code>R</code> -&gt; <code>22C</code><code>L</code> -&gt; <code>22Z</code> <code>22A</code><code>L</code> -&gt; <code>22B</code><code>R</code> -&gt; <code>22C</code><code>L</code> -&gt; <code>22Z</code> (6)</p>

<p>Maybe better visualised with an image:</p>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/3905753a-a567-4a59-beab-cc5bdcec1305.png" alt=""></p>

<p>So now, instead of praying that we land on nodes ending with <code>Z</code> all at once, we can instead perform 6 calculations through from <code>A</code> to <code>Z</code> (6 because in my starting input, there are 6 nodes that end with A), and then find the lowest common multiple amongst them all.</p>

<p>The setup is the same as part 1, so this is all that needs to change:</p>

<pre><code>total_hops = start_nodes.map do |node|  
  hops = 0  

  while !node.end_with?(&quot;Z&quot;) do  
    directions.chars.each do |direction|  
      node = direction == &quot;L&quot; ? neighbors(node)[0] : neighbors(node)[1]  
      hops += 1  
    end  
  end  
  hops  
end  

total_hops.inject do |sum, number|  
  sum * number / sum.gcd(number)  
end
</code></pre>

<ol>
<li>loop through each of the starting nodes, counting the hops until we land on a node ending with <code>Z</code></li>
<li>find the lowest comment multiple, by using the <a href="https://en.wikipedia.org/wiki/Least_common_multiple#Using_the_greatest_common_divisor">greatest common divisor</a></li>
</ol>

<p>⭐️ Success!</p>

<p>Code: <a href="https://github.com/dNitza/advent-of-code/tree/main/2023/08">https://github.com/dNitza/advent-of-code/tree/main/2023/08</a></p>

<h2>Performance</h2>

<p>In doing some googling to see if I could speed up the lowest common multiple bit, I found that Ruby has a built in lowest common multiple method, which yielded almost identical performance.</p>

<pre><code>day 08
├─ part 1 — 0.002344s
├─ part 2 — 0.012820s
╰─ part 2 (ruby lcm) — 0.012787s
</code></pre>
]]>
      </description>
      <link>https://dnitza.com/posts/advent-of-code-2023-day-8</link>
      <guid isPermaLink="true">https://dnitza.com/posts/advent-of-code-2023-day-8</guid>
      <pubDate>Tue, 12 Dec 2023 20:52:43 +0000</pubDate>
    </item>
    <item>
      <title>Advent of Code 2023 — Day 7</title>
      <description>
        <![CDATA[<p><a href="https://adventofcode.com/2023/day/7">Day 7</a> saw us playing cards, on camels — camel cards!</p>

<p>Camel cards are like poker. But simpler, so you can play it while on a camel.</p>

<p>The hand-rules are similar to Poker, but with a few changes — which is ace, because I have no idea what the poker rules are anymore.</p>

<blockquote>
<p><em>Five of a kind</em>, where all five cards have the same label:&nbsp;<code>AAAAA</code></p>

<p><em>Four of a kind</em>, where four cards have the same label and one card has a different label:&nbsp;<code>AA8AA</code></p>

<p><em>Full house</em>, where three cards have the same label, and the remaining two cards share a different label:&nbsp;<code>23332</code></p>

<p><em>Three of a kind</em>, where three cards have the same label, and the remaining two cards are each different from any other card in the hand:&nbsp;<code>TTT98</code></p>

<p><em>Two pair</em>, where two cards share one label, two other cards share a second label, and the remaining card has a third label:&nbsp;<code>23432</code></p>

<p><em>One pair</em>, where two cards share one label, and the other three cards have a different label from the pair and each other:&nbsp;<code>A23A4</code></p>

<p><em>High card</em>, where all cards&#39; labels are distinct:&nbsp;<code>23456</code></p>
</blockquote>

<p>The hands are also ranked by suit, in descending order where an ace is high.</p>

<h2>Part 1</h2>

<p>In part 1, we have the following input:</p>

<pre><code>32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483
</code></pre>

<p>Where the right hand column is the bid amount for the hand.</p>

<p>Our job is to sort each hand, and then multiply each hand&#39;s bid with its rank.</p>

<p>So if we take the order above as an example —&nbsp;we&#39;d have <code>765 * 1</code> + <code>684 * 2</code> + <code>...</code></p>

<p>I started off this puzzle by assigning a suit / card number a letter (easier to sort alphabetically than numerically in this case):</p>

<pre><code>ORDER = {&quot;A&quot; =&gt; &quot;a&quot;, &quot;K&quot; =&gt; &quot;b&quot;, &quot;Q&quot; =&gt; &quot;c&quot;, &quot;J&quot; =&gt; &quot;d&quot;, &quot;T&quot; =&gt; &quot;e&quot;, &quot;9&quot; =&gt; &quot;f&quot;, &quot;8&quot; =&gt; &quot;g&quot;, &quot;7&quot; =&gt; &quot;h&quot;, &quot;6&quot; =&gt; &quot;i&quot;, &quot;5&quot; =&gt; &quot;j&quot;, &quot;4&quot; =&gt; &quot;k&quot;, &quot;3&quot; =&gt; &quot;l&quot;, &quot;2&quot; =&gt; &quot;m&quot;}.freeze
</code></pre>

<p>Then turned the input in to an array of arrays that looked like <code>[[hand, bid], ...]</code> , sorted the list and then went to work adding up each bid:</p>

<pre><code>rounds = input.map { |l| l.split(&quot; &quot;) }  
sorted_rounds = rounds  
                  .sort {|a,b| [hand_type(a[0]), hand_value(b[0])] &lt;=&gt; [hand_type(b[0]), hand_value(a[0])] }  
sorted_rounds  
  .map  
  .with_index(1) {|r, idx| r[1].to_i * idx }  
  .sum
</code></pre>

<p>To find the type of hand I was looking at, I used the below 2 methods.</p>

<p>First, I turned the hand in to a hash with the number of cards of each type. So for an input of <code>K3558</code> I would have <code>{K =&gt; 1, 3 =&gt; 1, 5 =&gt; 2, 8 =&gt; 1}</code>. Then, I take the values and sort them — in to something like <code>[2,1,1,1]</code>.</p>

<p>This is then passed in to the <code>score_hand</code> method which returns a rank based on the hand.</p>

<pre><code>def hand_type(hand)  
  cards = hand.gsub(/(.)\0*/).to_a  
  sorted_cards = cards.tally.values.sort {|a,b| b &lt;=&gt; a}  
  score_hand(sorted_cards)  
end
</code></pre>

<pre><code>def score_hand(cards)  
  return 6 if cards == [5]  
  return 5 if cards == [4,1]  
  return 4 if cards == [3,2]  
  return 3 if cards == [3,1,1]  
  return 2 if cards == [2,2,1]  
  return 1 if cards == [2,1,1,1]  
  return 0 if cards == [1,1,1,1,1]  
end
</code></pre>

<p>Lastly, we need to sort cards that have the same type based on the order of the cards in hand.</p>

<p>For this, sorting alphabetically was was simpler than zipping each array and comparing card values.</p>

<p>For this, I took a look at the <code>ORDER</code> constant and turned a hand like <code>K3558</code> to <code>bljjg</code>, so when compared with another hand that has the same type, say <code>K3448</code> (<code>bliig</code>), the first hand wins.</p>

<pre><code>def hand_value(hand)  
  hand.gsub(/(.)\0*/).map{ |v| ORDER[v] }.join(&quot;&quot;)  
end
</code></pre>

<h2>Part 2</h2>

<p>In part 2, Jacks are out, Jokers are in.</p>

<p>Jokers are the lowest scoring card, <em>but</em> they can be used as a wildcard to improve a hand to one of a better quality. So for example, the hand <code>33JJ8</code> goes from being two of a kind <code>[33, JJ]</code> to four of a kind <code>[3333]</code>!</p>

<p>Because Jokers are now worthless, I have a new order for them:</p>

<pre><code>IMPROVED_ORDER = {&quot;A&quot; =&gt; &quot;a&quot;, &quot;K&quot; =&gt; &quot;b&quot;, &quot;Q&quot; =&gt; &quot;c&quot;, &quot;T&quot; =&gt; &quot;d&quot;, &quot;9&quot; =&gt; &quot;e&quot;, &quot;8&quot; =&gt; &quot;f&quot;, &quot;7&quot; =&gt; &quot;g&quot;, &quot;6&quot; =&gt; &quot;h&quot;, &quot;5&quot; =&gt; &quot;i&quot;, &quot;4&quot; =&gt; &quot;j&quot;, &quot;3&quot; =&gt; &quot;k&quot;, &quot;2&quot; =&gt; &quot;l&quot;, &quot;J&quot; =&gt; &quot;m&quot;}.freeze
</code></pre>

<p>The code for part 2 and part 1 is pretty much the same, except I try and improve each hand when sorting:</p>

<pre><code>rounds = input  
           .map { |l| l.split(&quot; &quot;) }  
sorted_rounds = rounds  
                  .sort {|a,b| [improve_hand(a[0]), improved_hand_value(b[0])] &lt;=&gt; [improve_hand(b[0]), improved_hand_value(a[0])] }  
sorted_rounds  
  .map  
  .with_index(1) {|r, idx| r[1].to_i * idx }  
  .sum
</code></pre>

<p>I try and improve each hand with the method below. I do the same tallying of each hand.</p>

<p>So <code>33JJ8</code> becomes <code>{3 =&gt; 2, J =&gt; 2, 8 =&gt; 1}</code>.</p>

<p>But then I take any <code>J</code> cards and add them to whichever card type has the most already.</p>

<p>So now, the tally looks like <code>{3 =&gt; 4, 8 =&gt; 1}</code> — which makes this a 4 of a kind.</p>

<p><em>Thankfully</em> Jokers only improve hand quality, not overall score, so we didn&#39;t need to consider hands like <code>KQKQJ</code> and <code>QKQKJ</code> and apply the Joker to the King in order to get a better overall score.</p>

<pre><code>def improve_hand(hand)  
  cards = hand.gsub(/(.)\0*/).to_a  
  return hand_type(hand) unless cards.any? { |c| c == &quot;J&quot; }  
  return hand_type(hand) if cards.all? { |c| c == &quot;J&quot; }  

  card_types = cards.tally  
  improve_by = card_types[&quot;J&quot;]  
  type_to_improve = type_to_improve(card_types)  
  card_types[type_to_improve] += improve_by if improve_by &amp;&amp; type_to_improve  
  card_types.delete(&quot;J&quot;)  
  sorted_cards = card_types.values.sort {|a,b| b &lt;=&gt; a}  

  score_hand(sorted_cards)  
end  

def type_to_improve(cards)  
  return nil if cards.empty?  
  no_jack = cards.reject{|k,v| k == &quot;J&quot; }  
  return nil if no_jack.empty?  
  no_jack.max_by{|_,v| v}[0]  
end
</code></pre>

<p>⭐️ Success!</p>

<p>Day 7: <a href="https://github.com/dNitza/advent-of-code/blob/main/2023/07/lib/puzzle.rb">https://github.com/dNitza/advent-of-code/blob/main/2023/07/lib/puzzle.rb</a></p>

<h2>Performance</h2>

<pre><code>day 07
├─ part 1 — 0.073877s
╰─ part 2 — 0.101986s
</code></pre>
]]>
      </description>
      <link>https://dnitza.com/posts/advent-of-code-2023-day-7</link>
      <guid isPermaLink="true">https://dnitza.com/posts/advent-of-code-2023-day-7</guid>
      <pubDate>Mon, 11 Dec 2023 21:43:57 +0000</pubDate>
    </item>
    <item>
      <title>Advent of Code 2023 — Day 6</title>
      <description>
        <![CDATA[<p>Catchup time! 😅 Time for day 6 — on day 11.</p>

<p>Today&#39;s puzzle has us racing boats. But not any boats, toy boats that are wound up with the hold of a button. The longer the button is held, the higher their speed. So holding the button for 3ms, sends the boat off at 3mm/s, 4ms sends the boat off at 4mm/s and so on.</p>

<h2>Part 1</h2>

<p>There are 3 races to be sailed, for each race we have a maximum number of milliseconds for the race. The race starts when the button to wind up the boat is pressed down, and the time it&#39;s held for counts towards the total time.</p>

<p>Our goal is to work out how many ways we can win each race, and multiply the results.</p>

<pre><code>Time: 7 15 30
Distance: 9 40 200
</code></pre>

<p>For instance, the first race can be won by folding the button down for 2ms, giving the boat a speed of 2mm/s, which means it will travel for 10mm over 5s. 3ms, gives a speed of 3mm/s over 4s, resulting in a distance of 12mm and so on, for a total of 4 possible winning scenarios.</p>

<pre><code>races = input.map { |l| l.scan(/\d+/).map(&amp;:to_i) }  
races = races[0].zip(races[1])  

wins = {}  
races.each_with_object(wins) do |race, memo|  
  (0...(race[0]).round).each do |time_held|  
    wins[race[1]] ||= 0  
    memo[race[1]] +=1 if time_held * (race[0] - time_held) &gt; race[1]  
  end  
end
</code></pre>

<p>This is my code for part 1.</p>

<p>We first build up an array of <code>[[time, distance], ...]</code> then for each race, we check the time we hold the button for incrementally to see if the <code>speed</code> of the boat (the time we&#39;ve held the button for) is greater than the target <code>distance</code> when <code>speed</code> is multiplied by the <code>time remaining</code> in the race. Then, multiply the scores.</p>

<p>⭐️ Success!</p>

<h2>Part 2</h2>

<p>As usual with part 2, it&#39;s caused by us not reading the input properly the first time! In part 2, we realise that the race times we saw are actually poorly kerned, and it is actually a single race!</p>

<p>So now our time and distance look like this:</p>

<pre><code>Time: 71530
Distance: 940200
</code></pre>

<p>In this part, we just need to work out how many times we can win the race.</p>

<pre><code>race = input.map { |l| l.scan(/\d+/).inject(:+) }.map(&amp;:to_i)  

(0...(race[0])).map do |time_held|  
  if time_held * (race[0] - time_held) &gt; race[1]  
    1  
  else  
    0  
  end  
end.inject(:+)
</code></pre>

<p>Here&#39;s a super simple pass at it, using exactly the same logic as before. I did try and speed it up by only checking half the total time, because it looked like once you reached a maximum at the half-way mark, the rest of the numbers would mirror the first half of the range, but I was off by 1 in a couple of cases, and wanted to move on to day 7, so left it for now.</p>

<p>⭐️ Success!</p>

<h2>Performance</h2>

<pre><code>day 06
├─ part 1 — 0.000040s
╰─ part 2 — 2.095320s
</code></pre>

<p>Part 2, not super fast, but no time to make any improvements!</p>
]]>
      </description>
      <link>https://dnitza.com/posts/advent-of-code-2023-day-6</link>
      <guid isPermaLink="true">https://dnitza.com/posts/advent-of-code-2023-day-6</guid>
      <pubDate>Mon, 11 Dec 2023 21:03:35 +0000</pubDate>
    </item>
    <item>
      <title>Advent of Code 2023 — Day 5</title>
      <description>
        <![CDATA[<p>Please, my CPU, it needs help!</p>

<p>A classic entry for AoC, in which there is a brute-force option that will melt any CPU.</p>

<p>Today&#39;s puzzle was fun! But also frustrating.</p>

<p>The setup is you need to work out the best place to plant some seeds.</p>

<h2>Part 1</h2>

<p>In part 1, we have a map that looks like:</p>

<pre><code>seeds: 79 14 55 13

seed-to-soil map:
50 98 2
52 50 48

soil-to-fertilizer map:
0 15 37
37 52 2
39 0 15

fertilizer-to-water map:
49 53 8
0 11 42
42 0 7
57 7 4

water-to-light map:
88 18 7
18 25 70

light-to-temperature map:
45 77 23
81 45 19
68 64 13

temperature-to-humidity map:
0 69 1
1 0 69

humidity-to-location map:
60 56 37
56 93 4
</code></pre>

<p>Where each seed maps to some soil, soil to fertilizer, fertilizer to water and so on, until you reach a location.</p>

<p>The location with the lowest number is the closest, and we&#39;re tasked with finding the lowest location! The mapping is <em>super</em> convoluted, and probably best read in full here: https://adventofcode.com/2023/day/5#part1</p>

<p>This was the body of my code for the first part:</p>

<pre><code>seeds = input  
          .first  
          .gsub(&quot;seeds: &quot;, &quot;&quot;)  
          .split(&quot; &quot;)  
          .map(&amp;:to_i)  

seeds_to_soil = get_map(input: input, name: &quot;seed-to-soil map:&quot;)  
soil_to_fert = get_map(input: input, name: &quot;soil-to-fertilizer map:&quot;)  
fert_to_water = get_map(input: input, name: &quot;fertilizer-to-water map:&quot;)  
water_to_light = get_map(input: input, name: &quot;water-to-light map:&quot;)  
light_to_temp = get_map(input: input, name: &quot;light-to-temperature map:&quot;)  
temp_to_humidity = get_map(input: input, name: &quot;temperature-to-humidity map:&quot;)  
humidity_to_location = get_map(input: input, name: &quot;humidity-to-location map:&quot;)  

closest_seed = seeds.map do |seed|  
  sts = resolve_map(val: seed, target: seeds_to_soil)  
  stf = resolve_map(val: sts, target: soil_to_fert)  
  ftw = resolve_map(val: stf, target: fert_to_water)  
  wtl = resolve_map(val: ftw, target: water_to_light)  
  wtl = resolve_map(val: wtl, target: light_to_temp)  
  ltt = resolve_map(val: wtl, target: temp_to_humidity)  
  resolve_map(val: ltt, target: humidity_to_location)  
end.min  

closest_seed
</code></pre>

<p>First build a list of seed numbers, then build the map for each section, and finally resolve each map.</p>

<pre><code>def get_map(input: ,name:)  
  seeds_to_soil_index = input.find_index(name)  

  maps = input[seeds_to_soil_index+1..-1].take_while {|v| v != &quot;&quot;}  

  maps.map do |map|  
    map.split(&quot; &quot;).map(&amp;:to_i)  
  end.map do |map|  
    [[map[0], map[0]+map[2]], [map[1], map[1]+map[2]]]  
  end  
end
</code></pre>

<p><code>get_map</code> constructs the source and destination maps for each section</p>

<pre><code>def resolve_map(val:, target:)  
  match = target.detect { |t|  
    val &gt;= t[1].first &amp;&amp; val &lt;= t[1].last  
  }

  match.nil? ? val : match[0].first + (val - match[1].first)  
end
</code></pre>

<p><code>resolve_map</code> takes a value and a target map and works out if the value matches a location in that target map by working out the offset of the found item, and adding it to the location of the destination location. If no match is found, the value is passed through.</p>

<p>My first attempt of <code>resolve_map</code> used ranges, and found the <code>index</code> of the passed in <code>val</code> in the range of the destination map. For the test input, that was fine! But for the actual input, where the numbers are in the billions, it was not.</p>

<p>Anyway, this part worked nicely!</p>

<p>⭐️ Success!</p>

<h2>Part 2</h2>

<p>In part 2, instead of looking for the locations of a handful of seeds, we need to look for the location of hundreds of millions of seeds!</p>

<p>Safe to say that this solution didn&#39;t scale, and after leaving it run for 30 mins, it had barely progressed.</p>

<p>I have another idea, to <em>reverse</em> the lookup. That is, start with the lowest locations and see when we match a seed — then that&#39;ll be the result.</p>

<p>But that&#39;s enough computering for today, and this&#39;ll be a problem for future Dan.</p>

<p>🙅🏽‍♀️ No good!</p>

<h2>Performance</h2>

<p>Super snappy for part 1, stay tuned for part 2</p>

<pre><code>Rehearsal ------------------------------------------
part 1
  0.000526 0.000009 0.000535 ( 0.000533)
--------------------------------- total: 0.000535sec

             user system total real
part 1
  0.000253 0.000002 0.000255 ( 0.000255)
</code></pre>
]]>
      </description>
      <link>https://dnitza.com/posts/advent-of-code-2023-day-5</link>
      <guid isPermaLink="true">https://dnitza.com/posts/advent-of-code-2023-day-5</guid>
      <pubDate>Tue, 05 Dec 2023 21:14:24 +0000</pubDate>
    </item>
    <item>
      <title>Advent of Code 2023 — Day 4</title>
      <description>
        <![CDATA[<p>My favourite so far!</p>

<p>Inspired by some folks at work, I decided to try and do this as functionally as possible.</p>

<p>Apparently there are some similarities with past puzzles, but I, for the life of me, cannot remember any of the puzzles of years past 😆.</p>

<h2>Part 1</h2>

<p>Today&#39;s puzzle revolved around scratch-and-win cards.</p>

<p>A card looks like this:</p>

<pre><code>Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
</code></pre>

<p>On the left of the pipe are your numbers, on the right, are numbers that have been chosen as winners. Your first winning number was worth 1 point, and each subsequent win would <em>double</em> your score.</p>

<p>So 4 wins would be <code>(1 * 2 * 2 * 2) = 8</code></p>

<p>In part 1, we need to calculate the score of our card.</p>

<pre><code>input  
  .map { |l|  
    l.gsub(/Card \d+[:]\s/, &quot;&quot;)  
      .split(&quot;|&quot;)  
      .map{|v| v.split(&quot; &quot;)}  
  }  
  .map { |c|  
    c[0] &amp; c[1]  
  }  
  .map { |c|  
    next 0 if c.count &lt;= 0  
    1 * 2**(c.count-1)  
  }  
  .inject(:+)
</code></pre>

<p>That was my code for part 1.</p>

<p>First throw away the <code>Card N:</code> bits, then split my numbers from the winning numbers, and finally split both sides in to their own array.</p>

<p>Next, map over each pair and count the matches (<code>n</code>), then for each card, get the score <code>1 * 2^(n-1)</code> and finally sum everything up!</p>

<p>⭐️ Success!</p>

<h2>Part 2</h2>

<p>Enter, recursion~</p>

<p>In this part, instead of winning points for a card, we win <em>more cards!</em>, which in turn <em>win MORE cards</em> — and so on until we&#39;re out of cards.</p>

<p>The ask this time? How many cards did we win in total.</p>

<p>First up a couple of functions to make this a bit nicer:</p>

<pre><code>def play(cards, index)  
  @tally += 1

  cards  
    .slice(index + 1, wins(cards[index]))  
    .each_with_index do |_, idx|  
      play(cards, index + 1 + idx)  
    end  
end  

def wins(card)  
  (card[0] &amp; card[1]).count  
end
</code></pre>

<p><code>play</code> loops over all the cards we&#39;ve won and plays each one, which then loops over all the cards they win and plays each one, which then loops ...</p>

<p><code>wins</code> was just a convenience function for calculating wins.</p>

<p>Now we can do our tallying:</p>

<pre><code>@tally = 0  
cards = input  
          .map { |l|  
            l.gsub(/Card \d+[:]\s/, &quot;&quot;)  
             .split(&quot;|&quot;)  
             .map{|v| v.split(&quot; &quot;)}  
          }  

cards.each_with_index do |_, index|  
  play(cards, index)  
end  

@tally
</code></pre>

<p>First throw away all of the <code>Cards:</code> info and pre-process our cards in to the same shape as before, then, play each round.</p>

<p>After many <code>SystemStackError: stack level too deep</code> errors, I got there (always remember to increment your indices folks).</p>

<p>⭐️ Success!</p>

<h2>Performance</h2>

<p>This one was a good candidate for some perf gains.</p>

<pre><code>Rehearsal ------------------------------------------
part 1
  0.001061 0.000019 0.001080 ( 0.001079)
part 2
 13.300407 0.015743 13.316150 ( 13.330276)
-------------------------------- total: 13.317230sec

             user system total real
part 1
  0.000637 0.000006 0.000643 ( 0.000644)
part 2
 13.554139 0.020912 13.575051 ( 13.608991)
</code></pre>

<p>The code above for part 2 was suuuuper slow. Clocking in at ~13s.</p>

<pre><code>Rehearsal ------------------------------------------
part 1
  0.001129 0.000038 0.001167 ( 0.001173)
part 2
  2.834447 0.007401 2.841848 ( 2.845803)
--------------------------------- total: 2.843015sec

             user system total real
part 1
  0.000636 0.000003 0.000639 ( 0.000637)
part 2 
  2.838135 0.007346 2.845481 ( 2.849414)
</code></pre>

<p>After adding a super simple cache to <code>wins</code>, it shaved ~10s off the total time:</p>

<pre><code>def wins(card_id, card)  
  @win_cache[card_id] ||= (card[0] &amp; card[1]).count
end
</code></pre>

<p>Happy with that!</p>

<p>Day 4: <a href="https://github.com/dNitza/advent-of-code/tree/main/2023/04">https://github.com/dNitza/advent-of-code/tree/main/2023/04</a></p>
]]>
      </description>
      <link>https://dnitza.com/posts/advent-of-code-2023-day-4</link>
      <guid isPermaLink="true">https://dnitza.com/posts/advent-of-code-2023-day-4</guid>
      <pubDate>Mon, 04 Dec 2023 19:49:10 +0000</pubDate>
    </item>
    <item>
      <title>Advent of Code — Day 3</title>
      <description>
        <![CDATA[<p>And so the painful puzzles begin!</p>

<p>Today&#39;s puzzle revolved around an ascii schematic and we were tasked with parsing it and finding adjacent elements in the map.</p>

<p>These ones always trip me up for some reason — and I think it&#39;s because I don&#39;t normally work with data structures where some elements are related, by space, to others.</p>

<h2>Part 1</h2>

<p>Here&#39;s our schematic:</p>

<pre><code>467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..
</code></pre>

<p>In this part, we need to find out how many numbers are directly adjacent to (horizontally, vertically and diagonally) to a symbol (<code>.</code> does not count). Those numbers then need to be summed up.</p>

<p>For this, I first built up a list of all the symbol positions on each line:</p>

<pre><code>symbol_positions = {}  
input.each_with_index{ |l ,idx|  
  symbol_positions[idx] = []  
  l.enum_for(:scan, /[^a-zA-Z\d[.]:]/).each {  
    symbol_positions[idx] &lt;&lt; Regexp.last_match.begin(0)  
  }  
}
</code></pre>

<p>This resulted in something like <code>{0 =&gt; [], 1=&gt; [3], ..., 8 =&gt; [3,5], ... }</code></p>

<p>Next, I needed the positions of each of the numbers:</p>

<pre><code>number_positions = input  
                     .flat_map  
                     .each_with_index { |l, idx| l.enum_for(:scan, /\d+/).map { [idx, [Regexp.last_match.offset(0), Regexp.last_match.to_s.to_i]] } }
</code></pre>

<p>This resulted in an array like: <code>[[0, [[0, 2], 467], [5,7], 114]], [1,[...]]]</code> (<code>[[line, [match_range, match]]]</code>)</p>

<p>Then, it was just a matter of working through that list of numbers to see if any had an adjacent symbol. Where an adjacent symbol is one that appears in the previous line, the same line, or the next line within the <code>match_range</code> of the number (increased by 1 each side to account for diagonals).</p>

<pre><code>number_positions.map do |pos|  
  line = pos[0]  
  match_range = pos[1][0]  
  match = pos[1][1]  

  same_line = symbol_positions[line].any? { |pos| adjacent(match_range, pos) }  
  above = line &lt; (input.count - 1) &amp;&amp; symbol_positions[line + 1].any? { |pos| adjacent(match_range, pos) }  
  below = line &gt; 0 &amp;&amp; symbol_positions[line - 1].any? { |pos| adjacent(match_range, pos) }  

  match if same_line || above || below  
end.compact.inject(:+)
</code></pre>

<pre><code>def adjacent(range, item)  
  (range[0] - 1...range[1] + 1).include? item  
end
</code></pre>

<p>I initially had the range defined as <code>(min..max)</code> and my tests passed, but he answer I submitted was not correct!</p>

<p>Cleaned that up and ⭐️ success!</p>

<h2>Part 2</h2>

<p>Part 2 drove me nuts!</p>

<p>The idea was simple. Given the same schematic, now only find &quot;gears&quot;. Where a &quot;gear&quot; is a <code>*</code> symbol that has exactly 2 adjacent numbers. Multiply the two numbers for each gear, and sum the total.</p>

<p>I started this one off in a similar way, list all the <em>potential</em> gear positions and list all of the number positions.</p>

<p>Then, look over each potential position, and check if there is a number above, next to, or below it. What tripped my up though was my wahackadoo data structure above. I was trying to make today&#39;s effort a little simpler than yesterday&#39;s, but that meant it was trickier to debug and reason about as I was jumping between cooking dinner and this at the same time.</p>

<p>In the end, I got my tests passing — but oh no! my solution was too high?!</p>

<p>I had a look through the input data and found a &quot;gear&quot; that looked like:</p>

<pre><code>.....755.. 
....*.....
.664.598..
</code></pre>

<p>Which was <em>obviously</em> incorrect as the <code>*</code> was adjacent to 3 numbers, when it&#39;s only allowed to be adjacent to exactly 2.</p>

<p>I started <code>puts</code>-ing out what my code thought was a gear and it returned <code>[755, 664]</code> and <code>[755, 598]</code> as 2 seperate gears!!</p>

<p>It turned out the bug was in how I was determining what numbers were around a <code>*</code>, and I was just taking the first — so when I checked to see if a gear had more than 3 numbers, I was missing the 3rd!</p>

<p>This is how that code ended up in the end:</p>

<pre><code>gears = potential_gear_positions.flat_map do |line, positions|  
  positions.map do |gear|  

    lines = [  
      line &lt; (input.count - 1) &amp;&amp; number_positions[line + 1],  
      line &gt; 0 &amp;&amp; number_positions[line - 1],  
      number_positions[line]  
    ].compact  

    nums = lines  
             .flat_map { |l| l.select { |ll| adjacent(ll[0], gear) }.map(&amp;:last) }  
             .flatten  
             .compact  

    next 0 if nums.count != 2  

    nums.inject(:*)  
  end  
end  

gears.inject(:+)
</code></pre>

<p>⭐️ Success! 🫠</p>

<p>The code: <a href="https://github.com/dNitza/advent-of-code/tree/main/2023/03">https://github.com/dNitza/advent-of-code/tree/main/2023/03</a></p>

<p>Performance:</p>

<pre><code>Rehearsal ------------------------------------------
part 1 0.004141 0.000094 0.004235 ( 0.004255)
part 2 0.002621 0.000048 0.002669 ( 0.002673)
--------------------------------- total: 0.006904sec

             user system total real
part 1 0.003456 0.000042 0.003498 ( 0.003516)
part 2 0.002341 0.000022 0.002363 ( 0.002366)
</code></pre>
]]>
      </description>
      <link>https://dnitza.com/posts/advent-of-code-day-3</link>
      <guid isPermaLink="true">https://dnitza.com/posts/advent-of-code-day-3</guid>
      <pubDate>Sun, 03 Dec 2023 19:42:45 +0000</pubDate>
    </item>
    <item>
      <title>Authenticated routes with Hanami 2</title>
      <description>
        <![CDATA[<p>As I was building out the admin area of this blog, I wanted to make sure only authenticated people could hop in an edit things.</p>

<p>The first iteration of this, was based on HTTP BasicAuth. To get this to work, I built a small module that uses <code>Rack::Auth::Basic</code> — <a href="https://github.com/rack/rack/blob/main/lib/rack/auth/basic.rb">https://github.com/rack/rack/blob/main/lib/rack/auth/basic.rb</a>.</p>

<p>It looked like:</p>

<pre><code>class AuthenticatedAdminAction  
  def self.call(action:)  

    action = -&gt;(env) { Admin::Container[&quot;actions.#{action}&quot;].(env) }  

    basic_auth_username = Hanami.app.settings.basic_auth_username
    basic_auth_password = Hanami.app.settings.basic_auth_password  

    if basic_auth_username &amp;&amp; basic_auth_password
      Rack::Auth::Basic.new(action) do |username, password|  
        username == basic_auth_username &amp;&amp;  
          password == basic_auth_password  
      end  
    else      
        Rack::Auth::Basic.new(action_proc) { |_username, _password| true }  
    end  
  end
</code></pre>

<p>Then, on each path that I wanted to authenticate, I would adjust the <code>to:</code> argument call this method:</p>

<pre><code>Auth = AuthenticatedAdminAction

get &quot;/&quot;, to: Auth.call(action: &quot;index&quot;)
</code></pre>

<p>Which looks nice, and works well! However, <em>something</em> changed in the way Safari on iOS and macOS handles auto-filling usernames and passwords for HTTP Basic Auth that meant it would log me out every few minutes and I would need to re-open 1Password to log back in!</p>

<p>I thought this may have been a beta-season thing (after seeing a few other cases of it online), so I stuck it out for a while. But after many months of being on a stable version of Sonoma, the problem persisted!</p>

<p>So, onwards with a more stable and scalable form of authentication. It didn&#39;t need to be fancy, it just needed to let me create a session with as little hassle as possible. For this reason, I didn&#39;t opt for something like OmniAuth or even Warden and instead built a simple token-based auth system that emails me a login link.</p>

<p>The above code changes slightly, to now call some custom middleware with the user in the session, but is now a zillion times more reliable as Safari doesn&#39;t seem to forget who I am every 2 seconds 🙃.</p>

<p>The middleware now looks something like:</p>

<pre><code>module Middleware  
  class Authenticate  
    def initialize(app, auth_proc)  
      @app = app  
      @auth_proc = auth_proc  
    end  

    def call(env)  
      session = env[&quot;rack.session&quot;]  
      return [403, {&quot;Content-Type&quot; =&gt; &quot;text/html&quot;}, [&quot;Unauthorized | &lt;a href=\&quot;/admin/login\&quot;&gt;Login&lt;/&gt;&quot;]] unless @auth_proc.call(session[:user_id])  

      @app.call(env)  
    end  
  endend
</code></pre>

<h3>Next steps</h3>

<p>The next thing I want to look at is wrapping a block of routes in an authentication block, e.g:</p>

<pre><code>AuthenticatedThing.call do
  get &quot;/pages&quot;, to: &quot;pages.index&quot;
  get &quot;/pages/new&quot;, to: &quot;pages.new&quot;
  get &quot;/pages/:slug/edit&quot;, to: &quot;pages.edit&quot;
end
</code></pre>

<p>mainly to make it easier to see which routes are authenticated and avoid accidentally missing some routes.</p>
]]>
      </description>
      <link>https://dnitza.com/posts/authenticated-routes-with-hanami-2</link>
      <guid isPermaLink="true">https://dnitza.com/posts/authenticated-routes-with-hanami-2</guid>
      <pubDate>Sun, 03 Dec 2023 14:31:26 +0000</pubDate>
    </item>
    <item>
      <title>Advent of Code — Day 2</title>
      <description>
        <![CDATA[<p><a href="https://adventofcode.com/2023/day/2">Onwards! To day 2.</a></p>

<p>(at least until the rest of the end-of-year things start getting in the way — which normally happens at around day 6)</p>

<p>Started reading this one thinking we&#39;d be getting in to probabilities — <em>thankfully</em> that&#39;s not the case, yet.</p>

<h2>Part 1</h2>

<p>For today&#39;s puzzle, we&#39;re given the following input:</p>

<pre><code>Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green  
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue  
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red  
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red  
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green
</code></pre>

<p>Each line, represents a game, and each segment (separated by a <code>;</code>) represents a round in that game. Where, in each round, a set of cubes are pulled from a bag.</p>

<p>We&#39;re tasked with finding out, which games can be played with only the following cubes: <code>12 red cubes, 13 green cubes, and 14 blue cubes</code>, then, sum the game number to find our answer.</p>

<p>For this one, I first wanted to add some structure to each game. Parsing each one in to a hash would then mean when I want to do some poking at each game, I could reason about each bit.</p>

<p>So for that, there&#39;s this method:</p>

<pre><code>def build_games(input)  
  input.map do |line|  
    id = line.scan(/(\d+)[:]/).flatten.first.to_i  
    game = line.gsub(&quot;Game #{id}: &quot;, &quot;&quot;).split(&quot;;&quot;)  
    rounds = game.map do |reveal|  
      red = reveal.scan(/(\d+) red/).flatten.first&amp;.to_i || 0  
      green = reveal.scan(/(\d+) green/).flatten.first&amp;.to_i || 0  
      blue = reveal.scan(/(\d+) blue/).flatten.first&amp;.to_i || 0  

      {  
        red: red,  
        green: green,  
        blue: blue  
      }  
    end  

    {  
      id: id,  
      rounds: rounds  
    }  
  end  
end
</code></pre>

<p>Which returns an array of games in the following shape:</p>

<pre><code>game = {  
  id: 0,  
  rounds: [  
    red: 0,  
    green: 0,  
    blue: 0,  
  ]  
}
</code></pre>

<p>So then, it was just a matter of rejecting the games that exceeded our cube limit, and summing the IDs:</p>

<pre><code>result = games.reject do |game|  
  game[:rounds].any? { |round|  
    round[:red] &gt; 12 ||  
      round[:green] &gt; 13 ||  
      round[:blue] &gt; 14  
  }  
end  

result.map{|r| r[:id]}.inject(:+)
</code></pre>

<p>⭐️ Success!</p>

<h2>Part 2</h2>

<p>Thankfully, today&#39;s part 2 was free of gotchas!</p>

<p>In part 2, we need to work out what is the <em>minimum</em> number of balls we would need to be able to play each game. Then, multiply the number of balls for each colour, to get the <em>power</em> of the game. Sum all the powers, and that&#39;s our answer!</p>

<p>So for each game, we can work out the minimum number of required cubes with something like:</p>

<pre><code>min_red = game[:rounds]  
            .map{|r| r[:red].zero? ? nil : r[:red] }  
            .compact  
            .max
</code></pre>

<p>for each cube colour (making sure to remove 0s so we don&#39;t multiply by 0).</p>

<p>N.B — this could have been solved by also making the number of cubes optional in our game hash.</p>

<p>Then it&#39;s just a matter of multiplying the cube counts, summing everything up and we&#39;re done for day 2!</p>

<p>⭐️ Success!</p>

<p>Day 2: <a href="https://github.com/dNitza/advent-of-code/tree/main/2023/02">https://github.com/dNitza/advent-of-code/tree/main/2023/02</a></p>
]]>
      </description>
      <link>https://dnitza.com/posts/advent-of-code-day-2</link>
      <guid isPermaLink="true">https://dnitza.com/posts/advent-of-code-day-2</guid>
      <pubDate>Sat, 02 Dec 2023 17:31:48 +0000</pubDate>
    </item>
    <item>
      <title>Advent of Code — Day 1</title>
      <description>
        <![CDATA[<p><a href="https://adventofcode.com">Advent of Code</a> is one of my favourite times of year. Partly for the puzzles, and partly because it&#39;s a sign the year is winding up. Like <a href="https://24ways.org">24 ways to impress your friends</a> and <a href="https://www.uxmas.com">UXmas</a> before it, Advent of Code is an advent calendar, where each day is a pair of code challenges. There&#39;s usually some kind of narrative that ties the puzzles together, which ends up adding a fun christmas-y spin to the whole thing.</p>

<p>Usually, I try and do it in a programming language I am not super familiar with, but this year I&#39;m going to stick to Ruby (mainly because I am out of ideas for languages to try 😆).</p>

<h2>Part 1</h2>

<p>The first part was nice and simple. The challenge was to extract the first and last digits from each line, concatenate them in to a new number, and then sum the total.</p>

<p>E.g. given:</p>

<pre><code>1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet
</code></pre>

<p>The first line would be <code>12</code>, the second <code>38</code> and so on, with the solution being <code>12 + 38 + ...</code></p>

<p>My solution was as follows</p>

<pre><code>input.map{ |line|   
  matches = line.scan(/\d/)   
  Integer(&quot;#{matches[0]}#{matches[-1]}&quot;)   
}.inject(:+)
</code></pre>

<p>⭐️ Success!</p>

<h2>Part 2</h2>

<p>The second part was sneaky! Building on from the previous part, we&#39;re now tasked with including numbers that have been <em>spelled out</em>.</p>

<p>So for example, given:</p>

<pre><code>two1nine  
4nineeightseven2
</code></pre>

<p>The first line would be <code>29</code> and the second would be <code>42</code>.</p>

<p>Which seemed simple at first:</p>

<pre><code>NUMBERS = {one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9}.freeze

num_pattern = NUMBERS.keys.join(&quot;|&quot;)  

input.map {|line|  
  matches = line.scan(/#{num_pattern}|\d/)  
  first = NUMBERS[matches[0].to_sym] || matches[0]  
  last = NUMBERS[matches[-1].to_sym] || matches[-1]  

  Integer(&quot;#{first}#{last}&quot;)  
}.inject(:+)
</code></pre>

<ol>
<li>Search for either a digit, or the number-in-letters</li>
<li>Grab the first and last match (and if either is a number-in-letters, replace it with the digit)</li>
<li>Create a new integer and we&#39;re done!</li>
</ol>

<p>I submitted my answer, and <strong>booo</strong> , too high!</p>

<p>My tests passed, what was I missing?</p>

<p>I threw in a bunch of <code>puts</code> in the above code and saw a line like the following:</p>

<p><code>pxxtd793frjfckbhstcdhsrx58mksktwoneqx</code></p>

<p>Which my code was resolving to <code>72</code>, but should have been <code>71</code>!</p>

<p>The issue was with the last section <code>ktwoneqx</code>, the regular expression I was using was was not looking ahead! So a quick change to use <a href="https://www.regular-expressions.info/lookaround.html">a positive lookahead</a> and I was done!</p>

<p>The new pattern becomes: <code>/(?=(#{num_pattern}|\d))/</code></p>

<p>⭐️ Success!</p>

<p>Day one: <a href="https://github.com/dNitza/advent-of-code/tree/main/2023/01">https://github.com/dNitza/advent-of-code/tree/main/2023/01</a></p>
]]>
      </description>
      <link>https://dnitza.com/posts/advent-of-code-day-1</link>
      <guid isPermaLink="true">https://dnitza.com/posts/advent-of-code-day-1</guid>
      <pubDate>Sat, 02 Dec 2023 16:01:46 +0000</pubDate>
    </item>
    <item>
      <title>My desks over the years</title>
      <description>
        <![CDATA[<p>I was poking through my photo library this evening and thought it&#39;d be fun to share some of the photos I had taken of my desk over the years!</p>

<p>There&#39;s a pretty big gap between 2012 &amp; 2017 — I am pretty sure I have photos of these, just Apple Photos was doing a bad job of surfacing them.</p>

<h2>2011</h2>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/1f1f932f-da11-4526-a0a3-caf014addf69.JPG" alt="A dimly lit monitor with a magic keyboard, mug and notebook in front"></p>

<h2>2012</h2>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/b38d5aa1-b0eb-4e91-8607-02874183899e.JPG" alt="A dimly lit monitor with a magic mouse and keyboard in-front. The light-source is an IKEA lamp"></p>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/61dc2999-186b-4811-a2dc-1f36b959a07f.JPG" alt="A desk with 2 monitors and a laptop. Above the desk are 3 posters of wolverine, batman, and spiderman"></p>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/04f45c39-f9d0-441c-95f2-bd21ee8a8ea5.jpeg" alt="My laptop at the time, covered in stickers"></p>

<h2>2017</h2>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/bd163c64-b2f0-450c-b601-69eefdc71871.JPG" alt="A desk lit by a beam of sunlight. On it is a laptop plugged in to a monitor, a magic mouse, a mechanical keyboard and a short coffee mug"></p>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/6f3acf78-5fdb-437a-a16d-42065981cc4c.jpeg" alt="A dog flying off my lap while I am at my desk"></p>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/3ade1720-7129-4819-bbaf-d8d23be657ef.jpeg" alt="My laptop at the time, covered in stickers"></p>

<h2>2018</h2>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/007845ab-9061-4cc0-8f6e-09a33b2327a8.jpeg" alt="A desk with a laptop in clam-shell mode, a set of bookshelf speakers, a green lamp and a mechanical keyboard"></p>

<h2>2019</h2>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/fb96a813-a383-43cd-9516-0093879679a3.jpeg" alt="A desk in front of a window. There&#39;s a laptop in clamshell mode, a split keyboard and magic mouse among a bunch of other desk-related bits and pieces"></p>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/ac8ae514-440e-499c-b7df-8f590df750fa.jpeg" alt="The same desk as above, at night, working on an iOS app"></p>

<h2>2020</h2>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/5696be2b-a1a4-4893-9ab3-1121a2160ebd.jpeg" alt="The same desk as above, but now the monitor is on a stand of books, and there is an iPad open"></p>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/a461f36c-abea-4b9d-8184-4756d2e73d79.jpeg" alt="A desk backed on to a wall. The main monitor is landscape, but there is also a portrait monitor to the right, leaning against the wall."></p>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/540f0848-1ad3-43d5-91b1-320a1da6e899.jpeg" alt="A photo I took of my office for OHS compliance. It shows a desk with a laptop connected to an external monitor side-by-side as well as a book-shelf full of books."></p>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/9b8568cd-7b3d-43c2-9ef7-db5d86612aac.jpeg" alt="My laptop at the time, covered in stickers"></p>

<h2>2022</h2>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/274124bb-f6d2-4235-af58-8731bd35892d.jpeg" alt="A desk with a monitor on an arm and raised well above the top of the desk. Also, a microphone, bookshelf speakers and a mechanical keyboard are in view"></p>

<h2>2023</h2>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/d4d3cbfc-1491-4333-8612-1e7dcf3347f1.jpeg" alt="A desk with 2 monitors. One horizontal, one vertical, both plugged in to a laptop. There&#39;s a microphone behind the screens as well as a wired keyboard and a wireless mouse"></p>
]]>
      </description>
      <link>https://dnitza.com/posts/my-desks-over-the-years</link>
      <guid isPermaLink="true">https://dnitza.com/posts/my-desks-over-the-years</guid>
      <pubDate>Tue, 21 Nov 2023 20:29:16 +0000</pubDate>
    </item>
    <item>
      <title>How I write this blog on an iPad</title>
      <description>
        <![CDATA[<p>For the <em>longest</em> time, I have wanted to be able to write with just an iPad, with <a href="https://en.wikipedia.org/wiki/Markdown">markdown</a>, in some native text editor and not need to copy and paste text in to a WYSYWIG.</p>

<p>My previous attempts included: - WordPress; which was okay. Though it required typing in a handful of apps that could post to <a href="https://codex.wordpress.org/XML-RPC_Support">WordPress&#39; XML-RPC</a> endpoint, and, at the time, none of those apps had a writing experience that I liked. Unfortunately, I moved away from WordPress (for other reasons) by the time <a href="https://ia.net/writer">iA Writer</a> and <a href="https://ulysses.app">Ulysses</a> came along. - Jekyll; which was probably the closest I got. This involved editing posts in a Dropbox folder that was kept in sync with my web server. Since Jekyll is a static site generator, all of the site&#39;s HTML needs to be regenerated every time a change occurs in order for that change to be made available to the public. This goes for new pages as well as changes to existing pages. So in practice, this means whenever a file is changed in Dropbox, <em>something</em> would need to tell Jekyll to regenerate the site. And this is where this attempt came unstuck. I wasn&#39;t able to get the regeneration process to work reliably, which meant that changes would sometimes never get picked up, requiring me to jump in to the server and manually build the site. - Ghost; this one never really took off with regard to publishing from an iPad. And it also suffered from something that was annoying me about Jekyll. It was somewhat inflexible out of the box, and to make it more flexible required putting in some work to update some of the template code. Which is fine, until the vendors of the base software publish a major update and then all of the edits required to make the site work, no longer work — meaning more work to get things back to normal.</p>

<p>Blogging ecosystem aside, there was also a lull in the capability of the software in the iPad ecosystem (both apps and iPadOS). This, however, has become less of an issue now that <a href="https://obsidian.md">Obsidian</a> exists for writing, and <a href="https://support.apple.com/en-au/guide/shortcuts/welcome/ios">Shortcuts</a> exists to patch many of the gaps in iPadOS. Which leads to where I am today.</p>

<p>This blog adheres to the <a href="https://www.w3.org/TR/micropub">Micropub spec</a>, which means that most of the techniques below can be used with any blog that also follows the spec.</p>

<p>For text posts, everything is written in Obsidian. I then have a shortcut that accepts the post&#39;s body as markdown. This means I can share a document with the shortcut, fill in a title as well as any other metadata (like tags, location, publish time) and then send the request on its way to result in a brand new post on this site.</p>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/5b8e5951-21da-4b6c-901d-9911c580fe14.png" alt="A screenshot of the Apple Shortcuts app showing a request to create a post"></p>

<p>Things get a bit more involved for media posts. Right now, photos need to be uploaded to a special &quot;media&quot; endpoint before they can be used in a post. This endpoint accepts a file, and returns the URL of the uploaded piece of media (photo or video). So for this, I have a shortcut that accepts one or more photos (just photos for now), sends them to the media endpoint, and copies the URLs of all the uploaded photos to the clipboard. Pasting these in to Obsidian renders them, so I can see which photo is which and place them accordingly in the post.</p>

<p>Right now, I don&#39;t publish many videos (though I wanted to during <a href="https://dnitza.com/trips/5">a recent trip</a>). So some upcoming changes to the media shortcut will allow the upload of videos (which the server supports). But I also want to be able to take a live photo with a loop effect, and upload that as a looping GIF / mp4.</p>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/889af11a-f92b-4841-b574-3f6a5d6e4bb0.png" alt="A screenshot of the Apple Shortcuts app showing a request to upload images"></p>

<p>I also have similar posts for <em>specific</em> post types (e.g. bookmarks, photo posts and the like).</p>

<p>So with a handful of shortcuts, publishing to this blog has finally become as simple as writing in a text editor that I <em>really</em> like, and then pressing a button or two. And since it&#39;s all done over HTTP, it&#39;s <em>hopefully</em> less likely to break with changes to iOS or iPadOS.</p>
]]>
      </description>
      <link>https://dnitza.com/posts/how-i-write-this-blog-on-an-ipad</link>
      <guid isPermaLink="true">https://dnitza.com/posts/how-i-write-this-blog-on-an-ipad</guid>
      <pubDate>Sat, 26 Aug 2023 01:43:45 +0000</pubDate>
    </item>
    <item>
      <title>Update 1 — G'day mate</title>
      <description>
        <![CDATA[<p>The first few weeks of any new project are always the most exciting! When exploration and problem solving us abundant, and suffering for decisions of yesterday is scarce.</p>

<p>Over the last few weeks I have been putting together a small app to help Abby organise her collection of clothing patterns. The current method of organisation is AirTable, which, is incredibly powerful, but because if its flexibility, falls short in a couple of key places. The working name is &quot;PatternMate&quot; — largely because of TextMate and MailMate 😅.</p>

<p>So, together we sketched out a quick handful of screens and flows that would make organising patterns simpler and bypass some over the overhead of using a tool like AirTable. Some of which, I have included below:</p>

<ul>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/7d60576b-d2c6-43aa-9042-1be54ac801e3.jpg" alt="A pattern detail screen"></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/c93c9715-5080-41ef-a9e6-0263e615f604.jpg" alt="A rough pattern list view, showing a couple of important details, like the front cover of the packet, and tags"></li>
</ul>

<p>One of the key use-cases for this app is to be able to quickly see if any patterns have a tag that fits with a fabric that is about to be purchased. So for example, some patterns are designed for fabrics that have some amount of <em>stretch</em>, while others can not tolerate <em>any</em> stretch. So, being able to tag and filter patterns, then quickly see how much fabric is required is a flow that I am paying particular attention to.</p>

<hr>

<p>I&#39;ve started a brand new app that targets iOS17 because I really like how SwiftData looks. After many years of trying to decipher CoreData (and often reaching for libraries that just wrap it instead), SwiftData looks like a really nice approach to persistence with SwiftUI. And so far? It has been really nice! There are a couple of bugs that I have run in to, but the API and developer experience are top notch so far.</p>

<p>The bugs I am running in to so far: - A crash when trying to load a Model&#39;s attribute that has ben annotated with <code>@Attribute(.externalStorage)</code>. This is <em>meant</em> to store the contents of the field on disk, next to the DB, which is useful for things like storing image data. So for the moment, I am loading an image from a hardcoded URL in place of an image loaded from disk. - The app hanging and a memory leak whenever I try and load a query that has a variable in it (the example below works when I replace <code>tag.name</code> with a hardcoded string)</p>

<pre><code>let predicate = #Predicate&lt;Pattern&gt; { $0.tags.contains { t in
  t.name == tag.name
} }

_patterns = Query(filter: predicate, sort: \.name)
</code></pre>

<p>But previews have largely meant that I can still work, just running the app in a simulator or on device and trying to visit these screens is a no-go for now. Here&#39;s hoping that Xcode 15 beta 6 addresses some of these.</p>

<p>One of the other things I have been looking at is Apple&#39;s &quot;vision&quot; APIs. In particular <code>VNRecognizeTextRequest</code>, which lets you pull out bits of text from a photo, then run some custom filtering to pick things that might be interesting to you. So in this case, I am checking the text of a scanned packet to try and grab the designer&#39;s name and pattern number to pre-fill them and streamline the pattern adding process. That scan will then be used as the pattern&#39;s image, so a step most people will hopefully find useful.</p>

<p>WIP early screens:</p>

<ul>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/1d6b249a-be6e-4d10-98bf-08531698d1aa.png" alt="Pattern list"></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/c8b50a04-760f-461f-bfad-bb74b9699b05.png" alt="Pattern detail"></li>
<li><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/e07930ea-85e1-4d2c-b235-b5c35173f711.png" alt="New pattern"></li>
</ul>
]]>
      </description>
      <link>https://dnitza.com/posts/update-1-gday-mate</link>
      <guid isPermaLink="true">https://dnitza.com/posts/update-1-gday-mate</guid>
      <pubDate>Mon, 07 Aug 2023 04:05:01 +0000</pubDate>
    </item>
    <item>
      <title>How I debug with Ruby</title>
      <description>
        <![CDATA[<p>Over the many years that I have been working with Ruby, I have never really learned to use a debugger, and really only lean on tools like <a href="https://github.com/pry/pry">Pry</a> to start a REPL session at a particular line in some Ruby code. From there, I reach for Ruby&#39;s built in debugging tools, and thus far, they have served me well! Below is a non-exhaustive list of some of the common techniques I use to work out what the heck is going on with some piece of code.</p>

<h2>How do I work out what methods are available on a class?</h2>

<p>The <code>methods</code> method lets you see which methods an class defines. This is useful when working with an un-documented or otherwise unknown class, but have an idea of what you might want to achieve. The return value of this call is an array of symbols that represent method names.</p>

<pre><code>Foo.methods #=&gt; [:bar, :baz, ...]
</code></pre>

<p>If you run this yourself on a class, you&#39;ll see a huge list of method names, including things like <code>:new, :==, :inspect</code>. This is is because <code>method</code> also returns a list of methods that the ancestors of your object respond to. If you want to clean up this you can:</p>

<p>Pass the <code>false</code> argument in to <code>methods</code>:</p>

<pre><code>Foo.methods(false) =&gt; [:bar, :baz]
</code></pre>

<p>Or selectively remove the methods of known ancestors from the resulting output:</p>

<pre><code>Foo.methods - Object.methods =&gt; [:bar, :baz]
</code></pre>

<p>There is also the <code>instance_methods</code> method that does the same thing, but only returns instance methods.</p>

<p>The following calls are almost the same:</p>

<pre><code>Foo.new.methods #=&gt; includes methods that belong to ancestors

Foo.instance_methods(false)
</code></pre>

<p>Ok cool, now we have an idea of the methods, but what about actually calling them?</p>

<p>The <code>parameters</code> method lets you take a peak at the arguments required to call a method. This is not always bulletproof, as some methods take a variable number of args (e.g. <code>def foo(*args)</code>)</p>

<p>Anyway, once we have a method to inspect, we can use <code>parameters</code> like so:</p>

<pre><code>class Foo
    def bar(one:, two:)
    end

    def baz(one, two = nil, three: nil)
    end
end

Foo.instance_method(:bar).parameters #=&gt; [[:keyreq, :one], [:keyreq, :two]]

Foo.instance_method(:baz) #=&gt; [[:req, :one], [:opt, :two], [:key, :three]]
</code></pre>

<p>You can even see if a method has a default provided (<code>:key</code>, <code>:opt</code>), or is required (<code>:req</code>, <code>:keyreq</code>) along with its name.</p>

<h2>Workout where a method is defined.</h2>

<p>Sometimes, you&#39;re calling a method and the behaviour being presented is not at all what you expect, or even worse, your myriad of <code>puts</code> and <code>raise</code> calls you&#39;ve put in place to debug the behaviour are not being called!</p>

<p>Here&#39;s an example:</p>

<pre><code># source/foo.rb

class Foo
    def bar(arg)
        raise &quot;here&quot;.inspect
        # do the method here
    end
end

Foo.new.bar(arg) #=&gt; Nothing is raised 😱
</code></pre>

<p>Inspecting the <code>bar</code> method with <code>source_location</code> should help work out what is going on here:</p>

<pre><code>Foo.instance_method(:bar).source_location #=&gt; [&#39;some_monkey_patch.rb&#39;]
</code></pre>

<p>In our case here, we can see that something else is being called instead of our method in our class! While this exact example is not super common, I have been bitten by this a couple of times by some gems implementing a <code>to_h</code>, <code>to_json</code>, <code>to_s</code> etc. method on basic Ruby classes that then cause unexpected behaviour.</p>

<p>Another variation of this is when you have a super-class that is calling <code>super</code> in its initialiser (or some other place) but you don&#39;t know where that goes.</p>

<pre><code>class Foo &lt; Bar
    def call
        puts method(:call).super_method.source_location #=&gt; [&#39;/gems/path/bar.rb#42&#39;]
        # super
    end
end
</code></pre>

<p>You can then use something like <code>cat</code> in the terminal to inspect the file and get a sense of what Bar&#39;s super method will call.</p>

<p>Or better than <code>cat</code>, you can use <code>bundle open</code> to dig around a gem with your editor of choice (discussed in the next section).</p>

<h2>Inspecting a Gem</h2>

<p>Sometimes, the code you&#39;re having trouble with is in a gem! There are a couple of ways to go about inspecting this code. If the code is hosted on Github, then that&#39;s a fairly simple way of spelunking through code, with the benefit of having commit messages right there that could explain the behaviour you&#39;re seeing (just make sure you&#39;re looking at the right version).</p>

<p>But, you can also use <code>bundle open gemname</code> to have bundler open the correct version of the gem in your <code>EDITOR</code> of choice. From here, you can throw in debugging statements, make changes, and even fix a bug or two if you find any :)</p>

<p>If you&#39;re spelunking has lead you to drop <code>puts</code> statements through many files, you can clean the gem up quickly with <code>gem pristine gemname</code> (or <code>gem pristine --all</code> if your debugging journey spanned multiple gems!).</p>

<h2>How did we get here?</h2>

<p>We&#39;ve discussed working out where a method call will <em>take us</em>, but what about how we got to a particular method.</p>

<p>For this, we can use <code>caller</code>. <code>caller</code> will print out a massive stack trace showing you how we got here, but the first line is the one that really matters:</p>

<pre><code>class Foo
    def bar
        caller
    end
end

irb(main):072:0&gt; Foo.new.bar #=&gt; [&quot;(irb):72:in `&lt;top (required)&gt;&#39;&quot;,&quot;/Users/danielnitsikopoulos/.asdf/installs/ruby/3.2.0/lib/ruby/3.2.0/irb/workspace.rb:119:in `eval&#39;&quot;, ...]
</code></pre>

<p>in this case, I&#39;m running the code in irb, so <code>(irb):72</code> is where this is being called from.</p>

<h2>Formatting</h2>

<p>The last thing I want to touch on, is how to do some basic formatting to make inspecting output a little easier.</p>

<p>Add some markers to split <code>puts</code> calls, especially when calling something in a loop:</p>

<pre><code>def foo
    puts &quot;Doing something:&quot;
    do_something
    puts &quot;*&quot; * 88 # print out 88 stars
end

2.times { foo } #=&gt;

Doing something:
****************************************************************************************
Doing something:
****************************************************************************************
</code></pre>

<p>When looking through a collection of data to understand something about it, also print out things like IDs so that you can pick out the one you want:</p>

<pre><code>some_collection.map {|c| [c.id, c.name] } #=&gt;
[
  [123, &quot;foo&quot;],
  [234, &quot;bar&quot;],
  [345, &quot;baz&quot;],
  ...
]
</code></pre>

<h2>Conclusion</h2>

<p>Ruby&#39;s built in meta programming tools are super powerful, and while there are definitely a bunch more techniques for debugging, these are the ones I always turn to first and usually are all I need to work out what is happening. I will (one day) get RubyMine&#39;s built in debugger working and report back if it blows any of this out of the water :D</p>
]]>
      </description>
      <link>https://dnitza.com/posts/how-i-debug-with-ruby</link>
      <guid isPermaLink="true">https://dnitza.com/posts/how-i-debug-with-ruby</guid>
      <pubDate>Sun, 07 May 2023 07:29:47 +0000</pubDate>
    </item>
    <item>
      <title>How I use Obsidian</title>
      <description>
        <![CDATA[<p>I&#39;ve been using <a href="https://obsidian.md">Obsidian</a> for a little while now. At first, it was largely <em>just</em> a replacement for <a href="https://www.icloud.com/notes">Notes.app</a>, like <a href="https://ulysses.app">Ulysses</a> before it, I preferred it purely because I could <em>see</em> the markdown files that it was saving. As time went on though, I would pick up some new bits from folks at work who also use Obsidian, or from podcasts or blogs I subscribe to. It&#39;s now become a big part of my life on macOS and iOS — right up there with iTerm or Safari.</p>

<h2>Writing documentation</h2>

<p>Almost any piece of documentation I write for work starts its life in Obsidian. I have a vault just for work docs that doesn&#39;t get synchronised and lives on my work computer, but means I can write outlines and drafts out and publish a doc when it&#39;s ready. Not to mention having a local backup in case Confluence decides to crash and lose my work — though, to be fair, this is happening a lot less lately.</p>

<h2>Daily notes and to-do lists</h2>

<p>This is where Obsidian really shines in my opinion. I used to use <a href="https://www.taskpaper.com">Taskpaper</a> for plain text daily to-do lists. And by daily to-do lists, I mean a single long list that I would keep adding things to / checking things off from. While this was kind of neat in the sense that I have a historic list of all things I&#39;ve don, the method for organising those to-do items was a bit unwieldy. Each day I would need to tag an item with <code>@today</code> if I wanted it to be visible in the &quot;today&quot; view. Booo 👎🏼.</p>

<p>Obsidian has a built in concept of a &quot;Daily Note&quot;. Right from the command palette, you can &quot;open today&#39;s daily note&quot; and if there isn&#39;t one, Obsidian will offer to make one for you. Of course, where this note is saved and its filename is customisable. But the <em>cool</em> thing is, you can also install a plugin to create a daily note <em>from a template!</em></p>

<p>My daily note template is as follows: - A created at timestamp - A tag of &quot;Daily Notes&quot; - A section for a to-do list - A section for notes, tagged with <code>#daily_note</code> (more on why in a bit).</p>

<pre><code>created: &lt;% tp.file.creation_date() %&gt;
---
tags:: [[+Daily Notes]]

---

##### ✅ To do
- [] 

---
# 📝 Notes
- &lt;% tp.file.cursor() %&gt;

#daily_note
</code></pre>

<p>So now, whenever a daily note is created, it&#39;s labeled with the day&#39;s date and the cursor is placed in the notes section, ready to go.</p>

<p>There is one more plugin I use though, to make this setup really work for me. And that is a plugin to roll-over any to-dos from the previous daily note to today. This way I still have the history of what was done when, but not all the clutter or manual work of tagging when a task should appear.</p>

<p>So when I first log in for the day, I&#39;ll usually open the day&#39;s daily note and clean up whatever to-do items are rolling over, add any new for the day and then be good to go!</p>

<p>Plugins to make this happen: - <a href="https://github.com/SilentVoid13/Templater">Templater</a>- <a href="https://github.com/lumoe/obsidian-rollover-daily-todos">Rollover Daily Todos</a>- <a href="https://github.com/liamcain/obsidian-calendar-plugin">Calendar</a> is also nice. It adds a calendar to the sidebar so you can jump to any daily note, without having to search.</p>

<h2>Reviewing daily notes</h2>

<p>I mentioned that I have a <code>#daily_note</code> tag in the notes section itself. The reason for this is that every 6 months or so at work, performance review season rolls around. And, in what I am calling a weird coincidence, every 6 months or so I wish I had taken notes about what I had done for the previous 6 months. This is where the daily note tag comes in. My mate Mike put me on to <em>another</em> plugin called <a href="https://github.com/blacksmithgu/obsidian-dataview">Dataview</a> which basically lets you query your Obsidian vault. He also shared with me this script (below) that will search for notes in a folder with a given tag, and then pull the notes in to a view broken down by month and year.</p>

<p>So now, whenever I am taking notes in meetings, or when I am puzzling something out, I throw a <code>#daily_note</code> on the end of important lines and violà, a prompt and a fighting chance of remembering what I did in the last 6 months.</p>

<pre><code>// get daily notes pages
let pages = dv.pages(&#39;&quot;Daily Notes&quot; and #daily_note&#39;).sort(p =&gt; p.file.cday)

// group pages by year
let years = pages.groupBy(p =&gt; p.file.cday.toFormat(&quot;yyyy&quot;))

// for each year...
years.forEach(year =&gt; {
    // add a header
    dv.header(3, year.key)

    // group by month
    let months = year.rows.groupBy(y =&gt; y.file.cday.toFormat(&quot;MMMM&quot;))

    // for each month...
    months.forEach(month =&gt; {
        // add a table with the month name as header
        // and each item for that month in the body
        let file = month.rows.file
        dv.table(
            [month.key], 
            file.
                lists
                .where(item =&gt; !item.task)
                .map(item =&gt; [&quot;- &quot; + item.text]))
    })
})
</code></pre>

<h2>Writing these posts</h2>

<p>I also use Obsidian for writing my weekly posts and posts like this one! While I wish it had better <a href="https://www.w3.org/TR/micropub/">Micropub</a> support, I make do with a Ruby script that pulls in Jekyll-like markdown files and converts them to the JSON that Micropub expects.</p>
]]>
      </description>
      <link>https://dnitza.com/posts/how-i-use-obsidian</link>
      <guid isPermaLink="true">https://dnitza.com/posts/how-i-use-obsidian</guid>
      <pubDate>Mon, 10 Apr 2023 08:33:47 +0000</pubDate>
    </item>
    <item>
      <title>Ruby learning materials.</title>
      <description>
        <![CDATA[<p>I often get asked about recommendations for resources to learn Ruby (and how to program, generally). This list is intended to catalogue the various resources I have shared over the years and be an easy reference point for future learners.</p>

<h3>Ruby Koans</h3>

<p><a href="https://www.rubykoans.com">https://www.rubykoans.com</a></p>

<p>This is my go to recommendation for learning the Ruby language from scratch. You&#39;ll complete a number of exercises in the form of tests. Your goal is to get each test to pass by writing Ruby code. As with most $language Koans library, it starts off very easy and then ramps up in complexity.</p>

<h3>The Odin Project</h3>

<p><a href="https://www.theodinproject.com">https://www.theodinproject.com</a></p>

<p>Specifically the Ruby portion of the &quot;Full Stack Ruby on Rails&quot; course. This course covers off almost all of the basics that you would need to be productive with Ruby in a project that isn&#39;t super complex.</p>

<h3>Ruby Weekly</h3>

<p><a href="https://rubyweekly.com">https://rubyweekly.com</a></p>

<p>One of the joys of the Ruby world is that someone is coming out with a Cool New Thing™ almost all the time. This can be hard to keep track of, and I have found Ruby Weekly a great resource to keep on top of all of the happenings in the world of Ruby. There are similar newsletters from the same publisher for other languages, too.</p>

<h3>Ruby Tapas</h3>

<p><a href="https://www.rubytapas.com">https://www.rubytapas.com</a></p>

<p>These timeless videos from Avdi Grimm illustrate the <em>why</em> behind a lot design patterns we employ every day, using real examples to help solidify the benefit of following these patterns.</p>

<h3>Blogs</h3>

<ul>
<li><a href="https://solnic.codes">Piotr Solnica</a></li>
<li><a href="https://sandimetz.com/blog">Sandi Metz&#39;s blog</a></li>
<li><a href="https://www.schneems.com">Schneems</a></li>
<li><a href="https://tenderlovemaking.com">Tenderlove making</a></li>
<li><a href="https://thoughtbot.com/blog/tags/ruby">Thoughtbot&#39;s blog</a></li>
<li><a href="https://timriley.info">Tim Riley</a></li>
</ul>

<h3>Podcasts</h3>

<ul>
<li><a href="https://www.bikeshed.fm">The Bike Shed</a> — Out of <em>thoughtbot</em>, general developer experience, challenges and joys, usually covering Ruby and Javascript.</li>
<li><a href="https://www.therubyonrailspodcast.com">The Ruby on Rails Podcast</a> — A weekly chat about Ruby on Rails, OSS and programming in general. Though not always about Rails specifically.</li>
</ul>

<h2>Talks</h2>

<h3>Boundaries</h3>

<p><strong>Garry Bernhardt</strong> — <a href="https://www.destroyallsoftware.com/talks/boundaries">Watch</a></p>

<p>Thinking about the boundaries of and within systems can be tricky for new programmers. This talk does an <em>excellent</em> job of introducing the topic and providing concrete examples to take with you throughout your career.</p>

<h3>Y Not- Adventures in Functional Programming</h3>

<p><strong>Jim Weirich</strong> — <a href="https://www.youtube.com/watch?v=FITJMJjASUs">Watch</a></p>

<p>A detailed introduction to functional programming with Ruby, via first principles of lambda calculus.</p>

<h3>Refactoring Ruby with Monads</h3>

<p><strong>Tom Stuart</strong> — <a href="https://www.youtube.com/watch?v=J1jYlPtkrqQ">Watch</a></p>

<p>Dealing with unwieldy code using monads to help simplify presence or absence of data in our apps.</p>

<h3>All the Little Things</h3>

<p><strong>Sandi Metz</strong> — <a href="https://www.youtube.com/watch?v=8bZh5LMaSmE">Watch</a></p>

<p>Another journey through a hairy refactoring, this talk takes an ugly section of conditional code and converts it into a few simple objects. It bridges the gap between OO theory and practice and teaches straightforward strategies that all can use to improve their code. Also, checkout Sandi&#39;s book at <a href="https://www.poodr.com">Practical Object-Oriented Design, An Agile Primer Using Ruby</a>.</p>
]]>
      </description>
      <link>https://dnitza.com/posts/ruby-learning-materials</link>
      <guid isPermaLink="true">https://dnitza.com/posts/ruby-learning-materials</guid>
      <pubDate>Mon, 30 Jan 2023 08:37:22 +0000</pubDate>
    </item>
    <item>
      <title>Deploying a Hanami 2.0 app to fly.io.</title>
      <description>
        <![CDATA[<p>I have a couple of small hobby apps that were on Heroku&#39;s free tier (RIP) that I have now moved over to Fly.io. Fly.io has a migrate from Heroku process for Rails apps, but as far as I could see, not for any Ruby app. So I had to do things manually, and while deploying to Fly.io is super simple, there were a couple of small things that tripped me up that I thought I&#39;d write about here in case any one else is also trying to deploy a Hanami 2.0 app to Fly.io.</p>

<h2>Getting setup</h2>

<p>After you&#39;ve setup an account on fly.io and have&nbsp;<a href="https://fly.io/docs/hands-on/install-flyctl/">installed the command line app</a>, you can then follow the docs for&nbsp;<a href="https://fly.io/docs/languages-and-frameworks/ruby/">setting up a Ruby app</a>.</p>

<p>While running through the&nbsp;<code>fly launch</code>&nbsp;setup, you should get the option to setup a Postgres database. If you do, the URL will be available as an environment variable in&nbsp;<code>DATABASE_URL</code>.</p>

<p>Once&nbsp;<code>fly launch</code>&nbsp;has completed, you should have a new&nbsp;<code>fly.toml</code>&nbsp;file in the root of your project.</p>

<p>Before deploying your application, you&#39;ll need to make a couple of changes specific to Hanami 2.0. The&nbsp;<code>fly.toml</code>&nbsp;below is where I landed in terms config for a small, simple app. The biggest changes from the generated file are as follows:</p>

<ul>
<li>on deploy, defines a release command to run database migrations (at the time of writing, migrations are not in Hanami 2.0 by default)</li>
<li>defines the web process as&nbsp; <strong><code>bundle exec puma -C config/puma.rb</code></strong></li>
<li>sets the Hanami environment</li>
<li>configures the processes to run (as&nbsp;<code>[&quot;web&quot;]</code>) and the internal port to listen on</li>
</ul>

<script src="https://gist.github.com/dNitza/649ba8b22de794e1d0c8724c3c567edd.js"></script>

<h2>Deployment</h2>

<p>Once you&#39;ve filled out your&nbsp;<code>fly.toml</code>, you can run&nbsp;<code>fly deploy</code>&nbsp;to send your first build to fly.io.</p>

<p>If you&#39;re familiar with Heroku, there&#39;s a similar build and deploy log on fly.io. Though I have found it to be not as detailed as Heroku, it has been enough to surface any config errors and correct them, so keep an eye on it as it&#39;ll surface any obvious issues.</p>

<p>Once your deploy is working, you likely wont need to change anything for a little while, so, if you&#39;re hosting your code on Github, you can setup an action to build and deploy on a successful build.</p>

<p>An example GitHub action to build and deploy a Hanami 2.0 to fly.io</p>

<script src="https://gist.github.com/dNitza/2cb744267dcf567b1f3aff3db85f17fe.js"></script>
]]>
      </description>
      <link>https://dnitza.com/posts/deploying-a-hanami-20-app-to-flyio</link>
      <guid isPermaLink="true">https://dnitza.com/posts/deploying-a-hanami-20-app-to-flyio</guid>
      <pubDate>Sun, 22 Jan 2023 06:29:33 +0000</pubDate>
    </item>
    <item>
      <title>Managing dotfiles.</title>
      <description>
        <![CDATA[<p>It&#39;s not often that I get a new computer, but on the odd occasion that I do, I like to have all of my config prepared and ready to copy over!</p>

<p>To achieve this, I am using <a href="https://www.chezmoi.io">chezmoi</a> and GitHub to keep an up to date snapshot of <a href="http://github.com/dnitza/dotfiles">my dotfiles</a>. My previous tool of choice for managing dotfiles was <a href="https://github.com/thoughtbot/rcm">rcm</a>, but chezmoi offers things like password manager integration, scripts, and templates to customise dotfiles based on the machine I am setting up, making it just that little bit more useful.</p>

<p>The initial setup of chezmoi is super straightforward:</p>

<pre><code>chezmoi init
chezmoi add ~/.my-first-file

chezmoi cd
# follow your usual git workflow to add a remote and push
</code></pre>

<p>Then, to pull down and apply your dotfiles on another machine, run:</p>

<pre><code>chezmoi init --apply $github_username
</code></pre>

<p>From here, the workflow of adding and updating files is essentially: - Edit the source file on your machine - Then, <code>chezmoi add ~/.file</code> to commit and push to GitHub</p>

<p>The chezmoi FAQ lists <a href="https://www.chezmoi.io/user-guide/frequently-asked-questions/usage/">a couple of other ways</a> you can keep a file in sync, including <code>chezmoi edit</code> and <code>chezmoi merge</code>, but I have found <code>add</code> to be the simplest.</p>

<p>Chezmoi also makes it simple to run scripts when you run <code>chezmoi apply</code>.</p>

<p>You can decide whether a script is <code>run_once_</code> when you first run <code>chezmoi apply</code>, or <code>run_onchange_</code>, which will run every time, provided the file has changed.</p>

<p>The scripts I have at the time of writing are:</p>

<ul>
<li><code>run_once_00_install-xcode-devtools</code> — Does what it says on the tin, installs Xcode&#39;s devtools</li>
<li><code>run_once_01_install_homebrew</code> — Installs Homebrew if it&#39;s not already installed</li>
<li><code>run_onchange_after_brew-bundle</code> — To run <code>brew bundle</code> whenever the <code>Brewfile</code> changes</li>
<li><code>run_onchange_after_configure-macos</code> — To configure a bunch of <code>macOS</code> and LaunchBar defaults.</li>
</ul>

<p>Having a look through the chezmoi docs, you&#39;ll see there&#39;s a bunch more you can configure to meet your specific requirements, but hopefully this has provided a brief insight in to what is possible, and how easy it is to get started with a simple setup.</p>

<p>Once the initial setup is done, the day-to-day to maintain your dot files will look like this:</p>

<p><img src="https://grouse.hel1.your-objectstorage.com/grouse-blog-1/20/12/2024/23f2e66a-adfe-4f35-a3cc-7f658fb47b8f.png" alt="A short flow chart showing how dot files can be managed with chezmoid"></p>

<h3>SSH keys</h3>

<p>My SSH keys are the one thing that are not managed by chezmoi. For that I make use of 1Password. All my keys are stored in its vault and are then made available to the ssh-agent via <a href="https://blog.1password.com/1password-ssh-agent/">1Password&#39;s ssh agent integration</a>. This makes it super easy to generate a bunch of keys for different services rather than using a single key for everything.</p>

<h2>What my chezmoi setup achieves</h2>

<h3>macOS Setup</h3>

<p>As a long-time computer user, my ideal macOS configuration is comprised of years of small tweaks here and there. Once set, those tweaks are normally forgotten until such time as I get a new machine, or use someone else&#39;s computer 😅.</p>

<p>Some of the highlights include: - Installing Xcode&#39;s command line tools - Installing Homebrew as well as all of my Homebrew packages - Finder config: - Disable transparency - Disable window animation - Show hidden files by default - Set window title to full POSIX file path of current folder - When performing a search, search the current folder by default - Disable the warning when changing a file extension - Show the ~/Library folder - Mail.app: - Copy an email address as <code>hello@example.com</code> instead of <code>Hello &lt;hello@example.com&gt;</code> - Shell - Set Fish as the default shell</p>

<h3>Config</h3>

<h4>Fish:</h4>

<ul>
<li><a href="https://github.com/dNitza/dotfiles/blob/main/private_dot_config/private_fish/git_aliases.fish">Set <code>git</code> aliases</a></li>
<li>Set default prompt as starship</li>
<li><p><a href="https://github.com/dNitza/dotfiles/blob/main/private_dot_config/private_fish/aliases.fish">Set a bunch of other aliases</a> (including 2 to fix some of macOS&#39;s flakier processes)</p></li>
<li><p><a href="https://github.com/dNitza/dotfiles/blob/main/dot_default-gems">Install default gems</a></p></li>
<li><p><a href="https://github.com/dNitza/dotfiles/blob/main/dot_irbrc">Setup IRB</a></p></li>
</ul>

<h4>General</h4>

<ul>
<li>Set a global <a href="https://github.com/dNitza/dotfiles/blob/main/dot_tool-versions">tool-version</a> file for Mise</li>
<li><a href="https://github.com/dNitza/dotfiles/blob/main/private_dot_config/private_karabiner/private_karabiner.json#L44-L75">Remap a couple of keys with Karabiner</a>:

<ul>
<li>Caps Lock to delete</li>
<li>Switch option and command on my non-Apple keyboard</li>
</ul></li>
</ul>
]]>
      </description>
      <link>https://dnitza.com/posts/managing-dotfiles</link>
      <guid isPermaLink="true">https://dnitza.com/posts/managing-dotfiles</guid>
      <pubDate>Sun, 22 Jan 2023 06:20:43 +0000</pubDate>
    </item>
    <item>
      <title>Naming things is ~hard~ fun</title>
      <description>
        <![CDATA[<p>Like most people*, I like to name the various devices in the house after a theme. For the longest time, my devices were unnamed, until I moved to Apple&#39;s ecosystem and picked up my first external SSD for time machine.</p>

<p>My chosen theme was the <a href="https://en.wikipedia.org/wiki/Metal_Gear">Metal Gear</a>&nbsp;series, so naturally, the drive was named Solid Snake, and my MacBook was Roy Campbell. Fast forward a couple years and now the lineup looks like:</p>

<ul>
<li>MacBook Pro — Zero<br></li>
<li>AirPods — Sigint</li>
<li>iPad — Shalashaska</li>
<li>iPhone — Fortune</li>
<li>Watch — Volgin</li>
<li>HomePod mini — Quiet</li>
<li>TV — Otacon</li>
<li>Wifi — Outer Haven</li>
<li>And then I have a room light and a lamp named Olga and Sunny respectively :)</li>
<li>... as well as a previous iPhone and AirPods pairing of Ground Control and Major Tom (which were the only ones to break the theme).</li>
</ul>

<p>The only annoying thing is that Air Drop shows the device name as a destination (which makes sense), but doesn&#39;t show who owns the device. A solution to this would be to leave everything as &quot;Daniel&#39;s Whatever &quot; — but where is the fun in that.</p>

<p>*computer nerds</p>
]]>
      </description>
      <link>https://dnitza.com/posts/naming-things-is-hard-fun</link>
      <guid isPermaLink="true">https://dnitza.com/posts/naming-things-is-hard-fun</guid>
      <pubDate>Thu, 05 Jan 2023 05:54:15 +0000</pubDate>
    </item>
  </channel>
</rss>
