4 minute read

Daily dispatches from my 12 weeks at the Recurse Center in Summer 2023

What an amazing five days. Don’t pity me when I say this, but this week was easily the most social week I’ve had in years. Years. Wish I could say it’s just been the last three – between Covid lockdown and first-time parenthood… yeah, I haven’t done shit. But no. The reality is I haven’t been so socially embedded probably since around the time that I advanced to candidacy, and that was in 2016. After that I hunkered down on dissertation research and withdrew to the confines of an aptly named “monk cell” at the research institution that would become my home for years to come. I like solitude and quiet, so it was all good. But in retrospect I’m beginning to suspect I was waaayyyy more isolated than was good for me. So that’s a thing. Never would have guessed that a community unfolding primarily on a message board could feel so vital, but it does, and it is, and I’m happy for it. Attitude of gratitude baby!

Two cool things that happened today:

Thing 1: Closures

I was doing a little pairing with another recurser, working on my implementation of a graph. And, well, I learned a thing or two.

The main thing I learned was the importance of closures. Here’s an example of my very basic GraphNode class, with a recursive dfs() function.

class GraphNode:

    def __init__(self, value=None, neighbors=None):
        self.value = value
        self.neighbors = neighbors if neighbors != None else []

    def __str__(self):
        out = str(self.value) + ': '
        out += ' '.join([str(neighbor.value) for neighbor in self.neighbors])
        return out

    def dfs(self, target, visited=None):

        # Base case: current node == target
        if self.value == target: return True

        # Initialize set to keep track of visited nodes
        visited = visited if visited else set()

        # Add current node to `visited`
        visited.add(self)

        # Visit current node's childen and recursively call function
        for neighbor in self.neighbors:
            if neighbor not in visited and neighbor.dfs_orig(target, visited): return True

        # If after all recursive calls target isn't found, return false
        return False

Because graphs can contain cycles, when performing a depth-first search it’s critical to keep track of which nodes you’ve already visited so we can avoid them in the future. Otherwise, we risk falling into an infinite loop (a certainty if a cycle is encountered before the target is found). My solution was to include a visited parameter that defaults to None. On the method’s initial call where visited == None, I re-initialize visited as a set. And at that point we can add the current node to the set, since we’ve visited it. In recursive calls, we can pass this set as an argument, so that way we have an ongoing list of visited nodes we know to avoid.

It works, but I learned it’s probably somewhat bad practice – a work-around to deal with the fact that an empty immutable like a set cannot be a default function argument.

Turns out a better solution is to use a closure to encapsulate the visited set within an outer function. This outer function would then return the inner DFS function configured to use the visited set. Here’s how the new version looks:

class GraphNode:

    def __init__(self, value=None, neighbors=None):
        self.value = value
        self.neighbors = neighbors if neighbors != None else []

    def __str__(self):
        out = str(self.value) + ': '
        out += ' '.join([str(neighbor.value) for neighbor in self.neighbors])
        return out

    def dfs(self, target):

        # Initialize set to keep track of visited nodes
        visited = set()

        # Inner recursive dfs function
        def dfs(node=self, target=target, visited=visited):

            # Base case: `node` == `target`
            if node.value == target: return True

            # Add `node` to `visited`
            visited.add(node)

            # Visite neighbors of `node` recursively
            for neighbor in node.neighbors:
                if neighbor not in visited and dfs(neighbor, target, visited): return True

            # If `target` not found after recursive calls, return false
            return False

        # Call inner function
        return dfs()

Now, the visited set is managed within the closure, keeping the outer function’s interface clean and its usage clearer. It also ensures that the state (i.e., the visited set) persists across recursive calls in a more intuitive manner.

Thing 2: Squirrel Poems

I’ve been looking forward to Data Disco all week, and it delivered! We played around with the NYC Open Data Squirrel Census dataset, which is a weird one, for sure. It’s about 3,000 squirrel observations made in Central Park during a two-week period from 10/6/2018 - 10/20/2018. It’s got long. and lat. coordinates, other locations information, details about age and colors of observed squirrels, whether it was making any squirrel noises of note (kuks, quaas, and moans) or speaking squirrel body language (tail twitches, tail flags) or doing anything else of note (running, chasing, climbing, eating, foraging). It also includes some user-supplied text fields, which add extra detail and color about the squirrel’s location, it’s activities, and its interactions.

We started with a simple scatter plot to just get a sense of where the observations were made. Evidently squirrels that approach observers are found throughout Central Park.

Squirrel map

Then we developed a poem generator based on the user-supplied fields, which resulted in honestly some pretty damn rich material:

Fat!
bench
climbing (over small wire fence then on the ground)

eating upside down on tree trunk
on trunk
Off-white, decorator's white?

hanging upside down to get a nut
large white highlight
posing

Between park and main drag
eating (on branch)
skinny tail

chasing squirrel #3
edge of park @ 98th (north)
Drawing included.

tree branches
walking
White ring on short tail

bringing green leaves to nest
on fence
Very far, so appx