De Bruijn Sequences with NetworkX

De Bruijn sequences are an interesting concept to study, with many real life applications.

One interesting application is related to PIN code cracking. For a code to be cracked (in this example), only the last 4 digits must be correct, which means that all the digits typed before will be ignored. In other words: given a stream of digits, a code is cracked when the last 4 digits of the stream match the code.

If we want to crack such a 4-digit PIN code, one possible way would be to enumerate all the different combinations and try them one by one. In that case, there would be 10000 combinations to try (from 0000 to 9999), and for each combination, 4 digits must be typed. This results in a total of 40000 digits to type in order to bruteforce the code with this naive approach.

As we might guess, there must be a clever way to produce a list of digits, such that if entered sequentially, would cover all the possible codes in the minimal amount of digits.

And the answer is yes, there exists such a way, thanks to De Bruijn sequences. De Bruijn sequences are used in many different domains ranging from computer science to neuroscience. More in-depth information can be found on external websites:

1. De Bruijn Sequences on Wikipedia
2. De Bruijn Sequences on Datagenetics

The article on Datagenetics provides a (the only?) 10003 digits long solution to the code cracking problem. It also explains the basic idea of these sequences and how to get to that solution.

Alright, let’s get started and copy the solution below to use it as a reference.

Reference

One might wonder why look at that problem if it is already largely covered online by many great resources?

Well, I thought it would be interesting to try to find a solution, but with a different approach.

First, let’s look at the (trimmed) code provided on the Wikipedia page generating a De Bruijn sequence for the given parameters k and n.

Now let’s verify this code produces an optimal solution. A solution is valid if it contains all the different codes from 0000 to 9999. And it’s optimal if its length is not greater than the reference solution (considered as the best solution).

Good solution? True


At least we find the same result, which is comforting.

But let’s be honest. This code snippet from Wikipedia is quite opaque. It would be nice to develop a more intuitive feeling of the problem.

With NetworkX

Representing this problem as a graph problems makes it more intuitive to understand. It is possible to represent the codes as the nodes and the digits (typed one by one) as the edges of a graph:

De Bruijn sequences have this great property of being generated by finding an Eulerian cycle from a De Bruijn graph.

The De Bruijn sequence corresponding to the De Bruijn graph above is: 0000111101100101.

Why does the Eulerian cycle give a valid sequence? A few observations can be made without entering into too much details and proofs:

• Each node represents a distinct code.
• All the codes must be tried at least once, so each node has to be visited at least once.
• One code can be created from another by entering a new digit (edge between two nodes).

With these points in mind, the problem is reduced to the implementation of the Eulerian cycle algorithm on the De Bruijn graph. This can be done from scratch, but we can also speed-up the process and use the existing NetworkX graph library.

We can plot the De Bruijn graph corresponding to the image above to visually verify its correctness.

The circular_layout is in that case more suited than the default spring layout. And even though this plot is quite simple (and doesn’t show the loop for node 000), we can still visually verify the graph against the one above.

Now with the real values k = 10 and n = 4:

Solution (overview): 16255004019983523821923269393645738058512587270874...21029426911638198665010844534159683130383134958162
Good solution? True


The solution contains all the codes. However it looks different than the reference.

Is circular permutation? False


That’s a practical way to notice that given a De Bruijn graph, the corresponding De Bruijn sequences are not unique.

Wrap up

There are many more things to learn from De Bruijn sequences. At least now we know that NetworkX is handy for some of the applications!