# sri, Sun Apr 13 15:05:56 PDT 2008
#
# A simple way to find palindromic pangrams.
# Completes in under 10 secs for the word list given
# by ITA software.
#
# The Algorithm: I find small palindromes (1-word & 2-word
# palindromes) and hook them up together. Example:
# if you have 1-word palindromes: "aa", "bb", & "cc", then
# aabbccccbbaa is a palindrome that contains all the characters
# a, b & c. It, by no means, produces an efficient
# palindrome. It also works for the dict file under OS X.
# So I really didn't need to find out 3-word palindroms,
# which seems much harder....
import string, sys
left = set(string.ascii_lowercase)
words = [x[:-1] for x in open(sys.argv[1]).readlines()]
# use below for certain dicts that have many 1-letter words
# if len(x) > 2]
wordset = set(words)
result = []
prefix = {}
suffix = {}
for word in words:
for i in xrange(len(word)):
pre = word[:i+1]
if pre not in prefix:
prefix[pre] = []
prefix[pre].append(word)
suf = word[-(i+1):]
if suf not in suffix:
suffix[suf] = []
suffix[suf].append(word)
def ispal(s):
i = 0
j = len(s)-1
while i < j:
if s[i] != s[j]:
return False
i += 1
j -= 1
return True
def words_containing(words, chars):
for word in words:
if any(c in word for c in chars):
yield word
def markpal(*words):
result.append(' '.join(words))
for c in result[-1]:
if c in left: left.remove(c)
# THIS IS BROKEN:
def smallest_pal(items):
def score(x):
x = x.replace(' ', '')
uniques = len(set(x))
return int(100 * uniques / len(x))
items = sorted(items, key=score, reverse=True)
left = set(string.ascii_lowercase)
result = []
for item in items:
if not any(c in item for c in left):
continue
result.append(item)
for c in item:
if c in left: left.remove(c)
print 'shortest pal:', 2 * len(''.join(result).replace(' ', ''))
print '|'.join(result)
print '|'.join(reversed(result))
# we don't care abt making the palindrome small
# we'll try do that later
for word in words:
if ispal(word): markpal(word)
print 'left over from 1-word pals:', left
# figuring out 2-word palindromes is very simple;
# try to "mirror" the characters on both sides
# and 1-st test if its forms a palindrome and
# then check to see if its in the word list
for word in words_containing(words, left):
for i in xrange(len(word)):
pre = ''.join(reversed(word[-(i+1):]))
#if ispal(pre+word) and prefix.has_key(pre): BUG!
if ispal(pre+word) and pre in wordset:
markpal(pre, word)
suf = ''.join(reversed(word[:i+1]))
#if ispal(word+suf) and suffix.has_key(suf): BUG!
if ispal(word+suf) and suf in wordset:
markpal(word, suf)
if not left:
# print things out nicely so i can quickly check result visually
print 'found palindromic pangram:', 2*sum(len(x) for x in result)
output = []
for c in string.ascii_lowercase:
for word in result:
if c in word:
output.append((c, word))
break
for (c, w) in output:
print "[%s] %s" % (c, w)
#smallest_pal(result)
else:
print 'no solution found'
left over from 1-word pals: set(['j', 'q', 'z'])
found palindromic pangram: 914
[a] aa
[b] aba
[c] civic
[d] dad
[e] deed
[f] deified
[g] aga
[h] aha
[i] bib
[j] raj ajar
[k] deked
[l] ala
[m] ama
[n] ana
[o] bob
[p] pap
[q] ta qat
[r] ere
[s] sagas
[t] otto
[u] alula
[v] ava
[w] awa
[x] oxo
[y] eye
[z] feeze ef