Written by: Paul Rubin

Primary Source: OR in an OB World, 12/12/2018.

This is going to be the first of a few posts on the same problem. My efforts are based on a blog post by Nate Brixius (@natebrix) titled “Optimizing 19th Century Typewriters“, which in turn is based on a post titled “Tuning a Typewriter” by “Hardmath123”. As the image in Hardmath123’s post shows, an old (very old!) Crown typewriter used a “keyboard” that had all the symbols in a single row. You shifted the row left or right to get to the symbol you wanted next, then pressed a key to enter it. There was a second key, the equivalent of “shift” on today’s keyboards, that let you strike an alternate symbol perched above the primary symbol at each position in the lone row.

If we ignore numerals and punctuation marks (which both Hardmath123 and Nate did), the problem of finding an optimal layout for the keyboard amounts to finding the best permutation of the 26 letters A through Z. The criterion we are all adopting for “best” is requiring the least movement, in some average sense, to get to the next letter. This requires some estimate of how often each possible transition occurs. Hardmath123 and Nate estimated those transition frequencies from three books in the Project Gutenberg library. Since Nate was kind enough to share his frequency data with me, I’m indirectly using those same books.

To be consistent with Nate’s post, I’ll use zero-based indexing, so “A” will have index 0 and “Z” will have index 25. Similarly, the leftmost slot on the keyboard will have index 0 and the rightmost will have index 25. The metric we have all adopted is to minimize \(\sum_{i=0}^{25} \sum_{j=0}^{j=25} f_{ij} |p_i – p_j|\), where \(f_{ij}\) is the frequency with which symbol \(j\) immediately follows symbol \(i\) and \(p_i\) is the position in which symbol \(i\) is located (so that \(|p_i – p_j|\) is the distance shifted moving from symbol \(i\) to symbol \(j\)). Note that \(i = j\) is perfectly fine, since a letter can follow itself.

A brute force solution would require \(26!/2 \approx 2\times 10^{26}\) layouts to be evaluated. The division by 2 is because the problem is bilaterally symmetric: given any layout, the reverse of that layout will have the same objective value. (This relies implicitly on the fact that we do not differentiate between moving \(n\) spaces to the left and moving \(n\) spaces to the right.) Hardmath123 applied a heuristic that I would characterize as a version of 2-opt (with restarts) and found a solution with cost 5,499,341. Nate ran a heuristic by a third party and matched Hardmath123’s result “within seconds”. He also modeled the problem as a quadratic assignment problem (more on that later) and ran a solver he’d written himself, back in the day … but did not let it run to completion, and did not say how good the incumbent was when he terminated the run.

I tried several models, in an attempt (as yet unsuccessful) to get a provably optimal solution. All the models used a beta copy of version 12.9 of IBM’s CPLEX Studio, but perform similarly with the current production version (12.8). In subsequent posts, I’ll document the various models I tried and give some results. Meanwhile, you are welcome to play with my Java code, the repository for which is publicly available. In addition to my Java code and the usual miscellany (README file, license file, …), you’ll find a text file (cro26_data.txt) with the frequencies being used, which Nate kindly is letting me share. To run the code, you will need a recent version of the CPLEX Studio suite, and you will need to put all three major pieces (CPLEX, CPOptimizer and OPL) on your library and class paths. You’ll also need the open source Apache Commons CLI library, which I use to process command line options. Once you have the code installed and linked up, you can run the program with the command line option -h or –help (that’s a double dash before “help”) to get a list of what can/should be specified as options.

More to come …

#### Paul Rubin

#### Latest posts by Paul Rubin (see all)

- Reversing Differences - February 19, 2020
- Collections of CPLEX Variables - February 19, 2020
- Generic Callback Changes in CPLEX 12.10 - February 3, 2020