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

Deklaratív programozás

Házi feladat, 1.5. változat

Felhők

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*m mezőből álló téglalap, amely egy radarernyőt ábrázol. A radarral felhőket figyelünk meg, a radarernyő minden mezője vagy egy felhőhöz, vagy a tiszta égbolthoz tartozik. Az alábbi feltételek mindig teljesülnek:

  1. A felhők téglalap alakúak, mind vízszintesen, mind függőlegesen legalább két egység hosszúak.
  2. A felhők egymást még sarkosan sem érintik.
A feladat az, hogy egy ún. radarkép alapján rekonstruáljuk a felhők elrendezését. Egy radarkép bizonyos információkat tartalmaz, ezek a következők:
  1. a radarernyő mérete,
  2. egyes oszlopok és sorok esetén a felhőrészletet tartalmazó (azaz felhős) mezők száma,
  3. egyes mezők esetén az is, hogy az adott mező felhőrészletet tartalmaz (azaz felhős) vagy nem tartalmaz (azaz felhőtlen).
Egy radarképhez természetesen több felhőelrendezés is tartozhat, tehát egy feladványnak több megoldása is lehet.

Az 1. ábra egy feladványt ábrázol, a 2. ábra ennek (egyetlen) megoldását mutatja. Az ábrán minden sorhoz és oszlophoz megadtunk egy számot. Ha ez a szám pozitív, akkor a felhős mezők számát adja meg, míg a -1 érték azt jelzi, hogy az adott sorról, ill. oszlopról nincs információ. Jelölések: # felhős mező; . felhőtlen mező.

  +---+---+---+---+---+
  |   |   |   |   |   | 4
  +---+---+---+---+---+
  |   |   |   |   | . | 4
  +---+---+---+---+---+
  |   |   |   |   |   | 0
  +---+---+---+---+---+
  |   | # |   |   |   | -1
  +---+---+---+---+---+
  |   |   |   |   |   | 4
  +---+---+---+---+---+
    4   4  -1   4   2

     
  +---+---+---+---+---+
  | # | # | # | # | . | 4
  +---+---+---+---+---+
  | # | # | # | # | . | 4
  +---+---+---+---+---+
  | . | . | . | . | . | 0
  +---+---+---+---+---+
  | # | # | . | # | # | -1
  +---+---+---+---+---+
  | # | # | . | # | # | 4
  +---+---+---+---+---+
    4   4   -1  4   2

     
1. ábra. Egy feladvány (n=5, m=5) 2. ábra. A feladvány megoldása

A megoldandó feladat

Írjon felhok 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

    f(SOL, OOL, RefPL)

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:

    f([4,4,0,-1,4], [4,4,-1,4,2], [e(2,5), f(4,2)]) 

A Prolog-eljárás kimenő paramétere az eredményt egy felhőlista formájában adja meg, a lista elemei egy-egy felhőt írnak le f(KS, KO, ZS, ZO) alakban, ahol a felhő a KS..ZS sorok és a KO..ZO oszlopok metszete.

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

    [f(1,1,2,4), f(4,1,5,2), f(4,4,5,5)] 

Az SML-függvény paramétere egy (SOL, OOL, RefPL) hármas, ahol SOL, OOL és RefPL jelentése 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:

    ([4,4,0,~1,4], [4,4,~1,4,2], [e(2,5), f(4,2)])

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-egy megoldást a felhők - (KS, KO, ZS, ZO) négyesek - listájaként kell megadni, a Prolog-megoldáshoz hasonlóan.

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

    [[(1,1,2,4), (4,1,5,2), (4,4,5,5)]] 

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

A felhok/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 radarkep        ---> f(list(sorosszeg), list(oszloposszeg), list(ref_pont)).
% :- type sorosszeg         == integer.
% :- type oszloposszeg      == integer.
% :- type ref_pont        ---> f(sor, oszlop) | e(sor, oszlop).
% :- type sor               == integer.
% :- type oszlop            == integer.

% :- type felhok            == list(felho).
% :- type felho           ---> f(kezdosor, kezdooszlop, zarosor, zarooszlop).
% :- type kezdosor          == sor.
% :- type kezdooszlop       == oszlop.
% :- type zarosor           == sor.
% :- type zarooszlop        == oszlop.

% :- pred felhok(radarkep::in, felhok::out).

A felhok/2 eljárás első, radarkep típusú paramétere mindenképpen kielégíti az alábbi feltételt:

helyes_feladvany(f(SorOsszegek, OszlopOsszegek, RefPL)) :-
        length(SorOsszegek, N),
        length(OszlopOsszegek, M),
        % a referenciapontok koordinátáinak KoordL listája
        % az eredetivel azonos sorrendű, ahol minden
        % referenciapont eget vagy felhőt jelöl:
        koordinatak(RefPL, KoordL),
        % az összegek legkisebb értéke -1 lehet,
        % de nem haladhatják meg a tábla méretét:
        forall( member(S, SorOsszegek), (-1 =< S, S =< M) ),
        forall( member(O, OszlopOsszegek), (-1 =< O, O =< N) ),
        % a KoordL lista minden eleme helyes:
        forall( member(Koord, KoordL), helyes_koordinata(Koord, N-M) ),
        % a KoordL lista lexikografikusan rendezett;
        % ebből következően RefPL is:
        sort(KoordL, KoordL),
	% A KoordL lista bármely két eleme különböző; 
        % ebből következően a RefPL listában szereplő 
	% (Sor,Oszlop) koordináták is páronként különböznek
        % (a rendezettség miatt elegendő ezt a szomszédos elemekre kikötni):
        forall( append(_, [K1, K2|_], KoordL), K1 \= K2 ).

koordinatak([], []).
koordinatak([e(S,O)|RefPL], [S-O|KoordL]) :-
        koordinatak(RefPL, KoordL).
koordinatak([f(S,O)|RefPL], [S-O|KoordL]) :-
        koordinatak(RefPL, KoordL).

helyes_koordinata(S-O, MaxS-MaxO) :-
        integer(S), S > 0, S =< MaxS,
        integer(O), O > 0, O =< MaxO.

%  forall(Cél1, Cél2) : Cél1 minden megoldása teljesíti Cél2-t.
forall(Goal1, Goal2) :-
        (   Goal1, \+ Goal2 -> fail
        ;   true
        ).

A Prolog megoldásában tehát feltételezheti, hogy a bemenő argumentumra a helyes_feladvany/1 eljárás sikeresen lefut (csak ilyen adattal teszteljük majd a programot).

A felhok 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 TFelhok =
struct
      type sorosszeg    = int
      type oszloposszeg = int
      type sor          = int
      type oszlop       = int
      datatype refpont  = f of sor * oszlop | e of sor * oszlop
      type radarkep     = sorosszeg list * oszloposszeg list * refpont list
      type kezdosor     = int
      type kezdooszlop  = int
      type zarosor      = int
      type zarooszlop   = int
      type felho        = kezdosor * kezdooszlop * zarosor * zarooszlop
      type felhok       = felho list
end

signature Felhok =
sig
  val felhok : TFelhok.radarkep -> TFelhok.felhok list
end

A Felhok.felhok függvény TFelhok.radarkep típusú paramétere mindenképpen kielégíti az alábbi feltételt:

app load ["Listsort"];

fun helyes_feladvany ((SorOsszegek, OszlopOsszegek, RefPL) : TFelhok.radarkep) =
    let
        val n = length SorOsszegek
        val m = length OszlopOsszegek

	fun ref_pont_koord (TFelhok.f(Sor, Oszlop)) = (Sor, Oszlop)
          | ref_pont_koord (TFelhok.e(Sor, Oszlop)) = (Sor, Oszlop)

	fun helyes_koord (Sor, Oszlop)  =
              Sor > 0 andalso Sor <= n andalso
              Oszlop > 0 andalso Oszlop <= m 

	val helyes_ref_pont = helyes_koord o ref_pont_koord

	fun helyes_osszegek ((osszeg::osszegek), max) =
              osszeg > ~2 andalso osszeg <= max andalso
              helyes_osszegek (osszegek, max)
          | helyes_osszegek _ = true

        fun lexikogr_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 lexikografikusan_hasonlit (rp1, rp2) =
              lexikogr_hasonlit (ref_pont_koord rp1, ref_pont_koord rp2)

	fun szomszedok_kulonbozok (rp1::(rps as rp2::_)) =
	      lexikografikusan_hasonlit (rp1, rp2) <> EQUAL
	      andalso szomszedok_kulonbozok rps
	  | szomszedok_kulonbozok _ = true

    in
          (* az összegek a megadott intervallumon belül vannak: *)
       helyes_osszegek (SorOsszegek, m) andalso
       helyes_osszegek (OszlopOsszegek, n) andalso
          (* a RefPL lista minden eleme helyes: *)
       (List.all (fn x => x) (map helyes_ref_pont RefPL)) andalso
          (* a RefPL lista lexikografikusan rendezett: *)
       Listsort.sorted lexikografikusan_hasonlit RefPL andalso
	  (* a RefPL listában szereplő (Sor,Oszlop) koordináták
	     páronként különböznek (a rendezettség miatt elegendő
             ezt a szomszédos elemekre kikötni): *)
       szomszedok_kulonbozok RefPL
    end

Az SML megoldásában tehát feltételezheti, hogy a bemenő argumentumra a helyes_feladvany függvény igazat ad eredményül (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ő sorában a sorösszegek szerepelnek szóközzel elválasztva, második sorában az oszlopösszegek hasonló módon. Az ezt követő sorok mindegyikében egy betű és két szám áll, amelyek az adott referenciapont típusát, valamint sor- és oszlopszámát határozzák meg. Ezekre a bejegyzésekre a már fent említett korlátok vonatkoznak, tehát a sor- és oszlopszámok a táblán belüli mezőt azonosítanak, a típust jelölő karakter pedig e vagy f (ld. 3. ábra, vö. 1. ábra).

4 4 0 -1 4
4 4 -1 4 2
e 2 5
f 4 2

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.

A Prolog-keretprogram

A Prolog-keretprogram öt eljárást exportál:

helyes_feladvany(radarkep::in)
A fent ismertetett feladványellenőrző eljárás.
felhok_be(file::in, radarkep::out)
A megnevezett szöveges állományból beolvassa a radarképet, és visszaadja a második argumentumban.
felhok_ki(file::in, radarkep::in, felhok::in)
A megnevezett szöveges állományba kiírja az adott radarképhez tartozó adott megoldás szöveges alakját.
megold(file::in, file::in)
Beolvas egy feladványt 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 felhok/2 eljárásra.
stopper(file::in, file::in)
Mint megold/2, de a futás végén kiírja a futási időt is.

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 írathatunk ki adatokat. Egyébként a megadott nevű állományba írunk, ill. állományból olvasunk.

Használat: a saját programját a felhok.pl nevű állományba tegye, különben a keretprogram nem találja meg. Ezután futtassa a SICStus interpretert, és töltse be a keretprogramot:

SICStus 3.10.0 (x86-linux-glibc2.2): Thu Jun 27 22:46:49 CEST 2002
Licensed to BUTE DP course
| ?- [kfelhok].
% consulting /home/david/uni/dp/04s/nhf/kfelhok.pl...
%  module felhok_keret imported into user
%  loading /usr/local/lib/sicstus-3.9.1/library/lists.po...
%  module lists imported into felhok_keret
%  loaded /usr/local/lib/sicstus-3.9.1/library/lists.po in module lists, 0 msec 11656 bytes
% consulted /home/david/uni/dp/04s/nhf/kfelhok.pl in module felhok_keret, 20 msec 29168 bytes
yes
| ?- stopper(..., ...).

A stopper/2, ill. megold/2 eljárások meghívása a felhok.pl programot automatikusan betölti (ha szükséges), ezért ennek módosításakor nem kell sem a SICStus interpretert újraindítania, sem a keretprogramot újra betöltenie.

Az SML-keretprogram

Az SML-keretprogram négy függvényt exportál:

signature KFelhok =
sig
    val helyes_feladvany : TFelhok.radarkep -> bool

    val felhok_be         : string -> TFelhok.radarkep
    val felhok_ki         : string * TFelhok.radarkep * TFelhok.felhok list -> unit

    val megold           : string * string -> string
end

helyes_feladvany rk
A fent ismertetett feladványellenőrző függvény.
felhok_be f
Az f szöveges állományból beolvasott adatokból képzett radarkép.
felhok_ki(m, rk, fk)
A rk radarkép fk megoldáslistájá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 Felhok.felhok függvényre. Eredménye az f nevét, 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.

Használat: először fordítsa le az összes modult, beleértve a felhok függvényt megvalósító Felhok.sml nevű saját programját is az alábbi paranccsal:

mosmlc -c TFelhok.sml Felhok.sig Felhok.sml KFelhok.sig KFelhok.sml

Ezután töltse be az interpreterbe és nyissa meg a KFelhok modult:

Moscow ML version 2.00 (June 2000)
Enter `quit();' to quit.
- load "KFelhok";
> val it = () : unit
- open KFelhok;

> val helyes_feladvany = fn : TFelhok.radarkep -> bool
  val felhok_ki = fn : string * TFelhok.radarkep * TFelhok.felhok list -> unit
  val megold = fn : string * string -> string
  val felhok_be = fn : string -> TFelhok.radarkep
- megold (..., ...);

A saját programja módosításakor elegendő a Felhok.sml állományt újrafordítani, és célszerű az SML értelmezőt újraindítani.

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! Kövesse a Prolog, ill. SML jegyzetben látható szövegelrendezési szokásokat! Ne írjon 72 karakternél hosszabb sorokat! Minden eljáráshoz és függvényhez készítsen fejkommentet!

A két programhoz készítsen közös dokumentációt; 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 alakban 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 programokat Unix környezetben a SICStus Prolog 3.11.0, ill. az MOSML 2.00 rendszerekkel teszteljük.

A programokat és az elektronikus dokumentációt a webes felületen keresztül lehet külön-külön feltölteni az Elektronikus Tanársegéd HF beadás menüpontjában. Programját egy 10-elemű 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 tesztesetek úgy vannak kialakítva, hogy 3-4 feladatot az egyszerű, algoritmikus ötletek nélküli programok is meg tudjanak oldani.

Figyelem! A jó jegyhez gyakorlatilag elengedhetetlen a házi feladat elkészítése.

A programok és az elektronikus dokumentáció végső beadási határideje 2004. május 3. hétfő, 24 óra.

Az értékeléshez a beadási tesztsorozathoz hasonló nehézségű teszteseteket fogunk használni. Az időkorláton belül helyes eredményt adó tesztesetek mindegyikére 0,5-0,5 pontot adunk. A 10 tesztesetre ö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 pluszpontok nélkül elérhető legfeljebb 100 alappontnak).

Azok a programok, amelyek az alapteszt minden feladatát helyesen és az előírt időkorláton belül megoldják, létraversenyen 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 alapponton felül 15 pluszpontot kap.

A programot és dokumentációt mindenkinek saját magának kell elkészítenie. A beadott házi feladatokat gépi úton hasonlítjuk össze, másolás esetén a kódot adó és kapó hallgató sem kaphat pontot. Az algoritmusra vonatkozó ötletek cseréjét viszont nem tiltjuk.


dp@inf.bme.hu

Utolsó módosítás: 2004. 03. 17.

Valid HTML 4.01! Valid CSS!