Linked list recursion: A smidge of functional programming in Ruby

Nathan Sanders‘ recent post about functional programming in Ruby includes some advice about not trying to force too much mapping and folding into Ruby. Here’s how he expresses it: “Ruby! OOP! Remember?”

But I couldn’t help myself. I spend a good deal of time these days in student mode, programming in scheme, and I really like the elegance of recursion, especially when it’s used on data structures that support recursion and iteration, like linked lists. So I indulged myself a bit while writing some practical Ruby code. I marked it mentally as something I would probably have to come back to and replace, but a funny thing happened. I still like it. I think it works really well.

Linking up a set of objects

Here’s the gist. I’m modeling grading systems for students. Each system has a set of possible grades (as in scores). Each grade has a letter value—a string like “A” or “B+”—and a range of number grades—92 to 98, or 88 to 90. Each student is being evaluated against some such set, and we’re assuming there could be more than one set (since the students go to different schools). So every grade has to have a string for the letter grade. But how about the number ranges?

When I imagined the process of matching a number score to a letter grade, it seemed simplest to give each grade a minimum number grade, a lower boundary. Each grade, starting with the highest one, is examined. If the score is higher than the grade’s minimum, then we’ve found the grade. If not, we should examine the next-highest grade. It’s an iterative process that could be handled with find. But, scheme-infected as I am, I decided to give each grade object a link to the next grade object. Then I could write recursive methods for the grade object with a classic scheme three-condition construct: 1) Is this the last object? Then we’re done. Return this object. 2) Is this the object that we’re looking for? Then return it. 3) Are there any more objects? Then try the next one.

Here’s the method. I’m sweating just a bit to expose this, since it may still prove to be stupid or naive, but nothing ventured means nothing gained.

  def letter_grade_for_number(grade)
    if @nxt.nil?
      self.letter_grade
    elsif grade >= self.min_number_grade
      self.letter_grade
    else 
      @nxt.letter_grade_for_number(grade)
    end
  end 

Enumeration in the data, not the methods

What I like about this setup is that there’s no enumeration code in the method. There’s no each or find and no blocks. The enumeration is built into the data structure. So each method like letter_grade_for_number can be constructed this way. The alternative, it seems to me, would involve repeating that enumeration code in each and every method. Not to mention kind of messy look-aheads or special cases that might arise in other methods.

There’s also an advantage in the way other objects use this grade object. The app I’m building will associate grade ranges with more than one kind of object. Instead of each of these client objects needing to maintain their own collections of grades and methods for arraying them and searching them, all they have to do is pass a message to an object, their grade object. All the searching and finding is encapsulated behind this single object. Doesn’t that feel more object-oriented?

Data Storage

The linked list is a bit more complicated than a simple array. You might think it’s tough to handle persistence. But not really. I defined a belongs_to/many ActiveRecord relationship between a client class and the grade range class. Once you’ve retrieved a set of grades, linking them together is this easy:

    ranges = ranges.sort_by(&:min_number_grade)
    ranges.inject(nil) {|prev_range, range|
      range.nxt = prev_range
      range
    }

The inject returns the final (highest-score) grade, which is the head of the linked list. Ruby’s functional capacities are considerable, and they make recursive and iterative operations natural.

Yeah, I know: O(n) no! growth!

Each recursive call to a @nxt object’s method means another method call on the stack and Theta-n growth for the memory space needed for the operation. I wouldn’t do this with a list that could have thousands of members. With a single grading system, however, I can be confident I’ll be handling a dozen or so at most.

Hybrid Languages

Last week I posted about learning how to program in an object-oriented fashion in scheme, about which one could say, “Lisp! Functional! Remember?” Using a recursive structure in Ruby turns out to be less exotic than one would think. I’m certainly not going to start programming in Ruby as if it were scheme (or vice versa), but I have decided to treat each as a hybrid language. One in which it’s probably easiest to use one style of programming 80% of the time; but in the remaining 20% of cases, it isn’t hard to do something else if you decide that would be best.

One thought on “Linked list recursion: A smidge of functional programming in Ruby

Comments are closed.