BME Villamosmérnöki és Informatikai Kar
Műszaki informatika szak
Nappali tagozat
2001/2002-es tanév, tavaszi félév

Deklaratív programozás

Házi feladat, 1.2. változat

Bűvös csiga

Ez a leírás a Deklaratív programozás c. tárgyból kiadott féléves házi feladatot ismerteti.

A feladvány

Adott egy n*n mezőből álló négyzet alakú tábla, amelynek egyes mezőiben 1 és m közötti számok szerepelnek. A feladat az, hogy további 1 és m közötti számokat helyezzünk el a táblában úgy, hogy az alábbi feltételek teljesüljenek:
  1. Minden sorban és minden oszlopban az 1..m számok mindegyike pontosan egyszer szerepel.
  2. A bal felső sarokból induló csigavonal mentén a számok rendre az 1,2,...m,1,2,...,m,... sorrendben követik egymást.
A csigavonalat a következőképpen definiáljuk. Először a négyzet első sorában haladunk balról jobbra, majd az utolsó oszlopban felülről lefelé. Ezután az utolsó sorban megyünk jobbról balra, majd az első oszlopban alulról felfelé, egészen a 2. sor 1. mezőjéig. Miután így bejártuk a négyzetes tábla szélső sorait és oszlopait, rekurzívan folytatjuk a bejárást a 2. sor 2. mezőjében kezdődő (n-2)*(n-2) mezőből álló négyzettel.

Az 1. ábra egy feladványt ábrázol, a 2. ábra ennek (egyetlen) megoldását mutatja.

-----------------------\
                  2     |
/-------------------\   |
|     1             |   |
|   /-----------\   |   |
|   |           |   |   |
|   |   /---\   |   |   |
|   |   |       |   | 1 |
|   |   \-------/   |   |
|   |               |   |
|   \---------------/   |
|                       |
\-----------------------/
           
-----------------------\
  1   -   -   -   2   3 |
/-------------------\   |
| -   1   2   3   - | - |
|   /-----------\   |   |
| - | 3   1   2 | - | - |
|   |   /---\   |   |   |
| - | 2 | 3   - | - | 1 |
|   |   \-------/   |   |
| 3 | -   -   -   1 | 2 |
|   \---------------/   |
| 2   -   -   1   3   - |
\-----------------------/
1. ábra. Egy feladvány(n=6, m=3)             2. ábra. A feladvány megoldása

A megoldandó feladat

Írjon buvos_csiga néven olyan Prolog-eljárást, ill. SML-függvényt, amely egy feladvány összes megoldását előállítja!

A Prolog-eljárásnak két paramétere van. Első (bemenő) paramétere a feladványt, második (kimenő) paramétere a megoldást írja le. Az eljárásnak a visszalépések során minden megoldást pontosan egyszer kell kiadnia. Ha a feladványnak nincs megoldása, az eljárás hiúsuljon meg.

A Prolog-eljárás bemenő paramétere egy

     csiga(N, M, As)
felépítésű struktúra, ahol

Az 1. ábrán bemutatott feladvány esetén például a Prolog-eljárás bemenő paramétere:

     csiga(6, 3, [i(1,5,2),i(2,2,1),i(4,6,1)])

A Prolog-eljárás kimenő paramétere a kitöltött tábla. Ezt sorok listájaként kell előállítani, ahol az egyes sorok egész számokból álló listák. Ez utóbbi lista minden eleme a tábla egy mezőjét írja le: a 0 számérték jelenti az üres helyet, míg a nem-nulla I érték azt jelenti, hogy az adott helyen az I szám áll.

A 2. ábrán látható megoldást írja le az alábbi lista:

     [[1,0,0,0,2,3],
      [0,1,2,3,0,0],
      [0,3,1,2,0,0],
      [0,2,3,0,0,1],
      [3,0,0,0,1,2],
      [2,0,0,1,3,0]]
Az SML-függvény paramétere egy (N, M, As) hármas, ahol N és M jelentése azonos a Prolog-eljárásnál leírtakkal, míg As egy lista, amelynek elemei (Sor,Oszlop,Érték) alakú hármasok, amelyek jelentése szintén azonos a Prolog-eljárásnál leírtakkal.

Az 1. ábrán bemutatott feladvány esetén például az SML-függvény paramétere:

     (6, 3, [(1,5,2),(2,2,1),(4,6,1)])
Az SML-függvény eredménye egy olyan lista, amely a feladvány összes megoldását tartalmazza, mégpedig minden megoldást pontosan egyszer. Ha a feladatnak nincs megoldása, az eredmény az üres lista. Egy megoldást a Prologgal azonos módon, listák listájaként kell megadni.

A 2. ábrán látható megoldást írja le például az alábbi (egyelemű) megoldás-lista:

     [[[1,0,0,0,2,3],
       [0,1,2,3,0,0],
       [0,3,1,2,0,0],
       [0,2,3,0,0,1],
       [3,0,0,0,1,2],
       [2,0,0,1,3,0]]
     ]

A megírandó függvény, ill. eljárás specifikációja

A buvos_csiga függvény paraméterének és eredményének típusát a következő SML-típusdefiníciók írják le.

structure TCsiga =
struct
      type meret           = int (* 0 < meret *)
      type ciklus          = int (* 0 < ciklus <= meret *)
      type sorszam         = int (* 0 < sorszam <= meret *)
      type oszlopszam      = int (* 0 < oszlopszam <= meret *)
      type ertek           = int (* 0 < ertek <= ciklus *) 
      type adott_elem      = sorszam * oszlopszam * ertek
      type feladvany_leiro = meret * ciklus * adott_elem list

      type ertek_vagy_ures = int (* ertek_vagy_ures = 0, ha ures,
                                    egyebkent 0 < ertek_vagy_ures <= ciklus *)
      type sor             = ertek_vagy_ures list
      type csigatabla      = sor list
end
signature Csiga =
sig
  (* buvos_csiga (N, M, (Sor,Oszlop,Ertek) list) = olyan lista, amelynek az
          elemei (tetszőleges sorrendben) az összes olyan N méretű,
          egymástól különböző táblák, amelyek mindegyike a felsorolt
          (Sor,Oszlop,Ertek) hármasok által meghatározott M ciklusú
          csigavonalat ír le
  *)
  val buvos_csiga : TCsiga.feladvany_leiro -> TCsiga.csigatabla list
end
A Csiga.buvos_csiga függvény feladvany_leiro típusú paramétere mindenképpen kielégíti az alábbi feltételt:
load "Listsort";

fun helyes_feladvany_leiro(Meret, Ciklus, Adottak) =

    let
	fun helyes_adott_elem(Sor, Oszlop, Ertek) = 0 < Sor andalso
	      Sor <= Meret andalso 0 < Oszlop andalso Oszlop <= Meret andalso
	      0 < Ertek andalso Ertek <= Ciklus;

	fun lexikografikusan_hasonlit ((x1,y1,_), (x2,y2,_)) =
	      if      x1 < x2 orelse x1 = x2 andalso y1 < y2 then LESS
	      else if x1 > x2 orelse x1 = x2 andalso y1 > y2 then GREATER
	      else EQUAL

	fun szomszedok_kulonbozok ((x1,y1,_)::(x2y2s as (x2,y2,_)::xys)) =
	   (x1 <> x2 orelse y1 <> y2) andalso szomszedok_kulonbozok x2y2s
	  | szomszedok_kulonbozok _ = true
    in
	0 < Meret andalso 0 < Ciklus andalso Ciklus <= Meret andalso
          (* minden eleme helyes az Adottak listának: *)
        (List.all (fn x => x) (map helyes_adott_elem Adottak)) andalso
          (* az Adottak lista lexikografikusan rendezett: *)
	Listsort.sorted lexikografikusan_hasonlit Adottak andalso
	  (* Az Adottak lista minden eleme különböző (Sor,Oszlop) 
	     koordinátára vonatkozik (a rendezettség miatt elegendő
             ezt a szomszédos elemekre kikötni): *)
	szomszedok_kulonbozok Adottak
end
Az SML megoldásában tehát feltételezheti, hogy a bemenő argumentumra a helyes_feladvany_leiro predikátum igazat ad eredményül (csak ilyen adattal teszteljük majd a programot).

A buvos_csiga/2 Prolog-eljárás paramétereinek típusát a következő - megjegyzésként megadott - Prolog-típusdefiníciók írják le.

% :- type feladvany_leiro ---> csiga(meret, ciklus, list(adott_elem)).
% :- type meret             == integer.
% :- type ciklus            == integer.
% :- type adott_elem      ---> i(sorszam, oszlopszam, ertek).
% :- type sorszam           == integer.
% :- type oszlopszam        == integer.
% :- type ertek             == integer.
		          
% :- type csigatabla        == list(sor).
% :- type sor               == list(ertek_vagy_ures).
% :- type ertek_vagy_ures   == integer.

% :- pred buvos_csiga(feladvany_leiro::in, csigatabla::out).
A buvos_csiga/2 eljárás első, feladvany_leiro típusú paramétere mindenképpen kielégíti az alábbi feltételt:
helyes_feladvany_leiro(csiga(Meret,Ciklus,Adottak)) :-
	integer(Meret), integer(Ciklus),
	Meret >= 1, Ciklus >= 1,
	Ciklus =< Meret.
        % nincs nem helyes eleme az Adottak listának:
	\+ (   member(Adott, Adottak), 
	       \+ helyes_adott_elem(Meret, Ciklus, Adott)
	   ),  
	% az Adottak lista lexikografikusan rendezett:
	sort(Adottak, Adottak),
	% Az Adottak lista minden eleme különböző (Sor,Oszlop) 
	% koordinátára vonatkozik (a rendezettség miatt elegendő a
        % szomszédos elemekre kikötni ezt):
	\+ append(_, [i(S,O,_),i(S,O,_)|_], Adottak).

helyes_adott_elem(Meret, Ciklus, i(Sor,Oszlop,Ertek)) :-
	integer(Sor), integer(Oszlop), integer(Ertek),
	1 =< Sor, Sor =< Meret,
	1 =< Oszlop, Oszlop =< Meret,
	1 =< Ertek, Ertek =< Ciklus.
A Prolog megoldásában tehát feltételezheti, hogy a bemenő argumentumra a helyes_feladvany_leiro/1 eljárás sikeresen lefut (csak ilyen adattal teszteljük majd a programot).

A keretprogram

Programjait keretprogramok segítségével próbálhatja ki. A keretprogramokat a specifikációs modulokkal és egy, a beadáskor használthoz hasonló tesztsorral együtt letöltheti innen. A házi feladatok teszteléséhez és értékeléséhez ezekkel kompatíbilis programokat fogunk használni.

A keretprogram bemenete egy olyan szöveges állomány, amelynek első két sora a feladványtábla mérete és a csigavonal ciklusa. Az ezt követő sorok mindegyikében három szám áll, amelyek az adott elemek sorszámát, oszlopszámát ill. értékét írják le (ld. 3. ábra, vö. 1. ábra).

6                                         
3                                        
1 5 2
2 2 1
4 6 1
3. ábra. A bemenő állomány tartalma az 1. ábrán látható feladvány esetén

A keretprogram kimenete a 2. ábrán bemutatotthoz hasonló tartalmú szöveges állomány.

Az SML-keretprogram három függvényt exportál:

signature KCsiga =
sig
    val csiga_be : string -> TCsiga.feladvany_leiro
    val csiga_ki : string * TCsiga.csigatabla list -> unit
    val megold   : string * string -> string
end
csiga_be f
Az f szöveges állományból beolvasott adatokból képzett feladványleíró.
csiga_ki(m, ms)
Az ms megoldáslistát olvasható formában kiírja az m szöveges állományba.
megold(f, m)
Beolvas egy feladványt az f szöveges állományból és kiírja az összes megoldást az m szöveges állományba. Ehhez természetesen szüksége van a Csiga.buvos_csiga függvényre. Eredménye az f nevet, a megoldások számát és a futási időt mp-ben tartalmazó füzér.
A bemenetet, ill. a kimenetet a terminálra irányíthatjuk Unix alatt a /dev/stdin, ill. a /dev/stdout, MS-DOS alatt mindkét esetben a con állománynév megadásával.

A Prolog-keretprogram három eljárást exportál:

csiga_be(file::in, feladvany_leiro::out)
A megnevezett szöveges állományból beolvassa a feladványleírót, és visszaadja a második argumentumban.
csiga_ki(file::in, csigatabla::in)
A megnevezett szöveges állományba kiírja az adott megoldást.
megold(file::in, file::in)
Beolvas egy feladványleírót az első paraméterként kapott állományból, és kiírja az ahhoz tartozó összes megoldást a második paraméterben megadott állományba. Ehhez természetesen szüksége van a buvos_csiga/2 eljárásra.

A keretprogram felhasználja a file típust:

     % :- type file == atom.
A file típusú paraméterben a user atomot megadva a terminálról vihetünk be, ill. a terminálra irathatunk ki adatokat. Egyébként a megadott nevű állományba írunk, ill. állományból olvasunk.

Dokumentációs követelmények

Mindkét programot úgy írja meg (választása szerint vagy magyarul, vagy angolul), hogy a szövegük jól olvasható legyen: válasszon értelmes neveket, specifikálja a paraméterek és más azonosítók szerepét és értelmezési tartományát, magyarázza meg az egyes eljárások ill. függvények feladatát és működését! Írjon minden eljáráshoz és függvényhez fejkommentet!

A két programhoz közös dokumentációt készítsen - vagy magyarul, vagy angolul. A dokumentáció tartalma legalább a következő legyen:

Fogalmazzon világosan és tömören, ügyeljen a helyes nyelvhasználatra és a helyesírási szabályok betartására! Az indokoltan elvárható formai követelmények be nem tartását, a gondatlan kivitelt szankcionáljuk.

A dokumentációt elektronikus formában adja be, HTML, esetleg ASCII formátumban.

Egyéb követelmények és tudnivalók

A programok készülhetnek MS DOS vagy MS Windows alatt is, de Unix (linux) alatt is működniük kell. A beadott programok tesztelése Unix környezetben a SICStus Prolog ill. az MOSML rendszerekkel történik. A programokat és az elektronikus dokumentációt tömörítve, elektronikus levélben kell elküldeni egy bash szkript segítségével, amely hamarosan letölthető lesz. Programját egy 10-tagú tesztsorozattal automatikusan futtatjuk, és erről válaszlevélben értesítjük. A beadást többször is megismételheti, a legutolsóként beadott megoldást értékeljük.

A házi feladat beadása nem kötelező, de a DP nyelvek magasszintű elsajátításához nagy segítséget ad. Ezért javasoljuk, hogy a házi feladatot mindkét nyelven készítsék el és adják be. Mindazonáltal elfogadunk csak SML ill. csak Prolog nyelven készült megoldásokat, értelemszerűen felére csökkentett pontszámmal.

A programok és az elektronikus dokumentáció végső beadási határideje 2002. május 6-a, hétfő 24h.

Az értékelés a beadási tesztsorozathoz hasonló nehézségű tesztesetekkel történik. Az időkorláton belül helyes eredményt adó tesztesetek mindegyikére 0,5-0,5 pontot adunk. A 10 futásra összesen tehát nyelvenként maximum 5 pontot kaphat. Ezen felül a dokumentációt, a programkód olvashatóságát, kommentezettségét is értékeljük, nyelvenként maximum 2,5 ponttal. Ha mindkét nyelvből beadja a házi feladatot, akkor tehát maximum 15 pontot kaphat (ez 15%-a a 100 pontos alappontszámnak).

Azok a programok, amelyek az alapteszt minden feladatát helyesen és az előírt időkorláton belül megoldják, létraversenyben vesznek részt. A leggyorsabban futó SML-, ill. Prolog-programokra - helyezési számuktól függően - külön-külön 7,5 és 0,5 közötti pluszpont kapható. Ha valaki tehát mindkét nyelvből a leggyorsabb programot írja meg, akkor a 100 pontos alappontszámon felül 15 pluszpontot kap.


dp@inf.bme.hu
Utolsó módosítás: 2002. 04. 17.