Krüptoritmide lahendamine

Mitmesugused arvude ja numbritega seotud ülesanded aitavad aru erinevatest võimalustest Prologis ülesande tingimuste kirjeldamiseks ja asjaoludest, mis mõjustavad programmi kiirust.

1. Teha programm, mis leiab kolmekohalise arvu, mille tagurpidi kirjutamisel saadud arv on esialgsest 396 võrra väiksem; näiteks 965 - 569 = 396.

Esimene lahendus . Otsime kohe kolmekohalist arvu X ja leiame selle numbrid N1, N2, N3 , kasutades süsteemipredikaati mod , mis annab mooduliga jagamisel saadava jäägi, näiteks 965 mod 100 on 65 ja selle esialgsest arvust lahutamisel saamegi sajaliste numbri.

Et arvutuste alates arvul X juba väärtus oleks, kasutame (lõpprekursiivset!) generaatorit vahel(Algus,X,Lopp) , mis genereerib kõk arvud vahemikus Algus...Lopp . Kuna me tahame kasutada seda predikaati kõigi arvude Algus ja Lopp vahel olevate arvude genereerimiseks, ei või siin kasutada lõiget !; kui järgnevas definitsioonis esimene lause oleks kujul vahel(A,A,L):-!, tuleks esimese argumendi väärtuseks vaid A ja predikaat lõpetaks pärast seda töö, s.t. me ei saaks ülejäänud väärtusi.

vahel(A,A,L).
vahel(A,X,L) :-
A1 is A+1,
A1 =< L,
!, % see hoiab ära tagurdamise, kui eelmine tingimus enam ei kehti
vahel(A1,X,L).

Et predikaat arvuta esitaks kõik lahendused, sunnitakse see pärast järjekordse tingimust rahuldava arvu leidmist ja väljastamist süsteemipredikaadiga fail (selle väärtus on alati FALSE) tagurdama (s.t. algab backtracking). Kui kõik lahendid on leitud, lõpetab predikaadi arvuta viimane lause töö ("puhtalt", s.t. ilma ebaõnnestumiseta).

arvuta:-
vahel(100,X,999),
K1 is X mod 100,
N1 is (X-K1)//100, % süsteemipredikaat // teisendab jagatise täisarvuks,
% reaalarvuga annaks süsteemipredikaat mod hiljem vea

X1 is X-100*N1,
K2 is X1 mod 10,
N2 is (X1-K2)//10,
N3 is X1-10*N2,
Y is 100*N3+10*N2+N1,
Z is X-Y,
Z = 396,
write(X),
nl,
fail.
arvuta.

See viimane lause on vajalik vaid selleks, et Prolog saaks lõpetada; esimese lause lõpus olev fail sunnib Prolog-i tagurdama (backtrack) ja leidma kõik lahendid; kui rohkem lahendeid ei leidu, lõpetab teine lause töö.
Teine lahendus . Otsime kolmekohalise arvu numbreid N1, N2, N3 ; algul tuleb muidugi selgitada, mis on number; seda võiks teha kas "otse" deklareerides


num(1).
...
kuid võib ka kasutada ülalkirjeldatud predikaati vahel :
num(X):-
vahel(0,X,9).

Lahendus on veel sirgjoonelisem kui eelmine:

arvuta1 :-
num(N1), N1>0, % muidu ei oleks arv kolmekohaline!
num(N2),
num(N3),
A1 is 100*N1+10*N2+N3,
A2 is 100*N3+10*N2+N1,
Z is A1-A2,
Z = 396,
write(A1),
nl,
fail.
arvuta1.

2. Samal viisil saab lahendada ka krüptoritme, näiteks

  EIN
+ EIN
+ EIN
+ EIN
-----
 VIER
  ONE
+ ONE
+ ONE
+ ONE
-----
 FOUR
   SEND
 + MORE
  -----
  MONEY
   VATER
+ MUTTER
  ------
  ELTERN
   GAUSS
 + RIESE
  ------
  EUKLID
   FORTY
   + TEN
   + TEN
  ------
   SIXTY

(iga täht tähistab mingit numbrit 0..9, erinevad tähed tähistavad erinevaid numbreid ja arvud ei alga 0-ga). Näiteks esimese ülaltoodud krüptoritmi lahendid leiab predikaat:

leia(E,I,N,V,R):-
num(E), E>0,
num(I), not(I=E),
num(N), not(N=E),not(N=I),
num(V), V>0, not(V=E),not(V=I),not(V=N),
num(R), not(R=E),not(R=I),not(R=N),not(R=V),
kitsendused(E,I,N,V,R).
kitsendused(E,I,N,V,R):-
A1 is 4*(100*E+10*I+N),
A2 is 1000*V+100*I+10*E+R,
A2 = A1.

(sellel krüptoritmil on vaid 1 lahend!).

Ülalesitatud "jõumeetodil" lahendus on tegelikult väga aeglane, sest Prolog peab läbi uurima peaaegu kõik 10^5 lahendusvarianti (kõigi viie muutuja kümme võimalikku väärtust). Palju kiirema lahenduse saame, kui jälgime protseduuri, mida inimesed kasutavad tulba kujul liitmisel:

    EIN
  + EIN
  + EIN
  + EIN
     CD
  -----
   VIER
(C, D on ülekandenumbrid, liidetavate arvu põhjal C < 4, D < 5). Tulba kujul liitmise algoritmis (viimasest numbrist ettepoole) hakatakse genereeritud numbritele kohe kitsendusi rakendama, seega versioonid hakkavad kiiresti kaduma ja programmi töö muutub mitu korda kiiremaks:

kitsendused1(E,I,N,V,R):-
num(C),C< 4,
num(D),D< 5,
S1 is 4 * N,
S1 is 10 * C+R,
S2 is 4 * I + C,
S2 is 10 * D + E,
S3 is 4 * E + D,
S3 is 10*V + I.
Kitsendusi peab püüdma arvesse võtta nii pea kui võimalik, see vähendab läbivaadatave variantide arvu ja kiirendab prologi tööd. Näiteks ülaltoodud krüptoritmi
    SEND
  + MORE
  ------
   MONEY
lahendamisel peab olema S > 0, M > 0 ja S+M > 8 (muidu ei saa tekkida viiekohaline summa). Sellepärast defineerime abipredikaadi enumbers/3 (kolmekohalise); enumbers leiab esimese argumendi kohale nimistu erinevatest numbritest; selle pikkus on määratud teise argumendiga (seda predikaati kasutades otsitakse krüptoritmi numbrid S, M ); kolmas argument on nn "keelatud" numbrid, s.t. neid ei tohi kasutada (ülejäänud numbrite otsimisel on näiteks keelatud kasutata S,M väärtusi). Selline kaheosaline otsimine töötab oluliselt kiiremini kui kõigi muutujate otsimine korraga:
num(0).
num(1).
num(2).
num(3).
num(4).
num(5).
num(6).
num(7).
num(8).
num(9).
enumbers(N,L,M):-
%M- juba kasutatud numbrid, L - otsitava nimistu (esim argument) pikkus
enum(N,[],0,L,M).
% käivitab lõpprekursiivse abiprogrammi
enum(N,N,L,L,M):-!.
enum(N,Y,L1,L,M):-
num(X),
not(member(X,M)), % keelatud numbreid ei või kasutada
not(member(X,Y)), % uus number peab erinema eelnevatest
L2 is L1+1,
enum(N,[X|Y],L2,L,M).
member(X,[X|_]):-!.
member(X,[_|Y]):-
member(X,Y).
solve:-
enumbers([S,M],2,[]),
M>0,S>0,
S+M > 8, %kuna S,M peavad olema erinevad, on max 8+9+1(ülekanne)
enumbers([E,N,D,O,R,Y],6,[S,M]),
(C1 is 0; C1 is 1), % ülekanded on kõik kas 0 või 1
(C2 is 0; C2 is 1),
(C3 is 0; C3 is 1),
Y1 is D + E ,
Y2 is 10 * C1+Y,
Y1 = Y2,
E1 is N + R + C1,
E2 is 10 * C2 + E,
E1 = E2,
N1 is E + O+ C2,
N2 is 10 * C3 + N,
N1 = N2,
O1 is S + M + C2,
O2 is 10*M + O,
O1 = O2,nl,
write(' '),
write(S),write(E),write(N),write(D),nl,
write(' '),write(M),write(O),write(R),write(E),nl,
write(M),write(O),write(N),write(E),write(Y).

Ülesandeid:
1. Tee predikaadid teiste ülaltoodud krüptoritmide lahendamiseks.
2. Tee programm, mis leiab kõrvalolevas arvlabyrindis silmusteta tee, millel läbitud ruutude kogusumma oleks etteantud arv (näiteks 69).
3. Tee programm, mis paigutab 3x3 maatriksisse numbrid 1..9 (kõik erinevad) nii, et numbrite summa kõigis horisontaal- ja vertikaalridades oleks sama, näiteks agu kõrvaloleval joonisel (nn "maagiline ruut"; neid kasutatakse näiteks katsete planeerimisel)
4. Järgnevates krüptoritmides tähistab * mingit paarisnumbrit (0,2,4,6,8), # - paaritut numbrit (1,3,5,7,9) ja % - mingit algnumbrit (1,3,5,7).
    **#
  
x  ##
  -----
   *#*#
   *## 
  -----
  #####
    %%%
  
x  %%
  -----
   %%%%
  %%%% 
 ------
 %%%%%%
1. Ylesande tekst
1. Ylesande tekst


Küsimused, probleemid: ©2004 Jaak Henno