Palindromic Pangrams
A palindrome is a sequence of words like "lid off a daffodil" or "shallot ayatollahs" that uses the same letters reading backwards as forwards. The words need not form a meaningful or grammatical sentence. A palindromic pangram is a multi-word palindrome that includes all 26 letters of the alphabet.

The above description of palindromic pangrams is taken from ITA Software's puzzles page. They go on to suggest the following programming challenge:

Write a program to find a palindromic pangram.
Bonus (optional, very hard) find the shortest possible palindromic pangram in terms of the total number of words or letters used.

The listing below (based on the "permuter" class shown elsewhere) is a serious effort to solve the above programming challenge, in particular the "bonus" task of using the least number of letters.

Notes:
    The solution works by incrementally permuting, mirroring and splicing a character string, looking for palindromic pangrams. An alphabet of 26 characters results in a vast number of permutations: 26! (factorial). There are probably more permutations than grains of sand in the Sahara, so, although the solution works in theory, the Sun will have burned out and destroyed the Earth long before the program ever finishes, even if I were to rewrite it in C++ :)
    Therefore, to test the program, we use a much shorter alphabet of, say, "abcdef", resulting in only 6! initial permutations (120). The results are shown below. The most satisfactory palindromic pangram found using just these 6 letters is "decaf aba faced" (an "aba" is, apparently, a sleeveless garment woven from goat and camel hair). This is the shortest possible (13 letters) using the test alphabet. According to the Wrangler, there are no valid 11 or 12 character palindromic pangrams using the test alphabet (11 being the shortest technically possible).
    Another test run, this time using a 9 character alphabet of "abcdefghi", produced the
rather nonsensical palindromic pangram of "ab dig feh chef gid ba" (all "words" are in ITA Software's supplied dictionary file).
    The results contain duplicate solutions. These could obviously be eliminated by a trivial addition to the program.
    I have also created a heavily optimized version of the Wrangler that eliminates non-viable permutations by indentifying and bypassing character-triples not represented in the dictionary. This was, of course, in retrospect, a foolish and (mostly) ineffective improvement, since it does not fix the underlying problems associated with the "permuter" approach. It is obvious that an entirely different design is required to solve the problem with a full 26 character alphabet in a non-apocalyptic timeframe. I'm working on an Ant Colony Optimization-based approach.

Output results follow the listing.


//* Palindromic Pangram Wrangler
//* Andrew Carson 2006

import java.io.*;
import java.util.*;

public class PPWrangler {

    public static void main(String[] args) {
        PPWrangler p = new PPWrangler();
    }

    /* instance variables */
    private static String iAlphabet = "abcdef";
    private static int iOK = 4; // stop after finding at least 4 pp's
    private Dictionary iDict;
    private String iSeed;
    private ArrayList iFound = new ArrayList();

    /* constructor */
    public PPWrangler() {
        System.out.println("Palindromic Pangram Wrangler");
        iDict = new Dictionary();
        Lexi extraChars = new Lexi("a", 0);
        while (iFound.size() < iOK) {
            iSeed = iAlphabet + extraChars.next();
            int[] value = new int[iSeed.length()];
            for (int i = 0; i < iSeed.length(); i++) value[i] = i + 1;
            permute(value, 0, iSeed.length());
        }
        System.out.println(">>> found " + iFound.size()
                + " palindromic pangrams");
    }

    void permute(int[] v, int a, int b) {
        start(v);
        for (int i = b - 2; i >= a; i--) {
            for (int j = i + 1; j < b; j++) {
                swap(v, i, j); permute(v, i + 1, b);
            }
            rotate(v, i, b);
        }
    }

    void swap(int[] v, int a, int b) {
        int t = v[a];
        v[a] = v[b]; v[b] = t;
    }

    void rotate(int[] v, int a, int b) {
        int t = v[a];
        for (int i = a; i < b - 1; i++) v[i] = v[i + 1]; v[b - 1] = t;
    }

    void start(int[] v) {
        wrangle(mirror(toString(v), 0), 0);
    }

    String toString(int[] v) {
        String s = "";
        for (int i = 0; i < v.length; i++)
            s += iSeed.charAt(v[i] - 1);
        return s;
    }

    String mirror(String s, int e) {
        for (int i = s.length() - 2 + e; i > -1; i--)
            s += s.charAt(i);
        return s;
    }

    void wrangle(String pan, int i) {
        for (int j = i + 1; j <= i + iDict.max(pan.charAt(i))
                && j <= pan.length(); j++)
           
if (iDict.isWord(pan.substring(i, j)))
                if (j == pan.length()) {
                    iFound.add(pan);
                    System.out.println(" --- \"" + pan + "\"");
                }
                else
                    wrangle(pan.substring(0, j) + " " + pan.substring(j), j + 1);
    }

    private class Dictionary {
        private String iWordList = "";
        private HashMap iMaxWordLengths = new HashMap();

        Dictionary() {
            System.out.print(">>> Loading dictionary... ");
            try { loadWordList(); } catch (Exception e) { }
            lookupLongWords();
            System.out.println("loaded");
        }

        void loadWordList()
                throws FileNotFoundException, IOException {
            FileInputStream fin = new FileInputStream("c:\\word.lst");
            int c, i = 0;
            StringBuffer b = null;
            while (true) {
                if (b == null) {
                    b = new StringBuffer(20000);
                    b.setLength(20000);
                }
                c = fin.read();
                if (c == -1) { b.setLength(i); break; }
                b.setCharAt(i++, (char)c);
                if (i == 20000) { iWordList += b; i = 0; b = null; }
            }
            if (b != null) iWordList += b;
            fin.close();
            iWordList = "|" + iWordList.trim().replaceAll("\r\n", "|") + "|";
        }

        void lookupLongWords() {
            for (int i = 0; i < iAlphabet.length(); i++) {
                String alpha = Character.toString(iAlphabet.charAt(i));
                for (int j = 0;;) {
                    int k = iWordList.indexOf('|' + alpha, j);
                    if (k < 0) break; else j = k + 2;
                    while (iWordList.charAt(j) != '|') { j++; }
                    String s = (String)iMaxWordLengths.get(alpha);
                    if (s == null || Integer.parseInt(s) < j - k - 1)
                        iMaxWordLengths.put(alpha, Integer.toString(j - k - 1));
                }
            }
        }

        // see if exists in the dictionary
        boolean
isWord(String word) {
            return iWordList.indexOf("|" + word + "|") > -1;
        }

        // return the max word length starting with 'c'
        int max(char c) {
            String max = (String)iMaxWordLengths.get(Character.toString(c));
            if (max == null) return 0; else return Integer.parseInt(max);
        }
    }

    private class Lexi {
        private int iCount;
        private String iFirst, iCurr;

        public Lexi(String first, int c) { iFirst = first; iCount = c; }

        public String current() { return iCurr; }

        public String next() {
            if (iCount++ == 0) { return ""; }
            if (iCurr == null) { iCurr = iFirst; return iCurr; }
            int digit = 0, ch;
            do {
                ch = iAlphabet.indexOf(iCurr.charAt(digit)) + 1;
                if (ch == iAlphabet.length()) ch = 0;
                iCurr = iCurr.substring(0, digit)
                        + iAlphabet.charAt(ch) + iCurr.substring(digit + 1);
                if (ch == 0) digit++;
                if (digit == iCurr.length()) {
                    iCurr += iAlphabet.charAt(0); break;
                }
            } while (ch == 0);
            return iCurr;
        }
    }
}



Palindromic Pangram Wrangler
>>> Loading dictionary... loaded
  --- "ab decaf aced ba"
  --- "ab face decaf ba"
  --- "ba cafe de fa cab"
  --- "ba decaf ace dab"
  --- "ba decaf aced ab"
  --- "ba de fa cafe dab"
  --- "bade fa cafe dab"
  --- "ba face decaf ab"
  --- "ba cafe de fa cab"
  --- "ba decaf ace dab"
  --- "ba decaf aced ab"
  --- "ba de fa cafe dab"
  --- "bade fa cafe dab"
  --- "ba face decaf ab"
  --- "dace ba fa be cad"
  --- "deaf ba cab fa ed"
  --- "decaf aba faced"
  --- "decaf aba faced"
  --- "deaf ba cab fa ed"
  --- "dace ba fa be cad"
  --- "faced aba decaf"
  --- "fa ed ba cab deaf"
  --- "fed aba cab ad ef"
  --- "fed abaca bad ef"
  --- "fed aba cab ad ef"
  --- "fed abaca bad ef"
  --- "faced aba decaf"
  --- "fa ed ba cab deaf"
  --- "ab decaf aced ba"
  --- "ab face decaf ba"
>>> found 30 palindromic pangrams

 

 
     



Copyright © 2006 Andrew Carson. All rights reserved.
Subject to the following terms & conditions of use.