Fun with self-referential lists

Fun tricky things you can do after appending a list to itself.

Ah, the self-referential list:

a = [1, 2]
a.append(a)

a[-1] # => [1, 2, [...]]

The last item of a is a reference to a, so a[-1] is a and a[-1][-1] is a, etc., etc.:

a[-1]              # => [1, 2, [...]]
a[-1][-1] # => [1, 2, [...]]
a[-1][-1][-1] # => [1, 2, [...]]
a[-1][-1][-1][-1] # => [1, 2, [...]]

Of course, you should never actually do this in real life but it's fun to think of different ways to use a circular list. Especially because programming circular things can be pretty annoying.

  • We don't want a[-1] to be [1, 2, [...]]
  • We want it to point to a[0] (which evaluates to 1)

What we really want to do is to create a sort-of linked list by somehow appending a reference to the beginning of a... but we're lazy.

We need some way to flatten the list. Except we can't flatten the list because the list has an infinite amount of lists in it.

Instead, we'll write a generator that takes a self-referential list and behaves like a circular linked list:

def circular_generator(infinite_list):
for x in infinite_list:
if isinstance(x, list):
yield from circular_generator(x)
yield x
>>> g = next_item(a)
>>> next(g)
1
>>> next(g)
2
>>> next(g)
1
>>> # ...etc.

Here's an example using circular_generator to decipher messages encrypted with the Caesar cipher:

plain_alphabet = list('abcdefghijklmnopqrstuvwxyz')

rotary_alphabet = plain_alphabet[:]
rotary_alphabet.append(rotary_alphabet)

rotated = circular_generator(rotary_alphabet[3:])

decipherer = { # {'a': 'd', 'b': 'e', ...}
plainchar: next(rotated)
for plainchar in plain_alphabet
}

Edit 3/7: I had to fix the code in my circular_generator function because it didn't work. At all. Whoops!

Also, you'll probably notice that we don't actually need a self-referential list to accomplish any of this. I'll admit that this was an attempt to get students to think about self-referential lists and imagine a possible, practical application.

Here's the circular_generator function that takes in a normal list:

def circular_generator(l):
for x in l:
yield x
yield from circular_generator(l)