A tale of two large-ish app updates

This week I spent some time working on Earlham CS’s Field Day Android application. It’s the app used by our student-faculty field science researchers to collect data on trips to, say, a glacier in Iceland. I made two substantial changes.

The first was updating our system dependencies. At the start of the summer, Field Day wasn’t a fully modern application. That’s mostly because its development is contingent on the interest levels of students and faculty who (correctly!) have other priorities during the academic year. We experience our only consistent spikes in development during preparation for a trip to Iceland. Even then, we tend to focus on adding or fixing features, rather than major design choices or boring updates. Whatever their benefits, such changes always risk eating up precious time in the short run.

As a result, we had long neglected to update SDK versions, themes, and other app fundamentals. I wanted to fix that before classes resumed this month.

Not being an Android expert (yet?), I relied on a mix of automated tools in Android Studio, manual code tweaks, and careful testing to push the update process forward. Here’s how I described it in my merge request:

I wanted to make us a “grownup” application, by which I mean that I wanted to move us away from as many deprecated tools and dependencies as possible, as far in advance of a field trip as possible. (EDIT: With one exception: these changes do not attempt to resolve the [looming] Google Drive [API] deprecation.)

To that end, this merge request involves substantial changes to build fundamentals like the Gradle version, as well as some Lint cleanup and general tidying. Much of it was done following a simple pattern:

– run a built-in Android Studio update tool (e.g. “Update to AppCompat”)

– change a bunch of details in the code so it builds

– test on the device

– lather, rinse, repeat

Field Day merge request 9

After some tests by myself and a colleague, I approved the merge.

To reward myself for accomplishing that admittedly tedious process (which followed a long, slow battery testing process), I did something more fun.

For a long time I’d wanted to improve Field Day’s UI to streamline the navigation. I made a batch of changes, then submitted the following merge request:

[Field Day’s original creative developers] created a great design palette for Field Day: fun fonts, bright colors, intuitive icons.

I wanted to keep that but update the navigation to reflect the current understanding of our usage model. To that end, this merge centralizes everything onto one screen, miniaturizes our less-used buttons, and puts database and sensors at the forefront.

No specific activities or fragments other than the main screen (and the deletion of the obsolesced sensor screen) have been changed.

I can foresee a future where we do more data analysis and aggregation through the lab notebook, so I’ve preserved the notebook icon for future use.

Field Day merge request 10

The changes in that request took us from this set of two main screens:

Previous main screen (“Sampling” takes user to the second screen)
Previous second screen, containing our sensor and database features

… to this one screen:

Our most commonly-used buttons are on the main screen and fill the entire screen width.

I again checked with my colleague and then approved the request. I’m now working on other issues and have already found the changes to be substantial boosts to the user experience.

This is a sample of my own personal work, but of course building software is a team sport. And it relies on iteration. The original designers of Field Day – current and former colleagues of mine – did a lot of the heavy lifting over a few years building the core logic and aesthetic of the app. As I made my changes in the last few months, I’ve worked to maintain their original design palette while improving usability, performance, and the underlying data model. It’s a useful, specialized, and dare I say fun application, and I want it to keep getting better.

As a closing note about process, I find it sharpens my skills development when I have to summarize my work into prose, as in these merge requests. Writing them requires more precision than a quick chat in a hallway. That’s to say nothing of possible benefits to future developers trying to retrace changes and intentions.

Simple battery testing for common use cases of an Android application

In developing the Android app we use to collect field data, I recently completed a series of tests. We wanted to determine how worried we should be about the Android “Location” tab’s judgment that Field Day is a “High battery usage” app. We wanted to avoid installing and running any additional apps to do so.

Protocol

The simple (if slow) protocol we developed to meet those requirements goes something like this. It’s best when run on multiple, comparable devices at the same time.

  1. Fully charge the device.
  2. Restart the device.
  3. Close all applications other than the app you’re testing.
  4. Disable sleep, screen turn-off – any battery-saving features that apply to your phone but do not fit the use case of Field Day when we actually run it.
  5. Do any setup tasks – in our case, linking to a remote database and an Arduino sensor platform to collect data for us.
  6. Run it for hours not minutes. In our case this was convenient because our app and platform can run without much user interference and still produce valid data.
  7. Close the app.
  8. Immediately go to the phone settings and find battery info using the built-in Android battery analyzing tools.
    1. Check “Battery” (possibly a submenu) as well as “Location”.
    2. Write down the numbers someplace as data.

I did my day of tests using my cheap second-hand Samsung phone and a Nexus tablet. I connected a Bluetooth sensor platform and collected data from it once every five seconds for five hours (this is of course automated in the app). I kept the screens turned on most of the time.

Findings

You’ll observe below that different Android versions (including the extra stuff a manufacturer installs) cause apps and services to report battery usage somewhat differently. My phone, for example, doesn’t list Field Day itself, but does list screen and battery usage, which are (nearly) 100% attributable to Field Day because of the constraints we imposed on our device uses in the early steps in the protocol. (The Nexus did list Field Day, conveniently.)

Craig’s phone

  • 64% remaining after 5 hours
  • Bluetooth (97%)
    • Time on 5h7m35s
    • CPU total 19 sec
    • Stay awake 1 sec
    • Computed power usage 38232 mAh
  • Screen (2%)
    • Time on 4h 4m 20s
    • Computed power usage 1059 may
    • (adaptive display, mid-to-low brightness)

Nexus

  • 40% remaining after 5 hours
  • Screen 22%
    • Time on 5h 5m 0s
    • Computed power usage 753 mAh
    • (adaptive display, max brightness)
  • Field Day 9%
    • CPU Total 6m 19s
    • CPU Foreground 6m 9s
    • Keep awake 3m 25s
    • GPS 5h 2m 23s
    • WiFi packets received 19
    • WiFi packets sent 30
    • Computed power use 317 mAh

In both cases, the “Location” menu continues to report Field Day as “high battery usage” as this issue reports. In practice, battery usage appears to be what we might expect: bright screens and Bluetooth make your battery work more (“I’m shocked, shocked…”).

This test wasn’t the only one we’ve run, but it was the most systematic. It also tested a relatively power-greedy case – normally, we do not keep the screen on at all when we collect a data stream.

Next steps

I’m going to do another round of tests tomorrow, with the screens off to check the more common usage case. However, based on current observations, the app’s battery usage is in line with what I would expect.

There are pieces of this process we should refine – for example, we should have simply controlled for screen brightness rather than let it be such a dominant player in the first place. But after some discussion, we are confident enough to conclude that our battery usage conforms with what should be expected from an app that heavily uses Bluetooth, GPS, cellular, and/or WiFi. We expect and hope that tomorrow’s screen-off test will confirm this.

GitLab

I have a GitHub account, but most of my coding activity is on Earlham CS’s GitLab instance. That includes most of the code I write for use in production, as well as much of the commentary and communication about it.

Today, for example, I submitted a merge request for some work on the Field Day application, one of my favorite projects and one of ECCS’s most successful multi-year collaborative creations.

Software development like this is only one part of my job, and as you’ll see by the squares on my profile I don’t get to do it every single day. But it’s quite rewarding when I do get to dedicate some time to it.

Chrome History Inspector

I’m interested in digital minimalism right now, so I wanted to examine my browser history. Google Chrome on my desktop is the place I spend most of my time online, and it’s also a black box to me. Unlike iOS with its Screen Time feature, I have no obvious window into my browser activity over time. All chrome://history shows is a stream of links you’ve clicked in reverse-chronological order, with no aggregation options. I have a rough idea, but that’s not much to go on.

This weekend I decided I wanted to investigate. At first I thought I’d keep it simple: get my history as a CSV file and open it in Google Sheets. That didn’t work: 15,000 lines is apparently a lot for a web-connected browser-based tool, and it crashed my tab. I could have used macOS’s Numbers, but I realized quickly that my task lent itself to programming better than to a spreadsheet.

As a rough cut (and presented to you now), I made a Python program – code here – that, given a history file of a particular format, produces a graph of your most-visited websites. It makes use of the pandas, matplotlib, and seaborn libraries. The earliest date on my dataset is October 12, 2018. The program produced this graph:

The first thing I noticed in the image was that I clicked into Reddit a lot. I’ve had a Reddit account for less than a year, so I knew I could live without it and I swiftly deleted my account.

What was left fell into a few categories:

  • search/reference: I was surprised and then immediately unsurprised by Google’s supremacy on this list; Wikipedia and StackOverflow are also in this category
  • news: Instapaper, Feedly, Twitter
  • professional tools: Gitlab, GitHub, Wiki, Google Drive, and WordPress
  • entertainment: Netflix, TVTropes, Amazon, Facebook, YouTube – all of which I regulate using Freedom
  • Esquire scored surprisingly high, I think because viewing a slideshow there requires a click per slide and I’ve visited a few of them.

A few caveats about this approach:

  • I’d like something more dynamic, maybe an improved version of some old browser extensions I found on my initial research on this idea. This got the very specific information I wanted, but now I want more.
  • I’ve separated the code that obtains the data (which I didn’t write) from the code that processes it. This way when Google inevitably changes how it manages history data, I don’t have to disturb the processing code.
  • I used this tool to decide my Reddit account should be axed, but it’s arguably unfair to Reddit: I actually read a lot more tweets than Reddit posts, but when you want to expand a Reddit post you click it and it changes the URL. (One minor change I may make is to aggregate twitter dot com, t dot co, and tweetdeck into one row.)
  • This analyzes page visits, not time spent. This, I imagine, would be a much stickier problem. I’d need to have an indicator of when the tab and window were both active, and it would be distorted by the frequent distractions of my office. It would also be a much more useful thing to display. Maybe Google can get with the “digital wellness” moment on this.
  • Future work: group by time. I have a much better idea of when I’m on the Internet than of what sites I’m visiting most frequently over time, so this wasn’t my priority. That said, it’s possible I could learn something interesting.
  • Sites visited in Incognito Mode don’t appear in the history so they also don’t appear on the chart.

Finally, through the lens of digital minimalism, that graph is better than I had expected. There’s not a lot of cruft, the cruft that does exist can be removed pretty easily, and most of the sites provide real value to me. This has been a useful exercise.

Introducing @cooltreepix!

Once upon a time I was a character in a (wholesome!) meme a friend posted to a publicly-visible Earlham Facebook group. The meme, which I’ve stored for posterity here, said that one quality about me is “Takes pictures of cool trees”.

So my Twitter bot is super on-brand.

You can now follow @cooltreepix for pictures of cool trees! They were all taken by me and tweeted once per day. I’ve removed or never added geolocations, but probably 90% are from eastern Montana or the Richmond, Indiana, area.

Details follow for the curious. 🙂

What this bot does

Every day this bot tweets a tree picture.

That’s… that’s it, it tweets a picture containing one or more trees. Sometimes the tree will be the subject of the picture. Other times it will be a picture where the tree somehow accentuates the main element, e.g. fall color. The images are of varying quality but most were taken by my iPhone (currently an iPhone 7).

I kept it simple. I didn’t (and still don’t) want to collect your data or do much in the way of analytics. I just wanted to make a simple non-spammy bot that tweets a nice picture once a day.

Process

Here are the steps I followed, roughly, so that you can try your own:

  1. Create a Twitter developer account. I was doing this for education and with no intent to collect data etc., so I had no problems at all in creating the account.
  2. Create your app. If you’re not going to use it for your own account – i.e. if you want to allow the app to tweet on an account other than the @username of your developer account – make sure to enable “Sign in with Twitter”, though there are some more complex ways to do this if you have a specific reason to try them.
  3. Get a cloud-based server to set up your dev environment and hold any assets you need. At first I used AWS because (1) I needed something guaranteed to always be running and (2) I’ve been wanting to learn AWS. Ultimately I decided to stay on Earlham’s servers, but I’m glad to now have the AWS account and some experience with it. Your environment should have some flavor of twurl to make authentication via terminal easier (for more on that, see the next section).
  4. Write your code. I used Python and the tweepy library. My code is simple. As I describe in more detail below, the setup process was much harder than the coding. If I add any features there will be changes to make, but for now I’m happy with it.
  5. Try it out!
  6. Iterate until it works, fixing or adding one thing at a time.
  7. Maintenance.

When you’re done, most of the time your bot should live on its own, just a bot doing bot things.

Biggest challenges

Coding, it turns out, wasn’t the hardest part. I probably only spent about 10 percent of my time on this project programming. The greatest challenges:

  1. Authentication: This was easily my biggest time burner and the problem that most of the steps above solve. It’s easy to make a bot to tweet to your personal developer account but there are extra steps to tweet to a different account, as I wanted to. Worth noting: after you’ve authorized the app on whatever account you want, check out your twurl environment (e.g. if you’re running Linux, ~/.twurlc) to get the customer and access tokens that are needed to make the bot work.
  2. Image transfer: It turns out that when you have a lot of images they take up a lot of storage, so moving them around (i.e. downloading and uploading them) takes gobs of time and bandwidth. I knew this from a project in college, but if I needed a reminder I certainly got it this time.
  3. AWS: I now have a free-tier AWS account, which took some wrangling to figure out. I decided not to use it for this project in the end, but the learning experience was good. I want to try configuring it better for my needs next time I do a similar project.
  4. Image sizes: Twitter caps your images at a particular size, which was producing errors at the terminal and a failure to post tweets. I eventually used ImageMagick’s convert (via a Python subprocess) to solve the problem.

Notes on ownership

All photos tweeted directly by the bot are mine (Craig Earley’s) unless otherwise noted. Please don’t sell them or use them commercially, as they are intended for everyone’s benefit. Also please give me a photo credit and share this link to my site if you use them for your own project.

If you want to submit a tree photo, tweet it to me @cooltreepix or @craigjearley. If it’s a real picture of a tree, I’ll retweet as soon as I see it.

My logo is from the Doodle Library (shared under a CC 4.0 license) and edited by me to add color. My version is under the same license.

Finally you can put a little something in my tip jar if you want to support work like this.

What counts as making?

As I pursue becoming a maker of things, the two things I want to make are software and writing. If I can spend time making those two things, I’ll be happy.

Obviously you can make more than those things, and for anyone reading this I want to be generous with my definition of “making”. Here’s a non-exhaustive list of what I think we can include:

  • writing, which I mean in the almost limitless fashion that includes “the Great American Novel”, screenplays, and 10,000-word blog posts but also fan fiction, appliance manuals, etc.
  • coding
  • running a home media server
  • starting a business
  • film or video
  • music, including performances of pieces you didn’t yourself write
  • a podcast
  • handcrafts
  • woodworking
  • metalworking
  • social media posts are borderline, but if you’re doing it in a systematic way and not just for marketing, chit-chat, or blowing off steam, it counts
  • photography (it’s not all phone snaps)
  • illustration
  • learning to cook new meals or improve your skills in what you already know how to cook
  • science experiments
  • home decoration and design

I would also consider the following as making, or at least making-adjacent. Their inclusion may be controversial as they arguably don’t produce some external artifact for others’ enjoyment, but for many people they offer the same types of satisfaction that making does: purpose, fulfillment, legacy, impact, sense of progress, agency, a valuable way to spend finite time on this earth.

  • athletics, or at least fitness with emphasis on improvement, technique, and maybe participation in some group activity
  • teaching

This should go without saying in the age of open-source software and ubiquitous blogging/microblogging, but whether or not you make money from something does not factor into whether it’s “making.”

A plan for making

In my last post I discussed why I’ve concluded that I need to make more. This post looks forward, and it will explain how I plan to start and continue creating.

In that pile of articles I’ve saved up, I can find a lot of advice about habits, systems, workflows, etc. But I’m mostly drawing from the work of Cal Newport, whose Deep Work and So Good They Can’t Ignore You both changed my outlook on work within the last year or two.

I’d note that all, some, or none of this may be broadly applicable:

  • If you’re already hold a job where making is what you do from 9-5 (the sort of job I’d like to have!), this probably won’t be especially useful.
  • Since this is a post about getting started in a creative habit, it’s also focused on projects conducted by an individual, not a team. The patterns in those two kinds of work are different.
  • I’m posting this mostly as a public expression of a private commitment to myself.
  • This is by no means binding. I’ll be revisiting this list in the future and adjusting my methods based on how well it actually works for me.

The short version, as I currently imagine it, is this:

  • Clear distractions.
  • Make the time and space.
  • Do the work.
  • Share it, with 0 or more people.

That’s it!

Details follow on how to go from that design to an implementation that can last.

0. Clear distractions.

This is item 0 because it’s not actually part of the creative process. That’s very important to say: clearing distractions is about getting out of your own way, not a method of creating. By itself it does not add value (economic, social, personal, spiritual, etc.) to the world. It’s about making space for what you actually want.

Action steps:

  • Freedom: Run the Freedom app. I’ve experimented with it for a year now, and the way that works best for me is to run a fairly strict blocker on a repeating schedule – but not to disable quit during sessions. This lets me look things up if, say, I’ve blocked Reddit but there might be good information on /r/learnprogramming about something I’m learning. I recommend blocking social media, news, and all sites you mindlessly click to when you want to waste time while you’re working.
  • Pick simple tools or tools you know well. Choosing the “perfect” tools can easily chew up a lot of your time. (Of course, if there is a tool that is THE tool for what you’re doing, learn that.)
  • Have your environment ready. The time to pick your instrumental Spotify playlist for audio while you’re working is before the work session, not during it. Ditto lighting, seating, temperature, colors in your terminal, choice of word processor, etc.

1. Before making anything else, make time and space.

Put creating on your calendar. If necessary, consider it an appointment. A creative work session is a time during which you are unavailable except in emergencies. No email, no phone, etc.

Actions to take:

  • Add it to the calendar. I’m going to start with 30 to 60 minutes each day, longer when I need more depth. I’d like to go for 90 on, say, weekends. If you can do this in such a way that it accomplishes tasks for your job, that’s excellent and you could probably do it during work hours. If not, make sure to reserve the time outside work, at least a few days a week. Either way, add it to your calendar and stick to that appointment.
  • Be prepared to fill that time. Don’t arrive unprepared to make. “Write”, for example, might be on the calendar, but if you don’t know what to write, the path of least resistance is to hem and haw and not do anything. Prevent that by knowing what you’re going to work on in advance.

2. Do the work.

This is the only stage that counts. It’s the only non-subjective item on this list.

None of the rest of this matters, for purposes of this project, if it doesn’t ultimately help make this happen.

At the chosen time, in the chosen environment, and for the duration of the chosen time, create. Words and drawings onto paper and screen. Code into editor. Sound waves into air.

Action steps:

  • Make!
  • If you have 60 to 90 minutes, use the time to do deep work: focusing on a single cognitively-demanding task for an extended period of time to improve your own skillset. This may require a little more advance planning in the previous step.

3. Share your work, with 0 or more people.

For me, this is the hardest stage. Part of my motivation for publishing this list is to let me isolate it, in my mind, from the other stages.

All those questions I listed in my previous post about managing feedback, responses, comments, traffic, etc.? They all crop up now, when you “put it out there” for others to see.

This is also the most difficult stage to turn into action steps, because it varies so much by your medium. But here’s my attempt:

  • Create a site or blog for each part of your work. I’ve got my personal digital home right here, a developer blog hosted at GitHub, both private and public code repos on different platforms, and a blog for my writing.
  • Automate sharing tools, if you’d like. I’d rather pick and choose as I go, but the option to share across social networking accounts exists on most web tools you’d want to use.

Some caveats

There are a ton of lifestyle factors that matter to making as well: manage time well, stay healthy, exercise, get outside away from screens, etc. But for the specific work of making, this is the path that makes most sense to me: clear, schedule, work, share.

I also believe readers ought to willfully disregard any of this that won’t work for them because of personality, lifestyle, schedule, etc. But if you’re wondering how to start putting in the work, this is one possible framework that might at least provide some useful tactics.

On making

For a long time I’ve been operating in one mode of learning and personal growth, and I’m in the process of pivoting to another.

Since sometime in college, I have followed what we might call a consume-curate-archive model of learning. I read online frequently, so I have hundreds of articles saved to Instapaper about a range of topics. I read books. I guzzle podcasts, and I use Tweetdeck to follow a lot of carefully-refined Twitter lists about politics, tech, culture, and more.

This is a process of finding inputs, highlighting or annotating them, and stashing them away. It’s a kind of orderly digital hoarding, and I’ve been doing it for a long time.

At the same time, my creative output slowed to a crawl. I’m not sure if there was a direct 1-to-1 tradeoff of those two (any writer will tell you that reading is essential to better writing), but certainly I was gathering a lot of inputs and not using them to produce rare and valuable outputs.

There are a lot of reasons for that. Basic lifestyle is one factor, especially after college. Unsure what I wanted to do, I felt directionless and frustrated. After being across the country for college, I moved back home and worked for myself for about a year. (A contemporary indicator of the simmering desire to make was the joy I felt designing and creating my solopreneur business.) After a few months on my current job, at Earlham, I feel better consistently. Maybe I should have created in spite of my mood, but I didn’t, and now I feel like I can again.

I also experience typical human anxieties about reactions. Some of my work can be private, but some should be public, and how do I sort which is which? We know deliberate practice, “learn by doing” guided by consistent feedback, is the only path to mastering something, but how do I implement that? Say I decide writing is what I want to do. Do I have to spend all my time keeping track of website comments and worrying about traffic? Can I make money from it? Should I? I feel like I have a lot to say, but am I then violating my own privacy just to get attention or make a buck? If I succeed, do I have to be a “public figure” (I don’t want to be)? There’s a halting problem to be modeled here, and my conjecture is that this kind of thinking never terminates on its own.

All of that was enough to smother my creativity for a long time.

That’s no way to live, to work, or to grow professionally. That’s why, in the last few months, I’ve been gradually switching into a creation-first model of learning – a model in which producing some artifact (at a high level it doesn’t much matter what kind) is the guiding principle, rather than accumulating a lot of neatly-arranged inputs.

This is the first in a series of posts I’ll be writing about making. In my next post, I’ll share how I’m planning to orient myself, on a regular basis, into a pattern of creativity.

Looking for bugs in all the wrong places

When I took Earlham’s Networks and Networking class, we implemented Dijkstra’s algorithm.

Dijkstra’s algorithm is an algorithm for finding the shortest paths between nodes in a graph, which may represent, for example, road networks. It was conceived by computer scientist Edsger W. Dijkstra in 1956 and published three years later.

The algorithm exists in many variants; Dijkstra’s original variant found the shortest path between two nodes, but a more common variant fixes a single node as the “source” node and finds shortest paths from the source to all other nodes in the graph, producing a shortest-path tree.

Wikipedia

I got my implementation (in Python) close, but not quite right, by the time the deadline hit for submission.

And more deeply than I have for any coding project up till now, I always felt bad about falling short on this one. I trained much of my perfectionism out of me to become a CS major and decent programmer, but this particular hangup hit hard. In hours of work, I couldn’t find where my implementation was going wrong, or why it was going wrong so consistently.

I submitted my code for grading unhappily, then put it down to focus on other things. I felt like I’d reached my upper limit as a programmer (though I knew in my mind that this was probably not the case). The source code lay quietly in a directory for a couple of years.

Today I’m happy to report that – judged exclusively my own irrational metric, success in implementing Dijkstra’s algorithm – I underestimated myself.

This semester I’m helping teach the same networks class. Since we may assign Dijkstra’s algorithm at some point, I decided to review my old code and maybe try to make it work.

I spent about two hours today, Sunday, reading that rusty old code, tweaking it, running the new version, and parsing its output. I added debug statement after debug statement. I ran it on different input files.

Then I noticed a mistake in the output. Somehow, an edge of weight 1 was being read as an edge of weight 100000000 (the value I used to approximate infinite cost, i.e. the cost of moving directly between two nodes that do not share an edge). In effect, that edge would never be part of a shortest-path between any combination of source and destination. This was bad, because in fact that edge was part of many such shortest-paths in this network.

I went back to some of the most basic pieces of the code and found a possible problem. It was small, easy to fix but hard to detect. I edited a single line of code and ran the program.

It worked.

As it turns out, I’d gotten the implementation right. The core of the assignment, Dijkstra’s algorithm itself, had worked on the input it received.

Visually, here’s the network I had:

And here’s the network the program thought I had:

So what did I get wrong?

Believe it or not: counting.

You see, I had set a variable for the number of nodes N in the network graph. I also had a two-dimensional list describing the network, where each item in the list was an edge in the graph, itself represented by a list containing two nodes and the weight to go between them. Crucially, there are at most N^2 edges in such a graph.

My fatal flaw: rather than saying “for each possible edge in the network, read a line from the file”, I said, for each node in the network read a line in the file. In other words, for my graph with up to N^2 edges, I would only be loading the data about N of them. In this case, the program read only 4 lines, and the edge of weight 1 was described on the 5th line.

(This might have been obvious had I tested the code more thoroughly on one of the larger network files we had. Alternatively, the combination of edges being missed might have obscured the result a lot. A copy of the same input file, but with the lines reversed, would have been the most useful second test case.)

After switching the variable that the index would be checked against, everything worked as I expected.

The code still has problems. I intend to clean it up and streamline it. But the implementation now consistently returns correct output.

The concrete lessons of this experience for me are:

  • Don’t just write debug statements. Write clear and meaningful debug statements. Be specific.
  • Check your I/O, indices, and other such basic features of the code. You can have the greatest algorithm of all time (though I did not!), but if the program isn’t handling exactly what you expect it to, you won’t get the results you want.
  • Vary the input. Vary the input. Vary the input.
  • Don’t let one project, however important or complex or valuable, determine your feelings about your personal skillset.

Finally, while I emphasized the specific and silly programming error here, failure to count correctly wasn’t a root cause of my mistake. The root causes were factors removed from coding altogether: rushing to completion and getting too tangled in the weeds to think holistically about the problem. I don’t think it’s a coincidence that I solved this problem after spending a lot of time in my life disciplining those tendencies.