Sisendite-väljundite ühendamine (routing)

Trükiplaatide projekteerimisel on tarvis lahendada järgmine probleem (routing problem): n x n tasapinnalisel võrestikul on antud m sisendit ja väljundit, mis on tarvis paarikaupa ühendada nii, et kõik ühendusteed kulgevad mööda võrestiku servi ja samal lõigul ei kulge kusagil kaks ühendusteed; ühendusteed võivad ristuda, kuid üheski punktis ei või ristuda rohkem kui kaks teed; sageli nõutakse ka ristumiste koguarvu minimaliseerimist. Ülesanne sarnaneb kaardil tee otsimise ülesandega: võrestiku servad on otseteed, sisend - lähtelinn ja väljund - sihtpunkt.

Järgnevas vaatleme programmi, mis lahendab selle ülesande lihtsustatud variandi: kõik sisendid ja kõik väljundid asuvad samas veerus (seega piisab nende esitamiseks vastava rea numbri andmisest; ühendada tuleb esimene sisend esimese väljundiga, teine sisend teise väljundiga jne. Peale sisendite ja väljundite reanumbreid (need sisestatakse ühekaupa, kui kõik numbrid on sisestatud, sisestatakse sõna 'kõik') on programmi sisendiks veel skeemi laius, st. veergude arv. skeemi ridade arvuks arvutatakse maksimum sisendite ja väljundite reanumbritest.

alga :-
write('Kus on algused? (sisesta ühekaupa, lõpetuseks: kõik) '),
nl,
sisesta(Xa),
write('Kus on lõpud?'),
nl,
sisesta(Xb),
write('Mitu veergu on? '),
read(V),
retractall(veerge(_)),
assert(veerge(V)),
salvesta_read(Xa,Xb),
teed(Xa,Xb,[]).
sisesta(Kokku) :-
hakka_lugema(Kokku,[]).
hakka_lugema(Kokku,Loetud) :-
read(Y),
(Y = kõik , ! , Kokku = Loetud , nl ; hakka_lugema(Kokku,[Y|Loetud])).
salvesta_read(Xa,Xb) :-
suurim(Xa,MXa),
suurim(Xb,MXb),
max(MXa,MXb,MX),
retractall(ridu(_)),
assert(ridu(MX)).
Predikaat suurim arvutab nimistu maksimaalse elemendi.

suurim([X],X) :-
!.
suurim([X|Y],Z) :-
suurim(Y,MY),
max(X,MY,Z).
max(X,Y,X):-
X>Y,!.
max(X,Y,Y).
Teed arvutab predikaat teed, mille esimeseks argumendiks on veel ühendamata sisendite nimistu, teiseks argumendiks - veel ühendamata väljundite nimistu ja kolmandaks - nimistu juba leitud teedest (iga tee on nimistu sellel esinevatest võrgu tippudest). Kui predikaat teed käivitatakse, on tema esimeseks ja teiseks argumendik kõigi sisendite ja väljundite nimistud ja kolmas argument on tühi nimistu (ühtegi teed not ole veel leitud). Kui kõik sisendid-väljundid on ühendatud, on esimene ja teine argument tühjad nimistud ja tuleb leitud teed ekraanile joonistada.

teed([],[],Teed) :-
!,
väljasta_teed(Teed).
teed([Xa|Xad],[Xb|Xbd],Senised) :-
veerge(N),
tee([Xa,1],[Xb,N],[],Tee,Senised),
teed(Xad,Xbd,[Tee|Senised]).
Uue tee leiab (st. ühendab ühe sisendi ja sellele vastava väljundi) predikaat tee (see on väga sarnane kahe linna vahelise tee leidmise predikaadiga). Selle esimene argument on punkt, kus ollakse praegu, teine - kuhu on tarvis jõuda, kolmas - nimistu võrgu tippudest, mis on juba läbitud, neljas argument on vajalik vaid lõpliku tee väljastamiseks ja viies argument on teiste seni juba leitud teede nimistu (seda vajatakse kontrollimiseks, et sama serva ei kasutata kaht korda). Punktide [X,Y] ja [X1,Y1] vahelist teed [X,Y,X1,Y1] (serva) valides püütakse algul liikuda vertikaalsuunas õigele (s.t. sihtpunkti) kõrgusele ja siis jätkata horisontaalsuunas Näiteks ülaloleval joonisel sisend [5,1] ja väljund [2,5] on ühendatud just nii (teede leidmine algab viimasena sisestatud punktist). Kui kohe õigele horisontaalreale jõudmine not õnnestu, liigutakse üks samm paremale ja proovitakse uuesti. Kõrvaloleval näitel on punktide [4,1] ja [1,5] ühendamisel Prolog olnud sunnitud tagurdama ja muutma algul leitud ühendusteed [[4,1],[4,2],[3,2],[2,2],[1,2],[1,3],[1,4],[1,5]], kui punkte [1,1],[4,5] ühendada püudes selgus, et see tee suleb edasipääsud punktist [1,1]. Abipredikaat kasutatud/3 (s.t. kolme argumendiga - erineva argumentide arvuga predikaadid on Prologis erinevad!) kontrollib, kas kaar (serv) esineb juba mingil teel (servade nimistu, kasutatud/3 teine argument), või teede nimistus (kasutatud/3 kolmas argument) esineval teel, käivitades vastavalt abipredikaadid kasutatud/2 ja kusagil/2. Abipredikaat kasutatud/2 kontrollib, kas kaar või vastassuunaline kaar esineb teel (kaarte nimistu) ja käivitab selleks abipredikaadi esineb. Abipredikaat kusagil/2 kontrollib, kas kaar esineb teede nimistus; selleks käivitab ta (selle kaarega) iga nimistus esineva tee jaoks abipredikaadi kasutatud/2. Abipredikaat esineb kontrollib, kas kaar (nelik [X,Y,X1,Y1]) esineb mingil teel (selliste nelikute nimistu.
tee([X,Y],[X,Y],Tee,Tee,_).
tee([X,Y],[X1,Y1],Käidud,Tee,Senised) :-
X < X1,
Y =< Y1,
Xuus is X + 1,
not kasutatud([X,Y,Xuus,Y],Käidud,Senised),
tee([Xuus,Y],[X1,Y1],[[X,Y,Xuus,Y]|Käidud],Tee,Senised).
tee([X,Y],[X1,Y1],Käidud,Tee,Senised) :-
X > X1,
Y =< Y1,
Xuus is X - 1,
not kasutatud([X,Y,Xuus,Y],Käidud,Senised),
tee([Xuus,Y],[X1,Y1],[[X,Y,Xuus,Y]|Käidud],Tee,Senised).
tee([X,Y],[X1,Y1],Käidud,Tee,Senised) :-
Y =< Y1,
Yuus is Y + 1,
not kasutatud([X,Y,X,Yuus],Käidud,Senised),
tee([X,Yuus],[X1,Y1],[[X,Y,X,Yuus]|Käidud],Tee,Senised).
kasutatud([X,Y,X1,Y1],Käidud,_) :-
kasutatud([X,Y,X1,Y1],Käidud).
kasutatud([X,Y,X1,Y1],_,Teed) :-
kusagil([X,Y,X1,Y1],Teed).
kusagil([X,Y,X1,Y1],[Tee|Teed]) :-
kasutatud([X,Y,X1,Y1],Tee).
kusagil([X,Y,X1,Y1],[_|Teed]) :-
kusagil([X,Y,X1,Y1],Teed).
kasutatud([X,Y,X1,Y1],Käidud) :-
esineb([X,Y,X1,Y1],Käidud).
kasutatud([X,Y,X1,Y1],Käidud) :-
esineb([X1,Y1,X,Y],Käidud).
esineb([X,Y,X1,Y1],[[X,Y,X1,Y1]|_]):-
!.
esineb([X,Y,X1,Y1],[_|Tee]):-
esineb([X,Y,X1,Y1],Tee).
tee([X,Y],[X,Y],Tee,Tee,_).
tee([X,Y],[X1,Y1],Käidud,Tee,Senised) :-
X < X1,
Y =< Y1,
Xuus is X + 1,
not kasutatud([X,Y,Xuus,Y],Käidud,Senised),
tee([Xuus,Y],[X1,Y1],[[X,Y,Xuus,Y]|Käidud],Tee,Senised).
tee([X,Y],[X1,Y1],Käidud,Tee,Senised) :-
X > X1,
Y =< Y1,
Xuus is X - 1,
not kasutatud([X,Y,Xuus,Y],Käidud,Senised),
tee([Xuus,Y],[X1,Y1],[[X,Y,Xuus,Y]|Käidud],Tee,Senised).
tee([X,Y],[X1,Y1],Käidud,Tee,Senised) :-
Y =< Y1,
Yuus is Y + 1,
not kasutatud([X,Y,X,Yuus],Käidud,Senised),
tee([X,Yuus],[X1,Y1],[[X,Y,X,Yuus]|Käidud],Tee,Senised).
Jääb järele teede väljastamine ekraanile.
väljasta_teed([]) :-
nl,
write('Kõik!').
väljasta_teed([Tee|Teed]) :-
väljasta(Tee),
väljasta_teed(Teed).
väljasta([[X,Y,X1,Y1]]) :-
!,
nl,
write(X),
write(','),
write(Y),
write(->),
write(X1),
write(','),
write(Y1).
väljasta([[X,Y,X1,Y1]|Lopp]) :-
väljasta(Lopp),
write(->),
write(X1),
write(','),
write(Y1).

Ülesandeid:
1. Laorobot Tee programm laos kaupu ümber paigutava roboti liikumistee arvutamiseseks. Antud on lao mõõtmed n,m; robot liigub mööda ruute (1,1), (2,2),...,(n,m). Iga kaubaühik katab igal hetkel täpselt ühe ruudu tipu. Antud on paigutamisele kuuluva kauba praegune asukoht (n1,m1) ja selle sihtkoht (n2,m2); ka juba laos olevate kaupade asukohad (s1,t1),(s2,t2),...,(sk,tk). Paigutamiseks võtab robot kauba sülle (astub paigutatava kauba ruudule) ja liigub siis sihtruudule; robot ei või astuda kaubale, mida ta praegu ei paiguta. Veidi raskem versioon: robot paigutab järjest kuitahes mitu kaubaühikut.


Küsimused, probleemid: ©2004 Jaak Henno