440 likes | 710 Views
Solving Jumble ® Puzzles Dictionaries, Hashes and Permutations. Richard A. DeVenezia. Jumble ® Puzzles. Four scrambled words Two five letter words Two six letter words Marked letters of unscrambled words Scramble of an answer to a cartoon hint. Sample Puzzle. LASRN O O O SHACE O O O
E N D
Solving Jumble®PuzzlesDictionaries, Hashes and Permutations Richard A. DeVenezia
Jumble® Puzzles • Four scrambled words • Two five letter words • Two six letter words • Marked letters of unscrambled words • Scramble of an answer to a cartoon hint
Sample Puzzle LASRN OOO SHACE OOO HOTGUF OOO CLATHE OOO Hint: You are here. A:
Puzzle Representation • Scrambled words • letter pool • Unscrambled key • Circled positions to answer pool • Answer key • Dashes
Problem Solving • Input • Unjumble • Permutation, Lookup • Letter Pool • Find Answer • Permutation, Lookup • Output Results
Puzzle Data • One puzzle per date datalines; 2006-10-09 ----- ------- fasrn OOO-- shace O--OO hotguf --OO-O clathe --OO-O run;
Input data jumbles (keep=date jumble circle) answers (keep=date answer) ; input date yymmdd10. answer $char50.; output answers; do i = 1 to 4; input jumble:$6. circle:$6. ; output jumbles; end;
ALLPERM • Permutations • interchange • “Because each permutation is generated from the previous permutation by a single interchange, the algorithm is very efficient.” SAS Help
ALLPERM data allperms; array p[5] (1:5); do i = 1 to FACT(5); call ALLPERM(i, of p[*]); output; end; run;
Lookup • DATA Step • SET KEY=index • Custom format • Hash object • Hash • Key • Data
Hash object declare HASH dict (); dict.defineKey('word'); dict.defineDone(); word='SESUG'; dict.add(); word='SUGI'; found = (dict.check()=0); put found=; --- found=0
Dictionary Data • http://wordlist.sourceforge.net/ • Word lists • http://prdownloads.sourceforge.net/wordlist/agid-4.zip • infl.txt (inflected) Part of Speech conferee N: conferees conference N: conferences conference V: conferenced | conferencing | conferences conferencing N?: conferencings
Dictionary Input infile INFL dlm=' ,|' missover end=end; input word pos @; do until (word=''); word = compress (word,'~<!?'); if not indexc (word,'123456790.{}') then words.replace(); input word@; end; input;
Dictionary Output • Hash method OUTPUT(dataset:dataset) rc = words.output (dataset:’sasuser.agid_dictionary’);
Unjumble • Data • Four jumbled words • Permutations • of letter array • 5! + 5! + 6! + 6! • 1,680 lookups by word • ALLPERM • N = n1 n2 n3 n4 combinations
Load Dictionary declare hash dict (); dict.defineKey ('word'); dict.defineDone (); length word $6; do until (end_dict); set &dictionary (where=(length(word) in (5,6))) end=end_dict ; word = lowcase(word); dict.replace(); end;
Data do until (end_jumble); set jumbles end=end_jumble; where date = “09OCT2006”D; _i + 1; jumble = lowcase(jumble); link allperm; end;
ALLPERM section • Jumbled Word to Letter Array • For each permutation of Array • Array to Word • If Word in Dictionary • Determine circled letters • OUTPUT
Word to Letter Array • length jumble $6 • array letters $1 letter1-letter6 • call pokelong (jumble, addrlong (letters[1]))
Letter Array to Word • length jumble $6 • array letters $1 letter1-letter6 • jumble = peekclong (addrlong (letters[1])), 6)
Check Each Permutation L = length (jumble); call pokelong(jumble,addrlong(letters[1])); do i = 1 to fact (L); if L = 5 then call allperm (i, of letters1-letters5); else call allperm (i, of letters1-letters6); word = peekclong (addrlong (letters(1)), L); if (word ne jumble) and dict.check () = 0 then ... end;
Circled Letters k = 1; circled = ' '; do j = 1 to length (circle); if substr(circle,j,1) = 'O' then do; substr(circled,k,1) = substr(word,j,1); substr(wurd,j,1) = upcase(substr(wurd,j,1)); k + 1; end; end; OUTPUT; c l a t h e * * O O * O c h a l e t circled=alt wurd=chALeT
WORK.UNJUMBLE 1211 = 2 combinations
WORK.POOL create table pool as select a.circled as A, b.circled as B , c.circled as C, d.circled as D , a.wurd as _A, b.wurd as _B , c.wurd as _C, d.wurd as _D from unjumble as a, unjumble as b , unjumble as c, unjumble as d where a._i = 1 and b._i = 2 and c._i = 3 and d._i = 4 ;
Finding the Answer • Permute pool • ALLPERM ? • 12 letter pool = 479,001,600 perms • Lexicographic ordering • Permutation f precedes a permutation g in the lexicographic (alphabetic) order iff for the minimum value of k such that f(k) g(k), we have f(k) < g(k).
Lexico-what? • Ordered progression • Avoid unnecessary checks • Example • iterator arrives at 1-3-2-4-5 • dictionary says no 1-3’s • advance to nextperm 1-4-x-x-x
Next Perm i j • From right • find i where f ( i-1 ) < f ( i ) • From i+1 • find j where f ( j ) < f ( i-1 ) • Swap • f ( i-1 ) and f ( j - 1 ) • Reverse • from i to end 1 - 5 - 4 - 3 - 2 swap 2 - 5 - 4 - 3 - 1 reverse 2 - 1 - 4 - 3 - 5 2 - 1 - 3 - 4 - 5
NEXTPERM next_perm: i = 12; do while (i > 1); if (indx[i-1] <= indx[i]) then leave; i + (-1); end; if i = 1 then return; j = i + 1; do while (j <= 12); if (indx[i-1] >= indx[j]) then leave; j + 1; end;
NEXTPERM * swap ; ix1 = i-1; ix2 = j-1; h = indx[ix1]; indx[ix1] = indx[ix2]; indx[ix2] = h; * reverse v[i..n] by swapping; ix1 = i; ix2 = 12; do while (ix1 < ix2); h = indx[ix1]; indx[ix1] = indx[ix2]; indx[ix2] = h; ix1 + 1; ix2 + (-1); end;
Letter Pool • POOL_0 • Original letters • POOL • Letters ordered according to current permutation • Mapping • Pool_0 to Pool
Filling the Pool array circles [4] $6 a b c d; ix = 1; do i = 1 to dim(circles); do j = 1 to length (circles[i]); pool0[ix] = substr (circles[i],j,1); pool [ix] = pool0[ix]; indx [ix] = ix; ix + 1; end; end;
Mapping * map items that were permuted; if i > 1 then do ix = i-1 to dim(pool); pool [ ix ] = pool0 [ indx [ ix ] ] ; end; return;
Dead Ends • 12 letters • s n a a e s u g t a l t • No words start with “snaa” • Can skip remaining sequence of 8! permutations that start with “snaa”
Short Cut • Word not in dictionary • Find shortest prefix that also isn’t • Sort after prefix • Compute next permutation
Prefix Dictionary * hash for dictionary of word prefixes; declare hash part (); part.defineKey ("length", "count", "prefix"); part.defineDone (); * ... For each word added to dictionary ...; * add word prefixes to word prefix dictionary; length = length(word); do count = 2 to length-1; prefix = substr(word,1,count); if part.check() eq 0 then continue; part.add(); end;
Word Tests word=peekclong(addrlong(pool(p)),length); if (dict.check() ne 0) then do; * word not found, find smallest prefix * not in prefix dictionary; do count = 2 to length-1; prefix = substr(word,1,count); if part.check() ne 0 then leave; end;
Last Perm of Tail • Sort the mapping indices • Descending order • Method • Index Testing • Not Quicksort
Tail Sort array map[12]; call missing (of map[*]); LEFT = p + count; RIGHT = 12; do ix = LEFT to RIGHT; map [ indx [ ix ] ] = 1; end; j = 12; do ix = 1 to 12 while (j >= LEFT); if not map[ ix ] > 0 then continue; indx [ j ] = ix; j = j - 1; end;
Word Found • Two cases • Last word of answer • Output • Proceed using next perm • Not last word • Continue using same perm
Successes if (q = 2) then do; if (soln.check() ne 0) then do; soln.add(); put 'NOTE: ' words[*]; end; words[q] = ' '; end; else do; * advance p to next word place; q + 1; p + length; length = wordlens[q]; CONTINUE; * return to top of loop; end;
Fallback • Word construction • starts at position p • Permutation • altered indices prior to p • search space exhausted • Response • reduce p to prior start points
Backing Up do while ((i <= p) and (i > 1)) ; q = q - 1; words[q]=''; length = wordlens[q]; p = p - length; end;
Answer is: SESUG ATLANTA Paper, Slides, and Code available at http://www.devenezia.com/papers
About the Author Richard A. DeVenezia Independent Consultant 9949 East Steuben Road Remsen, NY 13438 (315) 831-8802 http://www.devenezia.com/contact.php