Teaching Comparing Strings in Python the Hard Way

Wait, is that actually related to KDE?

If you read between the lines…

Some long-time subscribers may remember that I am teaching math to 10-18 year old students. The COVID-19 situation nearly made me quit and look for an alternative to earn my rent, but my love for the kids and teaching them was stronger. After a few months of shortage, we found ways to responsibly resume the meetings, either online or with safety measures.

When schools were closed, some parents wondered what they could do to drag their offsprings away from computers; playing computer games seemed to be the new all-time favorite hobby. Of course, resistance was expected. Why not turn this interest into something useful? I didn’t expect that kids as young as eight are interested to learn how to create games. But why not? I learned from electronic magazines and books how computers, MS BASIC, and Z80 assembly worked when I was ten, and I am sure I would have been interested with eight, if my classmate had broken his leg two years earlier… But that’s not the story I want to tell.

An Evil Plan and Lessons Learned

Today I was again supporting a just-turned-thirteen year old in his journey to learn Python. Earlier, we had already mastered coordinate geometry to move colorful images with PyQt on key presses, so variables, arithmetics, and conditionals in Python are well understood. We also used lists of strings to write simple “guess the correct answer” games. To automatically label the answers with A, B, C, etc. I showed him ord and chr functions, so he was aware that letters are internally stored as numbers.

But for me learning to code is not only about writing games. What I call “theory” is writing programs that solve seemingly uninteresting tasks. Showing him the specification and a partial implementation of a “orderNumbers” function, today’s exercise was to solve the same task for two strings.

def orderNumbers(n1, n2):
    ''' Decide about the ordering of two numbers
        Result:
        -1, when n1 comes before n2,
        0, when n1 is the same as n2,
        1, when n1 comes after/behind n2
    '''
    if n1 < n2:
        return -1
    elif n1 == n2:
        return 0
    # ...

def orderStrings(s1, s2):
    # ...

Intuitively, he started by typing “if s1 < s2:”, then he paused, and said: “No, this won’t work!”, and erased the faulty line. His understanding of strings is that they are a sequence of letters, each of them represented by a number, so his conclusion was that a single comparison cannot determine the ordering of complete words or names.

I could have simply said “It works, Python is that easy!”, but I didn’t. My evil plan was to make him implement that function the hard way. And I got rewarded, because he had to solve every single challenge this task offers.

  • we cannot use a simple “for c in s” loop, because we have to iterate both strings at the same time
  • we have to abort the loop/return early if the decision is final
  • he also found that “Andreas” incorrectly comes after “AXEL”, which forced us to understand the ASCII table to handle case conversion (I didn’t expect he finds this issue, but I am glad he did)
  • he had to fix the error that made the program abort when comparing “Andrea” with “Andreas” due to missing boundary checks
  • and finally, he needed to fix a wrong result when comparing two identical strings

Here is his final function (reformatted and variables renamed for brevity, his version was a bit messy).

    def orderStrings(s1, s2):
        ''' Decide about the ordering of two strings, ignoring case
            Result:
            -1, when s1 comes before s2,
            0, when s1 is the same as s2,
            1, when s1 comes after/behind s2
        '''
        pos = 0
        while True:
            if pos >= len(s1) or pos >= len(s2):
                if len(s1) < len(s2):
                    return -1
                elif len(s1) > len(s2):
                    return 1
                else:
                    return 0

            c1 = ord(s1[pos])
            c2 = ord(s2[pos])
            if c1 > 90:
                c1 -= 32
            if c2 > 90:
                c2 -= 32

            if c1 < c2:
                return -1
            elif c1 > c2:
                return 1
            else:
                pos += 1

Of course his case checks are not really correct; I will have to introduce him to Unicode and Python’s built-in ways to handle case-ignoring comparisons sometimes later.

Lessons learned:

  • using an index variable to iterate through sequences
  • using arithmetic on the ASCII codes to transform letters
  • missing boundary checks can “crash” your program
  • making sure you handle every case of inputs possible

Exercise solved, well done!

Leave a comment