How I Make Algorithms

I’ve been reading Clive Thompson’s Coders book, and it’s caused me to reflect upon the process I use for coming up with algorithms. I’ve come to realize this is one of those things which I do, but is incredibly difficult to explain to others– so this post is my effort to encapsulate “my process.”

  1. Become intrigued/obsessed with something (e.g. anagrams)
  2. Discover an online anagram finder
  3. Ask myself, “How would I go about building my own anagram finder?”
  4. Realize I have no idea, and beat myself up for not knowing
  5. Forget about building my anagram finder project
  6. Weeks later, coincidentally stumble across:
    1. Fermat’s notes on anagram comparison using prime numbers trick
    2. A code challenge to create anagram tester function
  7. Turn Fermat’s concept into a quick & dirty JavaScript function, areAnagrams().
  8. Feel good about my areAnagrams() milestone/accomplishment
  9. Realize Fermat approach hits integer bit-limit with medium length words.
  10. Convert areAnagrams() from prime numbers to “wordDNA” approach (i.e. case-insensitive letter sorting)
  11. Feel good about resolving limit in areAnagrams() function.
  12. Days later, realize wordDNA approach can be used to create a “rainbow table” of dictionary list– and now you’ve got the basic building parts of an online anagram finder.

The Situationally Aware Algorithm

Mountain lion lounging in a cottonwood tree during the heat of the day.

Developers often pick an algorithm for its speed, but sometimes it makes more sense to dynamically pick an algorithm based upon the resources available at a particular moment. For example– the “Update All” feature for installing available updates for applications on Android Lollipop devices. “Update All” will download and unpack available updates one after the other, without waiting for the first update it unpacked to be installed. This parallel work queue approach makes sense from a time-saving perspective . . . until you try doing it on a phone with only 400-500 MB of storage space.

When storage space is limited, the processes of downloading the next update and unpacking the last downloaded update rapidly fill up the storage, usually faster than the installing process can use and dispatch the files. Suddenly, you have multiple update error messages in your notifications due to “insufficient space.”

Now, our annoyed Android user has a choice:

  1. Clear their cached data in “Settings -> Storage” and hope this frees up enough space for the “Update All” feature to work properly.
  2. Update each Android app one at a time.

But what if the “Update All” process checked the available storage space before it started downloading updates, and based upon a particular threshold, decided between downloading all the available updates in rapid succession or downloading the first available update and waiting for it to be updated and purged before proceeding with the next download?

The idea of a “situationally aware algorithm” is not new. We can dynamically apply different sorting algorithms to data by determining the size of the dataset beforehand. We can determine how much free space remains in storage on an Android device by using the getFreeSpace() method. We have the means to inspect the resources available to us before we decide to tackle a particular problem, and yet more often than not we plunge ahead with a predetermined “one size fits all” approach.

I blame satisficing. I’m also guilty of it.

When I walk in the woods in my neighborhood, I never look up to see if a mountain lion might be perched on a limb I’m about to pass under. Mountain lions are a non-issue for me, and walking beneath tree branches is an experience which I take for granted will be uneventful.

Until, of course, it isn’t. 😉

Similarly, if we apply an algorithm for sorting large datasets to a smaller dataset, it will still give you results. It’s only when we come across a dataset which completely breaks the algorithm and get no results back (e.g. the dataset is so large it consumes all available RAM) we begin to question if the currently implemented approach to the problem is the correct one.

On the bright side, I think the adoption of practices like User Stories and Test Driven Development– particularly in team approaches where the stories, tests and code are written by different people– is gradually pushing development beyond the satisficing “just make it work” mentality into a more nuanced, situationally aware paradigm.

In the meantime, the best we can do is keep learning our craft, be patient with the status quo, and . . . start checking tree branches for mountain lions before we walk under them.