Ordkvadrat

Eksperimenter med ord og reducering af kompleksitet

Kildekode

Krav: Maven & JDK 1.6

Direkte kørbar

java -jar wordsquare-20130108.jar

Hvad er et ordkvadrat?

Man vælger en ordlængde, f.eks. 5 bogstaver, og danner et kvadrat ved at skrive 5 af disse ord under hinanden. De ord der dannes af både de vandrette rækker og de lodrette søjler af bogstaver skal eksistere i ordbogen. Et ordkvadrat er f.eks.

adict
tenes
opdra
moser
stuts

Man kan stille flere krav, som f.eks. at de to skrå rækker af bogstaver, læst fra venstre mod højre, skal være korrekte ord. I kvadratet ovenfor er det aedes og sodet. Et større ordkvadrat kan f.eks. være denne på 8x8 bogstaver:

abbedier
brunalge
buketten
eneboers
datoerne
ilterhed
egernene
rensedes

Selvom det er et større kvadrat, er det ikke nær så elegant som det på 5x5 bogstaver. Dels danner diagonalerne ikke korrekte ord og dels er det præcis de samme ord der findes i de vandrette og lodrette rækker.

Hvad er det med kompleksiteten?

Det er ikke oplagt hvordan man finder gyldige ordkvadrater. Hvis man eksempelvis vil finde alle ordkvadrater på 8x8 bogstaver og har en ordbog med 10.000 ord af den længde, kan de 8 rækker af ord udvælges på 10.000^8 måder. Hvis vi siger at der kan kontrolleres 1 milliard ordkvadrater i sekundet (det er urealistisk mange), vil det tage 10^32 sekunder at kontrollere alle mulige kombinationer. Vores lokale sol er brændt ud lang tid før den tid er gået.

Når beregninger tager for lang tid, er der forskellige muligheder. To af disse er at foredre algoritmen og at øge hastigheden for afviklingen af algoritmen. I dette tilfælde hjælper det ikke rigtigt at øge hastigheden. Selv hvis man brugte en maskine der var en milliard gange hurtigere end en 2012-model, ville Solen stadig nå at brænde ud. Så der er brug for at finde ordkvadrater på en smartere måde end bare at prøve alle kombinationer.

Hvad er det interessante her?

På det sproglige niveau er det på linje med at løse rebusser. Man har søgt efter ordkvadrater i årtusinder på forskellige sprog, men jeg har ikke kunnet finde meget om det på dansk og slet ingen af de større løsninger. Det skal der rettes op på!

Jeg synes både det er spændende at finde på bedre algoritmer og at optimere hastigheden af disse, dvs. forbedre implementationen. Jeg vil prøve at skelne mellem de to dele når jeg beskriver de forskellige ideer jeg afprøver. Der er selvfølgelig også selve de fundne ordkvadrater - jeg håber at kunne finde nogen pæne af slagsen på 8x8, 9x9 eller måske 10x10 bogstaver. På denne side vil jeg dokumentere de ting jeg prøver undervejs.

Hvilken ordbog?

Allerhelst ville jeg gerne bruge ordbogen fra Dansk Sprognævn og har kontaktet dem for at høre om de vil sende mig en fil med ordene. Indtil der kommer nyt derfra benytter jeg ordbogen fra da.speling.org, der efter en oprydning (ingen egennavne, ingen bindestreger, ingen forkortelser, ingen cifre) indeholder 600.000 ord af forskellig længde.

26. november 2012

To indledende teknikker bragte processeringstiden for et 6x6 ordkvadrat med en ordbog på 2000 ord ned fra anslået 20.000 år til 6-8 minutter. Interesserede kan hente SquareTest.java.

Teknik 1 (algoritme). Afbryd alle kombinationer når der ikke kan dannes lodrette ord

Hvis man forsøger at bygge et 5x5 ordkvadrat op hvor første ord er abbed og næste ord er abers vil det foreløbige ordkvadrat se således ud:

abbed
abers

Løber man igennem de lodrette søjler, altså aa, bb, be, er & ds og kontrollerer i ordbogen om der eksisterer ord der starter med disse bogstavkombinationer, vil man få at vide at der ikke eksisterer ord der starter med bb. Altså er der ingen grund til at bygge videre på kvadratet og man kan prøve med ordet efter abers.

Teknik 2 (implementation). Lav specialiserede ordbøger til ordbegyndelser

Ved at konstruere et hash-sæt for alle længder af ordbegyndelser, blev opslag efter ordbegyndelser hurtigere end f.eks. binær søgning (det var i hvert fald teorien).

Evaluering

Selvom alle 8x8 ordkvadrater med en ordbog på 4200 ord kunne findes på ca. 10 minutter, gik det helt i stå med en ordbog der var 10 gange større. Den nåede ikke engang em promille gennem opgaven i løbet af et døgn på en god computer.

20. november 2012

To nye teknikker bragte processeringstiden for et 6x6 ordkvadrat med en ordbog på 2000 ord ned fra de forgående 6-8 minutter til 5-6 sekunder. Interesserede kan hente wordsquare-20121128.zip (kræver Java og er lettest at bygge med Maven).

Teknik 3 (algoritme): Alternering mellem tilføjelse af vandrette og lodrette ord

Ved skiftevis at indsætte et vandret og et lodret ord behøver man kun afprøve ordkandidater med det givne præfix der dannes. Derved reduceres mængden af ordkandidater væsentligt. Der blev anvendt sorterede lister og binær søgning til (rimelig) hurtigt opslag og meget hurtig iterering af ord med givne præfix. For et ordkvadrat på 3x3 bogstaver kunne et succesfuldt forløb være som følger (hvor punktum angiver at der ikke er noget bogstav).

Skridt 0: Ordkvadratet er blankt.

...
...
...

Skridt 1: Der vælges et vilkårligt ord fra ordbogen, som indsættes i første række.

hun
...
...

Skridt 2: Der kontrolleres at der eksisterer ord der starter med henholdsvis h, u og n (teknik 1). Dernæst reduceres mængden af mulige kandidater til de ord der starter med h og dette indsættes lodret i første søjle.

hun
a..
l..

Skridt 3: Der kontrolleres at der eksisterer ord der starter med henholdsvis a og l. Dernæst reduceres mængden af mulige kandidater til de ord der starter med a. Et af disse ord indsættes i anden række

hun
ara
l..

Skridt 4: Der kontrolleres at der eksisterer ord der starter med henholdsvis ur og na. Dernæst reduceres mængden af mulige kandidater til de ord der starter med ur. Et af disse indsættes i anden søjle.

hun
ara
le.

Skridt 5: Der kontrolleres at der eksisterer ord der starter med le. Dernæst reduceres mængden af mulige kandidater til de ord der starter med le. Et af disse indsættes i tredje række.

hun
ara
let

Skridt 5: Et fuldt ordkvadrat blev fundet. Såfremt diagonaler er et krav, kontrolleres disse (diagonalerne er ikke gyldige i dette eksempel).


Teknik 4 (algoritme): Overspringning af ord med ikke-gyldig begyndelse

Såfremt der med teknik 1 findes en ugyldig ordstart, kan alle ord med denne ordstart ignoreres. I ordkvadratet nedenfor er onkel kandidat, men forkastes fordi ingen ord starter med rk. Det betyder at samtlige ord der starter med onk kan ignoreres.

kerte
onkel
n....
e....
r....

Evaluering

Selvom hastigheden blev væsentligt forbedret, var processeringstiden stadig for høj. Ved en søgning efter ordkvadrater på 8x8 fra en ordliste på 44.000 ord kom en moderne maskine kun 1-2 promille gennem opgaven på et døgn.

1. december 2012

Når 1-2 promille af opgaven kan løses på et døgn, er det værd at prøve om implementationen kan forbedres så hele opgaven kan løses på et par uger eller lignende. Det kræver at programmets hastighed forbedres med en faktor 50. Det er ikke helt urealistisk, men ret svært eftersom der allerede benyttes temmelig hurtige datastrukturer.

De sæt der benyttes til præfixopslag giver svar i praksis i noget nær konstant tid. Men når der jages rå hastighed er det interessant at se på hvad den tid er. Når en nydannet ord eller ordbegyndelse skal verificeres, skal det først udregnes en hashværdi. Det gøres ved at løbe gennem alle bogstaverne og opdatere værdien undervejs. Dernæst laves et direkte opslag i et array og såfremt der er flere ord med samme hash, løbes disse igennem og sammenlignes.

Der er flere problemer med sæt til opslag: Det ord der skal slås op bliver gennemløbet mindst én og muligvis to gange, der skal muligvis løbes gennem en lille liste af ord med samme hash og - her kommer det kritiske - hele strukturen med forskellige sæt til hver præfixlængde fylder så meget at den ikke kan caches fuld ud i level 2 cache på en 2012-maskine.

Teknik 5 (implementation): Skift til Trie som generel opslagsimplementation

En trie har den interessante egenskab at ordet gennemgås bogstav for bogstav. Gennemgangen afbrydes så snart et præfix af ordet ikke er i ordbogen og såfremt gennemgangen ikke afbrydes, er ordet i ordbogen. Der er altså færre operationer end med et sæt og samme struktur kan bruges til alle længder af præfix. Som udgangspunkt fylder trier en hel del i hukommelsen så den del skal der gøres noget ved. Målet er at få en ordbog af rimelig størrelse til at kunne ligge i level 2 cache på en moderne maskine.

5. december 2012

Den komprimerede Trie begynder at ligne noget. Der kan laves opslag på ord, men gennemløb af ordbogen er ikke på plads endnu. En ordbog på 44.000 ord á 8 tegn fylder 820KB, altså ca. 2½ gange den rå størrelse. Jeg havde nok håbet på at den ville fylde mindre, men den kan sagtens ligge i cache på en moderne maskine.

Teknik 6 (algoritme): Prøv kun halvdelen af alle kombinationer

Kollegaen Thomas Egense gjorde opmærksom på at alle løsninger kan transponeres så rækker bliver til søjler og omvendt. Det betyder at det kun er nødvendigt at gennemsøge halvdelen af alle muligt kombinationer for at finde alle løsninger. Det kan f.eks. gøres ved kun at afprøve ord i første søjle fra teknik 3 der er de samme eller kommer efter ordet i første række. Det skulle meget gerne fordoble hastigheden og gør at der "kun" er en faktor 25 der skal hentes ved teknik 5, for at få processeringstiden ned i et realistisk leje.

13. december 2012

Teknik 7 (implementation): Tegnsortering efter forekomster

En Trie er en graf hvor øverste knude indeholder en liste med alle begyndelsesbogstaver. Hvert bogstav peger på en ny knude med en liste med alle de mulige bogstaver på position 2 efter det første bogstav og så fremdeles. Den kompakte Trie, der anvendes her, holder styr på bogstaverne ved at have én bit for hvert muligt bogstav: Hvis den komplette mængde af unikke bogstaver i korpus er c, d, e, k, r, m, o &z, vil en liste med bogstaverne e, r & m kunne udtrykkes som 00101100, hvilket kun fylder én byte.

Når ordbogen gennemløbes efter kandidater skal bitlisten for bogstaver i knuderne gennemløbes. Ved at sortere tegnene efter antal forekomster og ved at holde styr på længden af listen af tegn, vil det for de fleste knuder betyde at kun en lille del af listen skal gennemløbes.

Teknik 8 (implementation): Specialiserede opslagsmekanismer

En førsteudgave af den komprimerede Trie er implementeret. Hele strukturen gemmes i en long[] og opslag håndteres med bitpilleri. Den kan givetvis optimeres yderligere (kvalificeret gæt: Faktor 2-5). Den har følgende interessante egenskaber:

Dette leder på elegant vis hen til:

Teknik 9 (algoritme): Gennemløb kun ord der vil passe i ordkvadratet

I nedenstående benyttes eksempelordbogen alt, art, asa, ash, agt, hat, sky, sya, syl & syr.

Skridt 0: Ordkvadratet er blankt.

...
...
...

Skridt 1: Et vilkårligt ord fra ordbogen indsættes.

asa
...
...

Skridt 2: De næste gyldige tegn for ordbegyndelserne a, s & a udtrækkes. De er henholdsvis [lrsg], [yk] & [lrsg]. Gyldige kandidater for række 2 er derfor alle ord der opfylder [lrsg[yk][lrsg], hvilket er sya, syl & syr. Det tegnafgrænsede ordgennemløb fra teknik 8 benyttes til at udtrække dem fra ordbogen. Et af ordene indsættes i kvadratet.

asa
sya
...

Skridt 3: De næste gyldige tegn for ordbegyndelserne as, sy & aa udtrækkes. De er henholdsvis [ah], [al] & []. Eftersom der ikke er nogen gyldige tegn på plads 3, opgives kandidaten sya og der forsøges med næste ordkandidat fra skridt 2.

asa
syl
...

Skridt 4: De næste gyldige tegn for ordbegyndelserne as, sy & al udtrækkes. De er henholdsvis [ah], [al] & [t]. Gyldige kandidater for række 3 er derfor alt & hat. Et af disse indsættes i kvadratet.

asa
syl
hat

Skridt 5: Ordkvadratet er udfyldt. Eftersom der kun er indsat ord der garanterer konsistens, er der ikke behov for kontrollerende opslag. Resultatet registreres og næste ord fra skridt 4 indsættes.

Evaluering

Den 1. december var målet at øge hastigheden 50 gange. Kombinationen af teknik 5, 7, 8 og 9 har sænket processeringstiden for et 15x15 ordkvadrat med 35.000 ordkandidater fra 2 timer og 32 minutter til 30 sekunder. Det er en faktor 300, så målet er klart nået og bringer den forventede processeringstid for det hårde 9x9 ordkvadrat ned fra 1-2 år til 2-3 dage.

Der er nu stadig lidt at arbejde med: Teknik 6 er ikke implementeret da det ikke er trivielt at få den til at fungere sammen med teknik 9. Det betyder at der stadig findes dobbelt så mange ordkvadrater som nødvendigt, så der er en oplagt optimering at gå i gang med.
Den binære Triestruktur har en hel del redundans i form af slutkknuder med kun ét tegn, så der burde størrelsen kunne bringes ned til det halve eller bedre. Mindre struktur = større sandsynlighed for at det hele ligger i level 2/3 cache på processoren = højere hastighed.

15. december 2012

Lidt spredte puslerier med at optimere ved at genbruge objekter gav på skuffende vis næsten ingen hastighedsforbedring. Til gengæld er den nuværende hastighed god nok til at der kan laves komplette afsøgninger for alle størrelser ordkvadrater med den nuværende ordbog. Der ventes stadig på ordbogen fra Dansk Sprognævn (det ser ud til at falde på plads, så der kan testes med 2001-udgaven), men indtil da forsøges der med listen fra da.speling.org. Der var desværre ingen 9x9 ordkvadrat (54876 ordkandidater, 32 timers processeringstid) så chancen for et 10x10 ordkvadrat (62253 ordkandidater, udtømmende søgning forventes færdig 2012-12-16) er meget ringe. Der er til gengæld masser af 8x8 ordkvadrater (43898 ordkandidater, udtømmende søgning forventes færdig senest 2012-12-25), så med lidt held er der også nogen pæne imellem.

Skulle nogen have lyst til at forsøge selv, kan programmet hentes i form af wordsquare-20121216.zip (Java & Maven).

Med en forventet maksimal processeringstid for 8x8 ordkvadrater på 10 dage er min motivation for yderligere hastighedsoptimeringer faldende. Det kan være jeg pusler lidt videre med den del på et tidspunkt, men mere aktuelt har jeg fået øje på Form Records, der indeholder rekorder for en masse andre former end kvadrater. Selvom det nuværende program er lavet til kvadrater, burde det være overskueligt at tilpasse det til at understøtte vilkårlige former, så længe reglerne er at de gyldige ord blot behøver at være alle vandrette og lodrette ord. På sigt kunne man muligvis indføre understøttelse for andre regler, så ting som skrå ord eller bi-grams kunne håndteres, men det ligner en større opgave.

Næste udvidelse må derfor være at gøre det nemt at definere og afsøge andre former. Jeg forestiller mig at man laver et simpelt bitmap i form af en tekstfil, f.eks.

..***..
.*****.
*******
*******
*******
.*****.
..***..

hvor programmet vil indsætte ord på de definerede felter og sikre at alle vandrette og lodrette ord er gyldige. En løsning fra Newrow, 2001 via Puzzlers.org på ovenstående er

  OPA
 SPICE
SEAPORT
ALLELIC
CLOTHES
 SITUS
  DEA

19. december 2012

Det ser ud til at der kom styr på rektangler og pyramider, som f.eks.

selskabstømmersag
 mineralogiernes 
  torarullernes  
   geleddernes   
    tindernes    
     edernes     
      ernes      
       ses       
        s        

Jeg valgte at tillade alle enkeltbogstaver i spidsen. Der er for få løsninger hvis man skal holde sig til i, å & ø og det bliver for arbitrært hvis der kommer bogstaver som c og m med (lysets hastighed og meter), men ikke f.eks. b. Teoretisk burde programmet nu understøtte alle former, som beskrevet tidligere, men det er ikke testet endnu og jeg har ikke lavet en parser til brugerdefinerede former.

Og klokken blev henad midnat... Det virker glimrende med vilkårlige former, her er f.eks. et s:

     essenser     
  definitionerne  
parvise      eis  
isings            
revenes           
kriderpapirers    
  sensibilitetens 
      rebslagerier
            egnene
a           snæres
karakterbristers  
    bådehuses     

Det der mangler er en indpakning, så de brugerdefinerede former kan angives fra kommandolinjen. Det der også mangler er en afrapportering. Dansk Sprognævn har meget venligt stillet ordene fra Retsskrivningsordbogen 2001 til rådighed til dette projekt (jeg må desværre ikke viderelevere ordlisten) og jeg har kørt alle de mulige størrelser ordkvadrater igennem. Jeg laver en liste med resultaterne senere, men kan med det samme sige at der desværre ikke var nogen af størrelse 9x9 eller derover. Der var 2145 8x8 ordkvadrater, men de var alle spejlede så de samme ord gik igen i søjlerne og rækkerne.

21. december 2012

Så kom brugerdefinerede former ordentligt på plads. Der skal lige lidt finpudsning til før der er et reelt release, men jeg havde lovet en rapport over ordkvadrater ud fra Retsskrivningsordbogen 2001, så den gives hermed: I nedenstående tabel er "Gyldige" samtlige ordkvadrater, "Unikke" er dem hvor samme ord ikke optræder flere gange og "Diagonal" er dem hvor mindst én diagonal har et korrekt ord. Interesserede kan hente alle gyldige 8x8 (20 KB)

Sider Ord Tid Gyldige Unikke Diagonal Unikke+diagonal
2x2 174 224 ms 4.911 3.663 1.171 841
3x3 1.089 11 sek 739.139 506.875 61.813 43.273
4x4 3.373 8 min 41.896.336 32.994.946 2.462.345 1.978.007
5x5 7.835 49 min 145.421.782 104.178.728 3.626.620 2.761.695
6x6 15.830 3 timer 40.348.491 19.335.652 438.959 273.244
7x7 24.644 7 timer 361.236 14.140 48 6
8x8 33.933 8 timer 2.145 0 0 0

Vigtigt: Dansk Sprognævn angiver at endelserne på ordene i den fil de har udleveret er delvist autogenererede og dermed ikke autoritative. Ovenstående er dermed ikke garanteret at være 100% korrekt. Den nye udgave af ordbogen skulle være korrekt og jeg laver gerne afsøgning på den, men Dansk Sprognævn offentliggør den først i starten af 2013 og de vil have penge for den, så jeg venter lige og ser hvad det helt konkret betyder.

30. december 2012

De 6 ordkvadrater af størrelse 7x7, som både bestod af unikke ord og som havde mindst én diagonal med gyldigt ord, kan transponeres (søjlerne og rækkerne ombyttes). Det kan alle ordkvadrater. Det betyder at der reelt kun er 3 pæne løsninger i størrelsen 7x7. Disse løsninger er som følger:

opkomst
provoer
poderne
everten
fonders
rederne
anelses
skifers
toneren
ordrige
lagenes
plyndre
rederen
ertsers
skifere
toneren
ordrige
lagener
plyndre
rederen
ertsers

En evert er et lille sejlskib, trense er en form for snor, fer er en del af et bræt der kan sættes sammen med andre brædder i deres not og erts er en geologisk dannelse, hvis nogen skulle undre sig.

6. januar 2013

Lidt løs eksperimentering med forskellige former fra The National Puzzler's League Form Records afslørede en svaghed ved programmet: Princippet med kun at iterere over mulige ord virker skidt, hvis der ikke er nogen bogstaver ovenfor. Det kan illustreres med en pyramide:

Skridt 0: Pyramiden er blank.

  *
 ***
*****

Skridt 1: Første række har kun ét tegn, så et vilkårligt af slagsen indsættes.

  a
 ***
*****

Skridt 2: Tegnafgrænsning for række 2 beregnes ud fra tegnene i række 1. Eftersom der kun er tegnet a, er det kun pladsen umiddelbart under a, der kan afgrænses (symboliseret med !). Tegnene til venstre og højre for dette kan være hvadsomhelst (symboliseret med ?). Dette resulterer i mange ordkandidater.

  a
 ?!?
*****

Skridt 2: En af de mange ordkandidater vælges og indsættes i række 2.

  a
 abc
*****

Skridt 3: Ækvivalent med skridt 2 kommer der igen 2 jokere i tegnafgrænsningen. Når listen af ordkandidater itereres, vil Trien internt gennemløbe samtlige mulige tegn på plads 1 i ordet (da det er en joker) for først derefter af lave reel afgrænsning. Dette er tungt arbejde. Jokere er onde!

  a
 abc
?!!!?

Teknik 10 (algoritme): Vælg den optimale gennemløbsretning

Hvis pyramiden fra ovenstående eksempel står på hovedet, er der ingen jokere. Et forløb kunne være

*****
 ***
  *
abers
 !!!
  *
abers
 ise
  !
abers
 ise
  t

Alle ordgennemløb vil være "perfekte", forstået på den måde at Trie iteratoren kan afgrænse ordene ét tegn af gangen, i stedet for at skulle kassere en masse blindgyder.

Det er nemt at beregne hvorvidt et top-bund- eller et bund-top-gennemløb vil være det mest effektive. Foretages et bund-top-gennemløb skal man dog være opmærksom på opslagene i ordbogen vil være fra slutningen af ordet (postfix) i stedet for starten (præfix). Det snedige og programmeringsarbejdebesparende trick er at spejle formen vandret og lodret, samt vende alle ordene i ordbogen om. Når resultaterne skal udskrives skal de også spejles vandret og lodret, men ellers skal der ikke ændres noget som helst i implementationen.

Den optimale gennemløbsretning var et fint trick, specielt fordi det ikke var besværligt at implementere. Desværre hjælper det ikke på former der er tunge i midten, som f.eks.

  *
 ***
********
    ***
     *

Her er top-bund præcis lige så tung som bund-top. En optimal gennemgang ville være

  *
 !!!
abemaden
    ***
     *
  !
 abe
abemaden
    ***
     *
  a
 abe
abemaden
    !!!
     *
  a
 abe
abemaden
    bad
     !
  a
 abe
abemaden
    bad
     g

Problemet er naturligvis at der skal afgrænses efter både præ- og post-fix. Det kunne man håndtere med to ordbøger, men hvad så med en form som f.eks. en ruder?

  *
 ***
*****
 ***
  *

Igen er det muligt at foretage et ideelt gennemløb, altså et gennemløb uden jokere.

  *
 !!!
atlas
 ***
  *
  *
 erg
atlas
 !!!
  *
  !
 erg
atlas
 bæk
  *
  e
 erg
atlas
 bæk
  !
  e
 erg
atlas
 bæk
  g

Her er det ikke bare præ- og post-fix der skal afgrænses på, men også forskellige infix. Det betyder ret mange ordbøger, for slet ikke at tale om udfordringen ved at beregne det optimale gennemløb for en vilkårlig form. Der er nok af arbejde at gå i gang med.

8. januar 2013

Teknik 10 er implementeret og programmet er blevet pudset af så det burde kunne bruges af andre end undertegnede. Det kan hentes fra download-boksen i toppen af denne side. Jeg har andre projekter, så jeg tror ikke jeg pusler videre med infix-opslag lige med det samme.

Et par indledende forsøg på at slå rekorder fra siden The National Puzzler's League Form Records virkede lovende for former som halfsquares, lattices og pyramids. Til gengæld var det meget tungt med octagons, pentagons og diamonds, hvor infix-opslag virkelig ville hjælpe.

Double right windmill 13

      eeriest
      clammer
      palmeri
      hilario
      odinian
      ninette
palingenesies
agendas      
vangeli      
ephebes      
teamers      
tiralee      
actress      

Kommentarer

Skriv en kommentar