Sharpening Your Tools: Advent of Code with iRules

Today I hosted a pop-up DevCentral Connects live stream to code live with the community. These shows are always a lot of fun...and a little scary as you never know what failure is just around the corner! That's ok, though, learning and laughing together is worth it! On this show, I highlighted the annual Advent of Code, a puzzle-solving challenge that presents a two-part challenge each day of advent in a crazy story featuring elves, reindeer, and occasionally a chaos monkey or two.

The experience is fantastic because it's interesting, challenging, and coding language agnostic. I have identified areas of weakness in my skill set (looking at you, data structures and algorithms) through the challenge, and today thought it'd be fun to revisit this year's day one exercise and see if we could solve it in an iRule!

The TL;DR of the challenge was to take a list of numbers as the calories in each elf's snacks. Two newlines separated each elf's list of snacks. Once you had summarized the calories for each elf, the solutions were to find the elf with the maximum amount of calories (part one) and then to find the total amount of calories for the top three elf totals (part two).

We got to the part one solution during the session, but found while solving for part two that part one had a logic problem that by sheer luck didn't prevent a correct answer. After the show, I found that the way we were splitting the data strings required a final newline after the last data point. Refreshing the iFiles with that data and then re-running the rule from the show returned the right valus. Huzzah!

Solution Steps

  1. Read the challenge and define a game plan
  2. Create iFiles on the BIG-IP to work with

    This could have been done manually, but I have a python script to upload files and create the system and ltm iFiles objects, so I used that.

    import getpass
    import os
    import sys
    from bigrest.bigip import BIGIP
    
    
    def instantiate_bigip(host, user):
        pw = getpass.getpass(prompt=f"\n\tWell hello there, {user}, please enter your password: ")
        try:
            obj = BIGIP(host, user, pw, request_token=True, session_verify=False)
        except Exception as e:
            print(f"Failed to connect to {host} due to {type(e).__name__}:\n")
            print(f"{e}")
            sys.exit()
        return obj
    
    
    if __name__ == "__main__":
        br = instantiate_bigip(sys.argv[1], sys.argv[2])
        for f in sys.argv[3:]:
            file_name = os.path.basename(f)
            ifile_name = file_name.split('.')[0]
            try:
                br.upload('/mgmt/shared/file-transfer/uploads/', f)
                file_data = {'name': ifile_name, 'sourcePath': f'file:/var/config/rest/downloads/{file_name}'}
                br.create('/mgmt/tm/sys/file/ifile', file_data)
                ifile_data = {'name': ifile_name, 'fileName': ifile_name}
                br.create('/mgmt/tm/ltm/ifile', ifile_data)
                print(f'iFile {ifile_name} created.')
            except Exception as e:
                print(e)
  3. Create a couple procs to find sums and max values from lists of data
    proc find_sum { snack_list } {
        set calorie_sum 0
        foreach snack [string map { \{ "" \} "" } $snack_list] {
            set calorie_sum [expr {$calorie_sum + $snack}]
        }
        return $calorie_sum
    }
    
    proc find_max { elf_list } {
        set max 0
        foreach elf $elf_list {
            if { $elf > $max } {
                set max $elf
            }
        }
        return $max
    }
  4. Import and format the data sets for the test and full puzzle data
        # Get and store test or puzzle data
        if { [HTTP::uri] eq "/test" } {
            set aoc_data [ifile get testdata]
        } elseif { [HTTP::uri] eq "/puzzle" } {
            set aoc_data [ifile get puzzledata]
        } else {
            HTTP::respond 406 content "Not an acceptable destination. You Lose, Good day, sir!"
            return
        }
        # get data ready to iterate on
        set aoc_data [split $aoc_data "\n"]
        set aoc_data [split $aoc_data "{}"]
  5. Work out the logic for iterating through the data sets
        # iterate on the snack data
        foreach snack $aoc_data {
            if { $snack != {} } {
                # append snacks list for current elf
                lappend snacks $snack
            } elseif { [info exists snacks] } {
                # hit {}, tally up calories in last elf's snacks and append total to elf list
                set elf_calories [call find_sum $snacks]
                lappend elves $elf_calories
                # unset snacks so new elf doesn't have last elf's data
                unset -- snacks
            }
        }
  6. Solve each part's challenge
        # Solving Part 1
        # find the elf with the most calories
        set elf_max [call find_max $elves]
    
        # Solving Part 2
        # find the three elves with the most calories and summarize
        set top_three [lrange [lsort -integer -decreasing $elves] 0 2]
        set top_three [call find_sum $top_three]
  7. Return the solutions
        # return the data
        HTTP::respond 200 content "\n2022 Advent of Code - Day 1\n\n\tPart one: $elf_max\n\tPart two: $top_three\n\n"

You can find the final iRule here on GitHub. At the end of the day, there isn't anything terribly valuable in this iRule or in counting the calories in elf snacks. The real value is two-fold: 1) taking a problem and working toward a solution and 2) sharpening the tools in your toolbox.

All pop-up shows (like today's) will be published to the DevCentral Connects group two days before a session, so if you want a heads up, make sure to join the group and watch for announcements! What else would you like to see covered in a live coding show? Drop a comment below!

Published Jan 12, 2023
Version 1.0

Was this article helpful?

No CommentsBe the first to comment