ADFGX cipher encryption

The ADFGX cipher was used by the German army in WWI. Write a program to encipher with it!

It works as follows:

  1. Create a 5x5 Polybius square with a mixed alphabet. The polybias square is labled A D F G X along the top and left.
  2. Generate the pairs, so A in its non-mixed position is AA, B is AD and so on.
  3. Perform columnar transposition using the columnar key.
  4. Output the ciphertext.


  • Your program should either define a function that accepts the arguments of the mixed alphabet and the columnar transposition key, in that order, or accept the alphabet/key as command-line arguments. You can also get input from STDIN.
  • Standard loopholes apply.
  • You can't use a pre-written columnar transposition library (or, for that matter, ADFGX) - i.e. no running something like import cipherpack.


This is , so shortest code (in bytes) wins!

Test cases

My example runs used a failed source. I'll do some more later.


Run the Stack Snippet to generate a leaderboard.

/* Configuration */

var QUESTION_ID = 71997; // Obtain this from the url
// It will be like https://XYZ.stackexchange.com/questions/QUESTION_ID/... on any question page
var ANSWER_FILTER = "!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe";
var COMMENT_FILTER = "!)Q2B_A2kjfAiU78X(md6BoYk";
var OVERRIDE_USER = 50171; // This should be the user ID of the challenge author.

/* App */

var answers = [], answers_hash, answer_ids, answer_page = 1, more_answers = true, comment_page;

function answersUrl(index) {
  return "https://api.stackexchange.com/2.2/questions/" +  QUESTION_ID + "/answers?page=" + index + "&pagesize=100&order=desc&sort=creation&site=codegolf&filter=" + ANSWER_FILTER;

function commentUrl(index, answers) {
  return "https://api.stackexchange.com/2.2/answers/" + answers.join(';') + "/comments?page=" + index + "&pagesize=100&order=desc&sort=creation&site=codegolf&filter=" + COMMENT_FILTER;

function getAnswers() {
    url: answersUrl(answer_page++),
    method: "get",
    dataType: "jsonp",
    crossDomain: true,
    success: function (data) {
      answers.push.apply(answers, data.items);
      answers_hash = [];
      answer_ids = [];
      data.items.forEach(function(a) {
        a.comments = [];
        var id = +a.share_link.match(/\d+/);
        answers_hash[id] = a;
      if (!data.has_more) more_answers = false;
      comment_page = 1;

function getComments() {
    url: commentUrl(comment_page++, answer_ids),
    method: "get",
    dataType: "jsonp",
    crossDomain: true,
    success: function (data) {
      data.items.forEach(function(c) {
        if (c.owner.user_id === OVERRIDE_USER)
      if (data.has_more) getComments();
      else if (more_answers) getAnswers();
      else process();


var SCORE_REG = /<h\d>\s*([^\n,]*[^\s,]),.*?(\d+)(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)[^\n\d<>]*)*<\/h\d>)/;

var OVERRIDE_REG = /^Override\s*header:\s*/i;

function getAuthorName(a) {
  return a.owner.display_name;

function process() {
  var valid = [];
  answers.forEach(function(a) {
    var body = a.body;
    a.comments.forEach(function(c) {
        body = '<h1>' + c.body.replace(OVERRIDE_REG, '') + '</h1>';
    var match = body.match(SCORE_REG);
    if (match)
        user: getAuthorName(a),
        size: +match[2],
        language: match[1],
        link: a.share_link,
  valid.sort(function (a, b) {
    var aB = a.size,
        bB = b.size;
    return aB - bB

  var languages = {};
  var place = 1;
  var lastSize = null;
  var lastPlace = 1;
  valid.forEach(function (a) {
    if (a.size != lastSize)
      lastPlace = place;
    lastSize = a.size;
    var answer = jQuery("#answer-template").html();
    answer = answer.replace("{{PLACE}}", lastPlace + ".")
                   .replace("{{NAME}}", a.user)
                   .replace("{{LANGUAGE}}", a.language)
                   .replace("{{SIZE}}", a.size)
                   .replace("{{LINK}}", a.link);
    answer = jQuery(answer);

    var lang = a.language;
    if (/<a/.test(lang)) lang = jQuery(lang).text();
    languages[lang] = languages[lang] || {lang: a.language, user: a.user, size: a.size, link: a.link};

  var langs = [];
  for (var lang in languages)
    if (languages.hasOwnProperty(lang))

  langs.sort(function (a, b) {
    if (a.lang > b.lang) return 1;
    if (a.lang < b.lang) return -1;
    return 0;

  for (var i = 0; i < langs.length; ++i)
    var language = jQuery("#language-template").html();
    var lang = langs[i];
    language = language.replace("{{LANGUAGE}}", lang.lang)
                       .replace("{{NAME}}", lang.user)
                       .replace("{{SIZE}}", lang.size)
                       .replace("{{LINK}}", lang.link);
    language = jQuery(language);

body { text-align: left !important}

#answer-list {
  padding: 10px;
  width: 290px;
  float: left;

#language-list {
  padding: 10px;
  width: 290px;
  float: left;

table thead {
  font-weight: bold;

table td {
  padding: 5px;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/codegolf/all.css?v=83c949450c8b">
<div id="answer-list">
  <table class="answer-list">
    <tbody id="answers">

<div id="language-list">
  <h2>Winners by Language</h2>
  <table class="language-list">
    <tbody id="languages">

<table style="display: none">
  <tbody id="answer-template">
    <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr>
<table style="display: none">
  <tbody id="language-template">
    <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr>


It is not necessary to split the output into groups of five.

  • 1
    \$\begingroup\$ Wouldn't B in its non-mixed position be AD, not AB? \$\endgroup\$
    – Doorknob
    Feb 13, 2016 at 15:26
  • \$\begingroup\$ Also, does it need to be case insensitive or can we assume all input is in uppercase? Could we have some test cases? \$\endgroup\$
    – Doorknob
    Feb 13, 2016 at 15:30
  • \$\begingroup\$ Also, you never specified where the actual message to encipher comes in the input. Does it come first (before the alphabet and the key) or last (after them)? \$\endgroup\$
    – Doorknob
    Feb 13, 2016 at 15:33
  • 1
    \$\begingroup\$ Do we need to separate the ciphertext into groups of five? \$\endgroup\$
    – lirtosiast
    Feb 13, 2016 at 16:07
  • \$\begingroup\$ @Doorknob Yes, I've updated the question. I'll do some test cases when I'm on my PC. \$\endgroup\$
    – user50171
    Feb 13, 2016 at 16:09

2 Answers 2


Pyth, 26 bytes


For this, the second line (columnar key) needs to be in quotes, which is acceptable by default.

Try it here.


Pyth, 36 34 33 32 31 bytes

Jw                                assign (z, J) to 2 lines of input
              m             w     map over each char of a 3rd input line...
                ^"ADFGX"2         generate ['AA', 'AD', 'AF', ...]
                         xzd      find the index of this char in z
               @                  index into ^^ with ^
             s                    join groups of 2 ch. into one string
            c                lJ   split ^ into groups of len(J) chars
          .T                      transpose
       C,J                        pair elems up with chars of J
     hD                           sort by chars of J (the key)
   eM                             get rid of the chars of J now
  s                               join

Thanks to Thomas Kwa for a byte!

Sample run, using the example from Wikipedia:

llama@llama:~$ pyth -c 'JwjdeMhDC,J.Tcsm@^"ADFGX"2xzdwlJ'
  • \$\begingroup\$ Sorry for posting a similar solution, and thanks for linking to the test case. \$\endgroup\$
    – lirtosiast
    Feb 13, 2016 at 16:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.