Prolog feladatgyűjtemény

Aszalós, László

Battyányi, Péter

DEIK
Debreceni Egyetem
Informatikai Kar


            Debrecen
            4032
            Egyetem tér 1
          

Új Széchenyi Terv logó.

2011


Tartalom

Bevezető
1. Feladatok
Családi kapcsolatok
Programtesztelés
Listakezelés
Logikai programok
Rekurzív adatszerkezetek
Prog1 feladatok
Állapottér-reprezentáció problémái
Nyelvtani feladatok
CLPFD
Euler projekt
Formális nyelvek és fordítóprogramok algoritmusai
2. Megoldások
Családi kapcsolatok
Programtesztelés
Listakezezelés
Logikai programok
Rekurzív adatszerkezetek
Prog1 feladatok
Állapottér-reprezentáció problémái
Nyelvtani feladatok
CLPFD
Euler projekt
Formális nyelvek és fordítóprogramok algoritmusai
Bibliográfia

Végszó

A tananyag a Kelet-magyarországi Informatika Tananyag Tárház projekt keretében készült.

Kelet-magyarországi Informatika Tananyag Tárház logója

A tananyagfejlesztés az Európai Unió támogatásával és az Európai Szociális Alap társfinanszírozásával valósult meg.

Magyarország megújul logó.

Nemzeti Fejlesztési Ügynökség http://ujszechenyiterv.gov.hu/ 06 40 638-638

Az Európai Únió logója

Bevezető

A Fortran, Basic, Pascal, C vagy a Java programnyelvekkel elsőként megismerkedők gondolkodásmódja igencsak rááll az imperatív nyelveknél megszokott megoldásokra. Számukra a Prolog nyelv elsajátítása nagyon nagy nehézséget jelent, mivel a ciklusok használata, az ismételt értékadások alkalmazása elengedhetetlen eszköz, erre pedig a Prolog esetén nincs lehetőség. Az utóbbi időkben az egyre méretesebb programrendszerek miatt előtérbe kerültek azon programozási módszerek, ahol a rendszer állapotára nem kell figyelnünk, mint például a funkcionális vagy éppen a logikai programozás esetén.

Az elmúlt években több magyar és angol nyelvű Prolog könyv is hozzáférhetővé vált, mely segít megismerkedni a programozási nyelv alapjaival. Feladatgyűjtemények viszont nem igazán léteznek, és így az első lépések igen nehezek lehetnek, mert a nemcsak a Prolog nyelv, hanem a Prolog programozási környezetek is annyira eltérnek a megszokott rendszerektől, hogy az átlagos felhasználó — aki esetleg profi más fejlesztőkörnyezetek használatában ̶— nem tudja, hogyan töltsön be egy programot, hogyan futtasson le egy programot, vagy hogyan keressen meg a programhibákat.

E rövid feladatgyűjtemény arra vállalkozik, hogy ezzel a nagyszerű nyelvvel ismerkedők az alapoktól indulva, lépésről-lépésre megismerjék az itt használt módszereket, elsajátítsák azokat a technikákat, amelyek más esetben is hasznosak lehetnek. A könyv első részében elemi feladatokkal foglalkozunk különféle témakörök köré csoportosítva, míg a második felében bonyolultabb feladatok kerülnek sorra. 2009-10-ben a Programtervező matematikus és informatikus hallgatóknak ezeket a feladatokat kellett gyakorlatokon megoldaniuk, illetve ezeket kellett beadniuk a félév végén.

Természetesen minden egyes feladathoz megoldást mellékelünk (esetenként többet is), és a megoldást megjegyzésekkel bőven ellátva mutatjuk be.

A szerzők várják az olvasók megjegyzéseit, a hibáink bejelentését, hogy a további kiadásokat ezek szerint javíthassuk.

1. fejezet - Feladatok

Családi kapcsolatok

A Prolog nyelv jellemző bevezetése a családi kapcsolatok leírása. Mi is ezzel fogjuk kezdeni.

Tekintsük az alábbi családfát!

elképzelt családfa

Családfa a megoldandó feladatok teszteléséhez.

A családfa ábrázolásához 4 predikátumot fogunk felhasználni. Ezek közül az első predikátum a ferfi, amely egy tulajdonság, így egy argumentuma van.

Annak érdekében, hogy ne ferfi(andras). prefix alakot kelljen használni, az adatainkat és definícióinkat kiegészítjük egy operátordefinícióval, melynek a leírása a kézikönyvekben megtalálható.

  :- op(500,xf,'ferfi').
  andras ferfi. csaba ferfi. bela ferfi. balazs ferfi. denes ferfi.
  endre ferfi. elemer ferfi. ferenc ferfi. florian ferfi. gusztav ferfi.
  gergely ferfi. hugo ferfi.
  

Ezzel definiáltuk a férfi predikátumot. Minden egyes tényt ponttal zártuk le, és a személyek nevét a Prolog megkötései szerint kisbetűvel írtuk. A no predikátum hasonlóképpen írható:

  :- op(500,xf,'no').
  andrea no. cecilia no. blanka no. beata no. diana no. eniko no.
  emese no. franciska no. gabriella no. hedvig no.
  

A következő dolgunk a gyerek-szülő kapcsolat megadása. Ez a kapcsolat egy kétargumentumú reláció, amely természetesen nem szimmetrikus, és nem is tranzitív. Ennek megfelelően lényeges az argumentumok sorrendje. Mi a gyereket írjuk először, a szülőt pedig másodszor. A szuloje predikátum például így adható meg:

  :- op(500,xfx,'szuloje').
  bela szuloje andras.      bela szuloje andrea.
  balazs szuloje andras.    balazs szuloje andrea.
  diana szuloje csaba.      diana szuloje cecilia.
  endre szuloje bela.       endre szuloje blanka.
  elemer szuloje balazs.    elemer szuloje beata.
  franciska szuloje denes.  franciska szuloje diana.
  gusztav szuloje endre.    gusztav szuloje eniko.
  gergely szuloje elemer.   gergely szuloje emese.
  gabriella szuloje elemer. gabriella szuloje emese.
  hugo szuloje ferenc.      hugo szuloje emese.
  hedvig szuloje ferenc.    hedvig szuloje franciska.
  

Utoljára maradt a házassági kapcsolatok megadása. Ez már szimmetrikus, a feleség házastársa a férjnek és viszont. A megírandó programrészletek egyszerűsítése érdekében ezt a relációt szimmetrikus formában adjuk meg, melynek legegyszerűbb módszere, hogy leírjuk a kapcsolatot oda és vissza is:

  :- op(500,xfx,'hazastarsa').
  andras hazastarsa andrea.     andrea hazastarsa andras.
  csaba hazastarsa cecilia.     cecilia hazastarsa csaba.
  bela hazastarsa blanka.       blanka hazastarsa bela.
  balazs hazastarsa beata.      beata hazastarsa balazs.
  denes hazastarsa diana.       diana hazastarsa denes.
  eniko hazastarsa endre.       endre hazastarsa eniko.
  elemer hazastarsa emese.      emese hazastarsa elemer.
  florian hazastarsa franciska. franciska hazastarsa florian.
  
  

Ezek után következzenek a megoldandó feladatok! A kevésbé ismert rokoni kapcsolatok megismerésére javasoljuk a Wikipédia Rokonság bejegyzését.

  1. Készítse el az apja/2 predikátumot, amelyben az első paraméter jelöli a gyereket, és a második az apát! Megoldás

  2. Készítse el az anyja/2 predikátumot, amelyben az első paraméter jelöli a gyereket, és a második az anyát! Megoldás

  3. Készítse el az apa/1 predikátumot, amely akkor teljesül, ha a szóban forgó személy apja valakinek! Megoldás

  4. Készítse el az anya/1 predikátumot! Megoldás

  5. Készítse el a felesege/2 predikátumot, ahol a második paraméter jelöli a feleséget. Megoldás

  6. Készítse el a ferje/2 predikátumot, ahol a második paraméter jelöli a férjet. Megoldás

  7. Készítse el a nagyszuloje/2 predikátumot, ahol a második paraméter jelöli a nagyszülőt. Megoldás

  8. Készítse el a dedszuloje/2 predikátumot, ahol a második paraméter jelöli a dédszülőt. Megoldás

  9. Készítse el az ukszuloje/2 predikátumot, ahol a második paraméter jelöli a ükszülőt. Megoldás

  10. Készítse el a szepszuloje/2 predikátumot, ahol a második paraméter jelöli a szépszülőt. Megoldás

  11. Készítse el a ose/2 predikátumot, ahol a második paraméter jelöli az őst. Megoldás

  12. Készítse el a testvere/2 predikátumot! Megoldás

  13. Készítse el a feltestvere/2 predikátumot! Megoldás

  14. Készítse el a mostohatestvere/2 predikátumot! Megoldás

  15. Készítse el a mostohaszuloje/2 predikátumot, ahol a második paraméter jelöli a mostohát. Megoldás

  16. Készítse el az unokatestvere/2 predikátumot! Megoldás

  17. Készítse el a masodunokatestvere/2 predikátumot! Megoldás

  18. Készítse el a nagybatyja/2 predikátumot, ahol a második paraméter jelöli a nagybácsit. Megoldás

  19. Készítse el a angya/2 predikátumot, ahol a második paraméter jelöli az ángyot. Megoldás

  20. Készítse el a nagynenje/2 predikátumot, ahol a második paraméter jelöli a nagynénit. Megoldás

  21. Készítse el az unokaoccse/2 predikátumot, ahol a második paraméter jelöli az unokaöccsöt. Megoldás

  22. Készítse el a unokahuga/2 predikátumot, ahol a második paraméter jelöli az unokahúgot. Megoldás

  23. Készítse el a sogora/2 predikátumot, ahol a második paraméter jelöli a sógort. Megoldás

  24. Készítse el a sogornoje/2 predikátumot, ahol a második paraméter jelöli a sógornőt. Megoldás

  25. Készítse el az aposa/2 predikátumot, ahol a második paraméter jelöli az apóst. Megoldás

  26. Készítse el az anyosa/2 predikátumot, ahol a második paraméter jelöli az anyóst. Megoldás

  27. Készítse el a menye/2 predikátumot, ahol a második paraméter jelöli az menyt. Megoldás

  28. Készítse el a veje/2 predikátumot, ahol a második paraméter jelöli a vejét. Megoldás

  29. Készítse el az naszura/2 predikátumot, ahol a második paraméter jelöli a nászuramat. Megoldás

  30. Készítse el a naszasszonya/2 predikátumot, ahol a második paraméter jelöli a nászasszonyt. Megoldás

Programtesztelés

Ebben a részben a Prolog programok futási mechanizmusával ismerkedünk. Az első feladatok megoldásához elegendő ismerni az alapelveket, viszont a későbbi feladatok építenek a vágás illetve a tagadás ismeretére is.

  1. Rajzolja meg az alábbi programhoz tartozó levezetési fát az a(A,B) lekérdezés esetén! Megoldás

    a(a,X):- b(a,X),b(X,a).         %a
    a(X,a):- b(X,X).                %b
    b(X,c(X)).                      %c
    b(a,b).                         %d
    b(A,b).                         %e
  2. Rajzolja meg az alábbi programhoz tartozó levezetési fát az p(A,B,C) lekérdezés esetén! Megoldás

    p(a,X,Y):- r(X,X),r(Y,Y).       %f
    p(X,a,Y):- r(Y,X).              %g
    r(X,X).                         %h
    r(b,a).                         %i
    r(A,b).                         %j
  3. Rajzolja meg az alábbi programhoz tartozó levezetési fát az z(A) lekérdezés esetén! Megoldás

    z(X):- y(Y,X), X\=Y.            %k
    z(X):- y(X,Y), X=\=Y.           %l
    y(1+2,2+1).                     %m
    y(2,1+1).                       %n
    y(1,1).                         %o 
  4. Rajzolja meg az alábbi programhoz tartozó levezetési fát az d(A) lekérdezés esetén! Megoldás

    d(X):-e(X,X).                   %p
    d(X):- f(X,a,X),!,f(X,b,b).     %q
    e(X,a).                         %r
    e(a,X):-!.                      %s
    e(b,b).                         %t
    f(b,a,b):-!.                    %u
    f(a,a,a).                       %v 
  5. Rajzolja meg az alábbi programhoz tartozó levezetési fát az q(A) lekérdezés esetén! Megoldás

    q(X):-s(a,X).                   %w
    q(X):- s(X,a).                  %x
    s(b,a):-!.                      %y
    s(a,X):-!,X=a.                  %z
    s(b,b).                         %A
    s(a,c).                         %B
    s(c,a).                         %C
  6. Rajzolja meg az alábbi programhoz tartozó levezetési fát az x(A,B) lekérdezés esetén! Megoldás

    x(X,Y):-w(X,Y),w(Y,X).          %D
    w(X,Y):-s(X,Y), X<Y,!.          %E
    w(X,Y):-s(X,Y), X=:=Y.          %F
    s(1,0).                         %G
    s(0,1):-!.                      %H 
    s(1,1).                         %I 
  7. Rajzolja meg az alábbi programhoz tartozó levezetési fát az h(A) lekérdezés esetén! Megoldás

    h(X):-i(X,Y), \+ i(Y,X).        %J
    h(X):-i(X,X).                   %K
    i(a,X).                         %L
    i(a,b).                         %M
    i(b,b).                         %N
    i(b,a).                         %O
    i(c,a).                         %P
  8. Rajzolja meg az alábbi programhoz tartozó levezetési fát az t(A) lekérdezés esetén! Megoldás

    t(X):-u(X,X), \+ u(Y,X).        %Q
    u(a,_).                         %R
    u(a,b).                         %S
    u(b,b).                         %T
    u(b,a).                         %U
    u(c,a).                         %V
  9. Rajzolja meg az alábbi programhoz tartozó levezetési fát az v(A,B) lekérdezés esetén! Megoldás

    v(X,Y):-u(X,X), u(Y,X).         %W
    u(a,b):-!.                      %X
    u(b,c).                         %Y
    u(c,a).                         %Z

Listakezelés

Ebben a fejezetben olyan feladatok szerepelnek, melyekben listák segítségével elsajátítható az induktív definíció, illetve az akkumulátorváltozó használata. Törekedjen a hatékony, és egyértelmű megoldások készítésére

  1. Készítsen olyan monoton/1 predikátumot, amely eldönti, hogy az argumentumaként megadott lista monoton növekvő-e vagy sem! Megoldás

  2. Készítsen olyan monoton/1 predikátumot, amely eldönti, hogy az argumentumaként megadott lista monoton csökkenő-e vagy sem! Megoldás

  3. Készítsen olyan paros_hosszu/1 predikátumot, amely eldönti, hogy az argumentumaként megadott lista hossza páros, vagy sem! Megoldás

  4. Készítsen olyan paratlan_hosszu/1 predikátumot, amely eldönti, hogy az argumentumaként megadott lista hossza páratlan, vagy sem! Megoldás

  5. Készítsen olyan metszet(+L1,+L2,-L3) predikátumot, amely a megadott L1 és L2 lista esetén, melyeket listaként értelmezünk, megadja azok metszetét L3-ként! Megoldás

  6. Készítsen egy olyan unio(+L1,+L2,-L3) predikátumot, amely az L1 és L2 halmazként értelmezett lista esetén megadja azok unióját L3-ként! Megoldás

  7. Készítsen olyan kulonbseg(+L1,+L2,-L3) predikátumot, amely az L1 és L2 halmazként értelmezett lista esetén megadja azok különbségét L3-ként! Megoldás

  8. Készítsen olyan szim_diff(+L1,+L2,-L3) predikátumot, amely az L1 és L2 halmazként értelmezett lista esetén megadja azok szimmetrikus differenciáját L3-ként, azaz azon elemek listáját, melyek pontosan csak az egyik listában fordulnak elő! Megoldás

  9. Készítsen olyan RH reszhalmaza H predikátumot, amely a megadott H esetén megadja annak egy RH részhalmazát! Megoldás

  10. Készítsen olyan intervallumban(+H1,+Also,+Felso,-H2) predikátumot, amely a H1 lista azon x elemeiből álló H2 listát adja vissza, ahol x értéke Also és Felso közé esik! Megoldás

  11. Készítsen egy olyan metszet(+L1,+L2,-L3) predikátumot, amely monoton növekvő L1 és L2 listák esetén megadja azok metszetét L3-ként! Használjon akkumulátorváltozót az eredmény tárolására! Megoldás

  12. Készítsen egy olyan unio(+L1,+L2,-L3) predikátumot, amely monoton növekvő L1 és L2 listák esetén megadja azok unióját L3-ként! Használjon akkumulátorváltozót az eredmény tárolására! Megoldás

  13. Készítsen egy olyan kulonbseg(+L1,+L2,-L3) predikátumot, amely monoton növekvő L1 és L2 listák esetén megadja azok különbségét L3-ként! Használjon akkumulátorváltozót az eredmény tárolására! Megoldás

  14. Készítsen egy olyan szim_diff(+L1,+L2,-L3) predikátumot, amely monoton növekvő L1 és L2 listák esetén megadja azok szimmetrikus differenciáját L3-ként! Használjon akkumulátorváltozót az eredmény tárolására! Megoldás

  15. Készítsen egy fordit(+L1,-L2) predikátumot, mellyel a paraméterül megadott L1 listák listájában minden egyes részlistát megfordít rekurzív módon, s az eredményt az L2 változóban adja vissza. Például fordit([a,[b,c],[d,[e,f,g]]],L). esetén a válasz a következő: L = [[[g,f,e],d],[c,b],a]. Megoldás

  16. Készítsen egy laposit(+L1,-L2) predikátumot, mellyel a paraméterül megadott L1 listák listáját egy egyszerű listává alakítja. Például laposit([a,[b,c],[d,[e,f,g]]],L). esetén a válasz a következő: L = [a,b,c,d,e,f,g]. Megoldás

  17. Készítsen egy olyan felez(+L,-L1,-L2) predikátumot, mellyel a megadott L listát két közel azonos listára bontja, melyeket az L1 és L2 változókban ad vissza. Megoldás

  18. Készítsen egy darabol(+L1,-L2) predikátumot, mellyel a paraméterül megadott L1 számlistát maximális méretű, monoton listákra bontja, és ezek listáját adja vissza. Például darabol([1,2,4,3,7,6,5],L). esetén a válasz a következő: L = [[1,2,4],[3,7],[6],[5]]. Megoldás

  19. Készítsen egy darabol(+L1,-L2) predikátumot, mellyel a paraméterül megadott L1 szöveget (karakterek listáját) szavakra bontja, és ezek listáját adja vissza. Megoldás

  20. Készítsen egy darabol(+L1,-L2) predikátumot, mellyel a paraméterül megadott L1 listát azonos elemek listáira bontja, és ezek listáját adja vissza. Például darabol([a,a,a,b,c,c],L). esetén a válasz a következő: L = [[a,a,a],[b],[c,c]]. Megoldás

  21. Készítsen egy forgat(+XL,-LX) predikátumot, mellyel a paraméterül megadott XL listát ciklikusan balra forgatja, s az így kapott listát adja vissza. Megoldás

  22. Készítsen egy forgat(+LX,-XL) predikátumot, mellyel a paraméterül megadott LX listát ciklikusan jobbra forgatja, s az így kapott listát adja vissza. Megoldás

  23. Készítsen egy csere(+L,+M,+C,+LL) preditátumot, mely az M minta minden előfordulását C-re cseréli L-ben, s ennek a cserének az eredményét adja vissza. Megoldás

Logikai programok

Ebben a részben a matematikai logikai alapvető algoritmusainak készítjük el Prolog nyelven írt programjait. Egy-két kivételtől nincs szükség más előismeretre, mint a rekurzív adatszerkezetek kezelésére, valamint alapvető logikai ismeretekre.

A következőkben logikai formulákkal kapcsolatos feladatokkal, programokkal foglalkozunk. A könnyebb kezelhetőség miatt operátorokat definiálunk. Az olvashatóság érdekében alkalmazzuk a következő definíciókat:

:- op(300,fx,'~').
:- op(400,yfx,'/\\').  
:- op(400,yfx,'\\/').
:- op(500,xfx,'=>').
:- op(600,yfx,'<=>').
[Megjegyzés]Megjegyzés

A Debreceni Egyetemen a konjunkció és diszjunkció azonos precedenciájúak, ami nem általános. Itt balasszociatív tulajdonságot adtunk meg ezeknek a logikai összekötőjeleknek, így nem okoz gondot az A∧B∨C alak sem, mert a Prolog ezt automatikusan az (A∧B)∨C alakra alakítja.

  1. Készítsen egy olyan nnf(+F,-NNF) predikátumot, amely a paraméteréül megadott formulának elkészíti negatív normálformáját! Megoldás

  2. készítsen egy olyan knf(F) predikátumot, amely teszteli, hogy a formula konjunktív normálformában van-e! Megoldás

  3. Készítsen egy olyan knf(+NNF,-KNF) predikátumot, amely a negatív normálformában megadott formulának elkészíti egy konjunktív normálformáját! Megoldás

  4. Készítsen egy olyan minimalizal(+KNF,-M) predikátumot, amely a számára megadott konjunktív normálformának elkészíti a legrövidebb változatát! Megoldás

  5. Készítsen egy olyan knf2dnf(+KNF,-DNF) predikátumot, amely a konjunktív normálformában megadott formulának elkészíti diszjunktív normálformáját! Megoldás

  6. Készítsen egy olyan valtozotiszta(+F,-V) predikátumot, amely a számára megadott elsőrendű formulának elkészíti egy változótiszta alakját Megoldás

  7. Készítsen egy olyan prenexalak(+F,-P) predikátumot, amely a számára megadott változótiszta formulának elkészíti egy prenex alakját! Megoldás

Rekurzív adatszerkezetek

Ebben a fejezetben a rekurziót tanuljuk meg alkalmazni rekurzív adatszerkezetekkel kapcsolatos feladatok megoldása során.

Egy bináris fa a következők egyike lehet:

  • üres

  • X elemet tartalmazó levél

  • X gyökerű, Fa1 és Fa2 részfákat tartalmazó fa.

Az előbbi eseteket rendre jelölje u, l(X)valamint n(X,Fa1,Fa2). Ezt a jelölést követve oldja meg az alábbi feladatokat!

  1. Készítsen egy keresofa(+Fa) predikátumot, mellyel ellenőrizheti, hogy a paraméterül megadott bináris fa keresőfa, vagy sem. A megoldás során használjon két segédváltozót, melyek az adott részfa alsó és felső korlátját jelölik. Megoldás

  2. Készítsen egy kh_fa(+Fa) predikátumot, mellyel ellenőrizheti, hogy a paraméterül megadott bináris fa 2-3-fa vagy sem. Megoldás

  3. Készítsen egy maximalis(+Fa,-Max) predikátumot, amellyel meghatározza a megadott Fa 2-3-fa maximális elemét, s ezt Max változóban adja vissza! Megoldás

  4. Készítsen egy kupac(+Fa) predikátumot, mellyel ellenőrizheti, hogy a paraméterül megadott bináris fa kupac-e vagy sem. Megoldás

  5. Készítsen egy kupacol(+Fa,-Kupac) predikátumot, amellyel a megadott Fa bináris fát kupaccá alakítja, s az eredményt a Kupac változóban adja vissza! Megoldás

  6. Készítsen egy maximalis(+Fa,-Max) predikátumot, amellyel meghatározza a megadott Fa kupac maximális elemét, s ezt Max változóban adja vissza! Megoldás

  7. Készítsen egy avl_fa(+Fa) predikátumot, mellyel ellenőrizheti, hogy a paraméterül megadott bináris fa AVL fa-e vagy sem. Megoldás

  8. Készítsen egy avl_forgat(+Fa,-UjFa) predikátumot, mellyel helyreállítja az AVL tulajdonságot a paraméterül megadott Fa bináris fában, s az eredményt az UjFa változóban adja vissza. Megoldás

  9. Készítsen egy sulyra_keszit(+List,-Fa) predikátumot, mellyel a paraméterül megadott List listában szereplő kulcsokat a mellettük szereplő gyakoriságok alapján súlyra kiegyensúlyozott fát készít, s az eredményt az Fa változóban adja vissza. Megoldás

  10. Készítsen egy dontesi_diagram(+Lista,-Fa) predikátumot, mellyel a paraméterül megadott Lista változóban szereplő igazságtábla alapján elkészíti a döntési fát, s az eredményt a Fa változóban adja vissza. Lehetséges alkalmazása lehet a következő: dontesi_diagram([[0,0,0,1],[0,0,1,1],[0,1,0,0],[0,1,1,1],[1,0,0],[1,1,1]],T) Megoldás

  11. Készítsen egy radix_fa(+Lista,-Fa) predikátumot, mellyel a paraméterül megadott Lista listák listáját radix-fába rendezi, s az eredményt a Fa változóban adja vissza. Megoldás

Prog1 feladatok

A Debreceni Egyetem Magasszintű programozási nyelvek gyakorlatának házi feladatait Prolog nyelven is megoldhatjuk. Természetesen a feladatok kitűzésén egy kicsit változtatni kell, a nyelv korlátainak megfelelően.

  1. Készítsen egy olyan sorozat(+Hossz,-Db) predikátumot, amely agy adott, pozitív egész Hossz-ra megadja az olyan 0 és 1 betűkből álló sorozatok számát, melyben nem szerepel egymás mellett két 1-es! Megoldás

  2. Készítsen egy olyan hullam(+Amplitudo,+Db,L) predikátumot, amely a megadott amplitúdónak és darabszámnak megfelelő hullámot generál! A hullam(3,2,L) eredménye L=[1,22,333,22,1,0,1,22,333,22,1] lesz. Az amplitudó nem lehet 9-nél nagyobb. Megoldás

  3. A nem egyértelmű permutáció olyan permutáció, amely nem különböztethető meg az inverzétől. Az inverz permutáció i-dik számjegye az eredeti permutációban az i számjegy helyét adja meg. Például az 5,1,2,3,4 inverz permutációja 2,3,4,5,1. Készítsen egy olyan nem_egyertelmu(+L) predikátumot, amely eldönti, hogy az 1,...,n elemek L permutációja nem egyértelmű permutáció-e, vagy sem! Megoldás

  4. Készítsen egy olyan zaro_nulla(+N,-Db) predikátumot, amely megadja, hogy az N szám faktoriálisa hány nulla számjegyre végződik! Megoldás

  5. Készítsen egy olyan goldbach(+N,-P1,-P2) predikátumot, amely a megadott N páros számot két prímszám (P1 és P2) összegeként előállítja! Megoldás

  6. Készítsen olyan max_oszto(+N,-Oszto) predikátumot, amely meghatározza a megadott pozitív szám legnagyobb valódi osztóját! Megoldás

  7. Készítsen olyan index_szum(+L,-S) predikátumot, amely megadja az L számlista azon elemei indexének az összegét, melyek nagyobbak az összes elem átlagánál! Megoldás

  8. Készítsen egy anagramma(+L,-LL) predikátumot, amely a bemenetként megadott lista szavait listák listájává szervezi úgy, hogy egy listában egymás anagrammái szerepelnek! Megoldás

  9. Készítsen egy leghosszabb(+L,-I) predikátumot, amely az L listában x:y formában megadott intervallumok esetén leghosszabb összefüggő intervallumot adja vissza! Például leghosszabb([3:8,8:17,0,2],I) esetén I=3:17. Megoldás

  10. Készítsen egy metszete(+L,-I) predikátumot, amely az L listában x:y formában megadott intervallumok esetén a metszetüket mint intervallumot adja vissza! Megoldás

  11. Készítsen egy szolista(+L,-R) predikátumot, amely az L listában megadott szavakat azok hossza alapján növekvő sorrendbe rendezi. Megoldás

  12. Készítsen egy elso_elofordulas(+Szoveg,+Minta,-Pozicio) predikátumot, amely megadja azt a pozíciót, melynél a Minta először fordul elő a Szoveg sztringben. Megoldás

  13. Készítsen egy relativ_prim(+X,+Y) predikátumot, amely eldönti, hogy a számára megadott két pozitív egész szám relatív prím, vagy sem. Megoldás

  14. Készítsen egy negyzet_tavol(+N,-D) predikátumot, amely megadja, hogy az N számtól milyen távolságra van a legközelebbi négyzetszám! Megoldás

  15. Készítsen egy prim_tavol(+N,-D) predikátumot, amely megadja, hogy az N pozitív egész számtól milyen távolságra van a legközelebbi négyzetszám! Megoldás

Állapottér-reprezentáció problémái

A Debreceni Egyetem Mesterséges Intelligencia 1 gyakorlatának beadandó feladatait vagy azokhoz hasonló feladatokat Prolog-ban is meg lehet oldani.

  1. Adott az 1,...,8 elemek egy permutációja egy listában. Egy lépésben a lista egy tetszőleges kezdőszeletét lehet megfordítani. Készítsen olyan forgat(+L,-R) predikátumot, mellyel a kezdeti L listát növekvő sorrendbe rendezheti! Megoldás

  2. Készítsen egy olyan huszar(+Kezd,+Veg,Ut) predikátumot, amely megadja azon lépések sorozatát, mellyel a sakktáblán a huszár eljuthat a Kezd mezőről a Veg mezőre. Megoldás

  3. Van A almánk, B körténk és C barackunk. Egy-egy különböző gyümölcsért cserébe két darabot kapunk a harmadik fajtából. Készítsen egy cserel(+A,+B,+C,-L) predikátumot, amely megad egy olyan csere-sorozatot, melyet végrehajtva csak egy fajta gyümölcsünk marad. Megoldás

  4. Egy számmal a következő műveleteket hajthatjuk végre:

    1. négyest írhatunk a végére,

    2. nullást írhatunk a végére és

    3. ha páros, eloszthatjuk kettővel.

    Készítsen egy szamol(+Szam,-L) predikátumot, mely megadja, hogy 4-ből kiindulva milyen lépések sorozatával (L) kapható meg a Szam! Megoldás

  5. A (3,4,5) számhármasból kiindulva minden pitagoraszi számhármas megkapható a következő lépések valamilyen sorozatával :

    1. (x,y,z) → (x-2y+2z, 2x-y+2z, 2x-2y+3z),

    2. (x,y,z) → (x+2y+2z, 2x+y+2z, 2x+2y+3z) és

    3. (x,y,z) → (-x+2y+2z,-2x+y+2z,-2x+2y+3z)

    Készítsen egy szamol(+A,+B,+C,-L) predikátumot, mely megadja, hogy a (3,4,5) számhármasból kiindulva milyen lépések sorozatával (L) kapható meg az (A,B,C) pitagoraszi számhármas! Megoldás

  6. Egy (m,n) számpárral a következő műveleteket hajthatjuk végre:

    1. (m,n) → (n,m),

    2. (m,n) → (m-n,n) és

    3. (m,n) → (m+n,n)

    Készítsen egy szamol(+M,+N,+X,+Y,-L) predikátumot, mely megadja, hogy az (M,N) számpárból kiindulva milyen lépések sorozatával (L) kapható meg az (X,Y) számpár! Megoldás

  7. A türelemjáték már az ókorban közismert játék volt. Készítsen egy pegged(+B,-L) predikátumot, mely megadja, hogy a 15-ös tábla adott elrendezése esetén (B) milyen lépések sorozatával (L) kapható meg az állás, melyben csak egy figura marad a táblán! Megoldás

  8. Készítsen egy buvos(+L,-T) predikátumot, amely az 9 hosszúságú L lista elemeit úgy rendezi egy 3x3-as táblázatba, hogy az bűvös négyzetet alkosson! Megoldás

Nyelvtani feladatok

A soron következő pár feladatban verstani ismeretekre is szükség van. A sztringként megadott verssorok olvashatóbbá tételéhez javaslom az itt elérhető predikátumok használatát. Miután nyelvészeti feladatokról van szó, és a Prolog tartalmazza a DCG nyelvek elemzéséhez szükséges eszközöket, ezeket előszeretettel alkalmazzuk a megoldásainkban. Javasoljuk az olvasóinknak, hogy a feladatok megoldása előtt mélyedjenek el ebben a témakörben, meri igen leegyszerűsíti a megoldásokat!

  1. Készítsen egy olyan hexameter(L) predikátumot, amely eldönti, hogy az argumentumaként kapott vers (verssorok listája) hexameterben íródott-e, vagy sem! Megoldás

  2. Készítsen egy olyan pentameter(L) predikátumot, amely eldönti, hogy az argumentumaként kapott verssor pentameterben íródott-e, vagy sem! Megoldás

  3. Készítsen egy olyan anakrenoi7(L) predikátumot, amely eldönti, hogy az argumentumaként kapott vers anakrenoi hetes, vagy sem! Megoldás

  4. Készítsen egy olyan szapphoi(L) predikátumot, amely eldönti, hogy az argumentumaként kapott versszak szapphoi-e, vagy sem! Megoldás

  5. Készítsen egy olyan rimkeplet(L,R) predikátumot, amely a megadott versnek elkészíti a rímképletét, azaz megadja, hogy mely sorok rímei esnek egybe! Megoldás

  6. Készítsen egy olyan haiku(L) predikátumot, amely eldönti, hogy a megadott vers haiku, vagy sem! Megoldás

  7. Készítsen egy olyan anbncn(X) predikátumot, amely az anbncn szavakat generálja illetve felismeri! Megoldás

  8. Készítsen egy olyan anbmck(X) predikátumot, amely az anbmck szavakat generálja illetve felismeri, ahol n≥m≥k! Megoldás

  9. Készítsen egy olyan a2nbn(X) predikátumot, amely azokat az a és b betűből álló szavakat generálja illetve felismeri, ahol az a betűk száma kétszerese a b betűk számának! Megoldás

  10. Készítsen egy olyan nemdupla(X) predikátumot, amely azokat az a, b és c betűkből álló szavakat generálja illetve felismeri, ahol a szóban nem áll egymás mellett két azonos betű! Megoldás

  11. Készítsen egy olyan nemdupla(X) predikátumot, amely azokat az a és b betűkből álló szavakat generálja illetve felismeri, ahol a szó prefixeiben nincs több b mint a! Megoldás

CLPFD

Kényszerkielégítési problémákkal igen gyakran találkozhatunk, mi most a szórakoztató rejtvényekből válogattunk [Bizam]. Több módszer alkalmazható ezek megoldására kezdve a legegyszerűbb back-track algoritmustól. A clpfd programkönyvtár segítségével olyan módon határozhatjuk meg a megoldásokat, hogy csak a korlátozó feltételeket kell megadnunk, a többit már megoldja a Prolog saját maga.

  1. Három embert mutatunk be, Aladárt, Bélát és Balázst. Egyikük asztalos, a másik bádogos, a harmadik agronómus. Egyikük Budapesten, a másik Békéscsabán, a harmadik Aszódon lakik, Hogy ki hol lakik, és mi a foglalkozása, azt találja ki Ön.

    Annyit azonban elmondhatunk, hogy

    1. Balázs csak ritkán látogat a fővárosba, pedig minden rokona Budapesten lakik;

    2. két olyan ember is van a társaságban, akinek foglalkozása és lakhelye ugyanazzal a betűvel kezdődik, mint a neve;

    3. az asztalos felesége, Balázs húga.

    Megoldás

  2. Egy társaságban öt katonatisztet látunk: egy gyalogos, tüzér, repülő, híradós, és hadmérnök van közöttük. Rendfokozatuk szerint egy százados, három őrnagy és egy alezredes. Beszélgetésükből a következőket tudhatjuk meg.

    1. János rendfokozata megegyezik hadmérnök barátjáéval.

    2. A híradós tiszt jó barátságban van Ferenccel.

    3. A repülőtiszt nemrégen Bélával és Lajossal együtt Ferencnél járt vendégségben.

    4. A múltkor a tüzérnek és a hadmérnöknek majdnem egyszerre romlott el a rádiója. Kérésükre mind a két esetben Lajos hívta segítségül a híradóst, és nem is hiába, azóta mindkettőjük rádiója jól működik.

    5. Ferenc eredetileg repülő akart volna lenni, de hadmérnök barátjának tanácsára végül mégis más fegyvernemet választott.

    6. János Lajosnak, Béla pedig Ferencnek előre tiszteleg.

    7. András az ötödik tiszt, tegnap látogatóban volt Lajoséknál.

    Állapítsuk meg mindegyikükről, mi a rendfokozata, és melyik fegyvernemnél szolgál! Megoldás

  3. A csúcsforgalomnak az idegrendszerre gyakorolt hatását tanulmányozza egy pszichológus. Az 55-ös, a 15-ös, a 25-ös, és a 33-as villamoson beszélgetett egy-egy utassal. A megkérdezettek neve Aladár, Péter, Vilmos és Lajos volt, foglalkozásuk pedig lakatos, villanyszerelő, kárpitos és marós.

    Sajnos a csúcsforgalom legjobban a pszichológus idegeit viselte meg. Így aztán nem csoda, ha a végén elfelejtette, melyik embernek mi a foglalkozása. Pedig igencsak fontos volna a vizsgálataihoz!

    Az alábbiakra emlékszik:

    1. Vilmos villamosának a száma nem 1-gyel kezdődött.

    2. A 33-ason vasmunkással beszélt.

    3. A marós olyan viszonylaton utazott, amelynek számában a jegyek összege ugyanannyi, ahány betűből a keresztneve áll.

    4. Lajossal olyan villamoson beszélt, amelynek száma két azonos jegyből állt.

    5. A villanyszerelő keresztneve más betűvel kezdődik, mint a foglalkozása.

    6. Péter az iránt érdeklődött, a pszichológustól, hogy át tud-e szállni közvetlenül a 25-ösre.

    7. Ekkor a pszichológusban hirtelen felvillant Lajos utolsó mondata: Rossz villamosra szálltam, átszállok az 55-ösre!

    Állapítsuk meg, hogy melyik villamoson milyen nevű és foglalkozású emberrel beszélt a pszichológus! Megoldás

  4. Az asztalnál négyen ülnek: Sándor, Gábor, László és Zoltán. A pincér épp hozza, amit rendeltek: egy pohár bort, egy korsó sört, egy meggylét, és egy kólát, valamint egy szendvicset, egy tányéron négy krémest, egy másik tányéron három pogácsát és egy adag fagylaltot.

    − Ki mit rendelt, uraim? − kérdezi, mert a rendelést a társa vette fel.

    − Mindenki ételt és italt, és mindenki másfélét − mondják kórusban a társaság tagjai.

    − Ettől persze nem lettem okosabb − gondolja a pincér, de azért lendületesen elkezdi szétosztani a tálcája tartalmát.

    1. − Figyelmeztetem, én antialkoholista vagyok − mondja Zoltán.

    2. − Akkor bizonyára nem az öné a pogácsa sem, hiszen az bor vagy sör mellé való.

      − Pedig én nem ahhoz rendeltem − mondja a társaság Zoltán mellett ülő tagja, és elveszi a pogácsát.

    3. − Nem meleg a söröd? − kérdi az egyik vendég a másikat. − Csak a fagylaltod legyen ilyen hideg − nyugtatja az meg.

    4. − Én édes dolgokat nem eszem − jelenti ki László.

    5. − Sajnos pogácsát elfelejtettem rendelni, de a meggylé az enyém. − mondja az egyik vendég.

    6. − Nem rendeltem süteményt − tiltakozik Sándor, és közben az elétett borospoharat is eltolja.

    Meglehetősen nagy a zűrzavar, de a pincér türelmesen és szolgálatkészen próbál a vendégek kedvére tenni − bár némileg tanácstalan.

    Tudunk segíteni neki? Megoldás

  5. Hamis Kati és Hati Miska jegyesek. Péntek délután találkoznak.

    1. Miska szemrehányóan panaszkodik Katinak, hogy hiába telefonált neki hétfőn, kedden, szerdán és csütörtökön délután, egyszer sem találta otthon.

    2. A barátnőimmel is kell egy kis időt töltenem − védekezik Kati. Csak négyen vannak: Olga, Piri, Rozi és Sári. Megérdemelnek néha egy délutánt.

      − De itt nem egy délutánról van szó, hanem négyről.

    3. − Na, persze hogy négyről. Négyen vannak, mindegyikkel másik napon találkoztam.

      − Miért nem jöttök össze egyszerre?

    4. − Mert ők nem szeretik olyan nagyon egymást, így mindegyikkel más-más programunk volt. Az egyikkel fodrásznál voltunk, a másikkal szabónőnél, a harmadikkal véletlenül futottam össze a könyvtárban, a negyedikkel pedig már régesrég megbeszéltük, hogy elmegyünk evezni a Római-partra. Különben mi közöd hozzá? Még mibe nem szólsz bele?

    Miska erre sértődötten elhallgatott, de azért valahogy gyanús maradt neki a dolog. Hiába bizonygatta Kati, hogy csak ezen a négy helyen járt a hét eddigi délutánjain, azért megpróbált utánagondolni a dolognak.

    1. A hét első három napján a strandról telefonált Katinak; Sári valamennyi alkalommal ott volt egész délután a strandon.

    2. Piri és Rozi nincsenek olyan rosszban. Épp ma panaszkodtak egymásnak, hogy borzalmas a hajuk: legalább egy hete nem jutottak el a fodrászhoz.

    3. Olgával kedden délután az igazat megvallva, Miska volt moziban. Ott mesélte a leány, hogy eredetileg Katival közös szabónőjéhez kellett volna mennie, de annak váratlanul el kellett utaznia ezen a napon.

    4. Kati fodrásznője a hét második felében (csütörtök, péntek, szombat) délelőttös. Mivel Kati délelőtt dolgozik, ilyenkor nem mehet hozzá.

    5. Sem Piri, sem Rozi nem jár soha könyvtárba.

    6. A római-parti csónakházban kedden mindig szünnap van.

    Hogyan is áll a helyzet Hamis Kati programjával? Megoldás

  6. A Torta és Retortagyár központjában szombat délelőtt a jövő heti kiszállásokat tervezik meg. Az alsókisvári, a felsőnagyvári és a csalavári telepet kell ellenőrizni, mindegyiket egy-egy nap alatt: hétfőn, kedden, illetve szerdán. A választás Szeél-Hámos Jeremos, Kiss-Király Kázmér és Látszathy Lola kartársakra esett: mindegyikük egy-egy telepet látogat meg. Egy nap persze csak egyikük lehet távol, mert égető szükség van rájuk a központban is.

    De melyikük mikor és hova is menjen? Ez az, amit nehezen tudnak eldönteni. Sok szempontot kell ugyanis figyelembe venniük:

    1. Szeél-hámos kartárs nem kíván Alsókisvárra menni, mert ott ebédre legutóbb is az üzemi konyha főztjével kínálták.

    2. Kiss-Király kartárs hajlandó ugyan akár Alsókisvárra, akár Felsőnagyvárra menni, de kedden egyik helyre sem, mert úgy tapasztalta, hogy az ottani vendéglőkben keddi napokon gyenge a választék.

    3. Felsőnagyvárra Kiss-Király kartárs hétfőn sem megy, mert az a portás, aki ott hétfői napokon teljesít szolgálatot, a múlkor nem volt elég tisztelettudó vele szemben.

    4. Alsókisvárra viszont senki sem szeret hétfőn menni, mert ott minden vendéglőipari üzemegység hétfőn tart szünnapot.

    5. Szeél-Hámos kartársat nem jó Felsőnagyvárra küldeni, mert ott nincs meg a kellő tekintélye: amikor a legutóbb ott járt, összetévesztette a tortát a retortával.

    6. Csalavárra nyugodtan elmehet Szeél-Hámos kartárs, de azt kéri, hogy lehetőleg ne hétfőn, mert − már nem emlékszik, hogy miért, csak oda hétfőn nem szívesen megy.

    Tudnak-e olyan megoldást találni a kiküldetésekre, amely minden feltételnek és kívánságnak megfelel? Megoldás

  7. Hat fiú beszélget egymással: András, Béla, Csaba, Dezső, Elemér, és Ferenc. Beszélgetésükből megtudjuk, hogy mindannyian sportolók: kettő futballozik, kettő magasugró, egy úszik és egy vízilabdázik. Ketten a Vasasban, ketten az Újpesti Dózsában, ketten pedig az FTC-ben sportolnak.

    Továbbá kiderül még:

    1. Béla edzései a Sportuszodában vannak. Mostani egyesületébe az FTC-ből ment át.

    2. Az újpestiek nem labdajátékosok.

    3. Egyikük keresztneve is, egyesülete is, sportága is ugyanazzal a betűvel kezdődik. S mivel tetszik neki ez az alliteráció, mindig is ugyanezt a sportágat művelte, ugyanebben a klubban.

    4. Csaba nem tud úszni.

    5. A két Vasas-beli különböző szárazföldi sportot űz.

    6. Elemér korábban gyakran játszott Feri ellen, de aztán abbahagyta azt a sportágat.

    7. Aladár és Elemér klubtársak.

    Határozzuk meg, hogy melyik fiú, milyen sportágat űz, és melyik egyesületben! Megoldás

  8. A Balatonnál öt fiatal verődik össze: egy magyar, egy lengyel, egy finn, egy svéd és egy német.

    1. Érdekességként azonnal megállapítják, hoy mindegyikük tud egy vagy több idegen nyelvet, de csakis olyat, amely valamelyiküknek anyanyelve.

    2. Ennek ellenére nehezen indul a társalgás, mert kiderül: nincs olyan nyelv, amelyen mind az öten tudnának.

    3. Igaz viszont, hogy bármelyikük bármelyikükkel tud valamilyen nyelven beszélni, és

    4. ezek között a közös nyelvek között mind az ötüknek az anyanyelve szerepel.

    5. A nyelvismereteket összeszámolva megállapították, hogy öten átlag két-két idegen nyelvet tudnak.

    6. A magyar is, a lengyel is három-három idegen nyelvet tud.

    7. Amikor a svéd elment fürödni, a többi négy már rögtön megtalálta a közös nyelvet − egy olyat, amelyen mind a négyen értettek.

    8. Hasonló helyzet állt elő akkor is, amikor a svéd visszajött, de a finn csónakázni ment.

    9. Ahhoz viszont, hogy svédül társaloghassanak egymással, kettőnek kellett volna eltávozni közülük.

    10. Finnül és lengyelül is összesen ketten tudnak.

    11. A lengyel és a finn két nyelven tud beszélni egymással, a német azonban nem tartozik ezek közé.

    12. A magyar és a svéd csak egy közös nyelvet ismer.

    Állapítsuk meg, melyikük milyen idegen nyelven beszél! Megoldás

Euler projekt

Az Euler project egy igen jó gyűjteménye matematikai/programozási feladatoknak. Innen válogattunk pár példát. Kifejezett figyelmet fordítunk a minél hatékonyabb feladatmegoldásra, és próbálunk általános megoldást adni a konkrét feladatokra.

  1. Határozza meg az 1000-nél kisebb azon számok összegét, amelyek 3 vagy 5 többszörösei! Megoldás

  2. Határozza meg a négymilliónál kisebb páros Fibonacci számok összegét! Megoldás

  3. Határozza meg egy összetett szám legnagyobb prímosztóját! Megoldás

  4. Határozza meg az a legnagyobb palindrom számot, amely előáll két háromjegyű szám szorzataként! Megoldás

  5. Határozza meg azt a legkisebb pozitív egész számot, melynek 1, ..., 20 mind osztói! Megoldás

  6. Határozza meg, mekkora az első száz szám összege négyzetének illetve az első száz szám négyzete összegének a különbsége! Megoldás

  7. Határozza meg a 10001-dik prímszámot! Megoldás

  8. Határozza meg azt az öt számjegyet egy ezerjegyű számban, melyeknek maximális a szorzata! Megoldás

  9. Határozza meg annak az egyetlen pitagoraszi számhármas tagjainak a szorzatát, ahol a tagok összege 1000! Megoldás

  10. Határozza meg a kétmillió alatti prímszámok összegét! Megoldás

  11. Határozza meg egy 20x20-as táblázat azonos sora, oszlopa vagy átlója négy egymás mellett álló eleme szorzatának a maximumát! Megoldás

  12. Határozza meg azt a legkisebb háromszögszámot, melynek több mint ötszáz osztója van. Megoldás

  13. Határozza meg száz darab ötven jegyű szám összegének első tíz számjegyét! Megoldás

Formális nyelvek és fordítóprogramok algoritmusai

  1. Írja meg Prologban az általános felülről lefele elemzőt! Megoldás

  2. Írja meg Prologban az általános alulról felfele elemzőt! Megoldás

  3. Írjon olyan predikátumot, mely adott nyelvtan esetén meghatározza a First halmazokat! Megoldás

  4. Írjon olyan predikátumot, mely adott nyelvtan esetén meghatározza a Follow halmazokat! Megoldás

  5. Írjon olyan predikátumot, mely adott nyelvtan esetén elkészíti annak balrekurziótól mentes variánsát! Megoldás

  6. Írjon olyan predikátumot, mely adott nyelvtan esetén elkészíti annak lambda-mentes variánsát! Megoldás

  7. Készítsen egy olyan programot, mely a megadott környezetfüggetlen grammatikából a felesleges szimbólumokat elhagyja. Megoldás

  8. Készítsen egy olyan programot, amely reguláris kifejezésnek megfelelő nemdereminisztikus automatát generál. Megoldás

  9. Készítsen programot, amely egy nemdeterminisztikus automatát determinizál! Megoldás

  10. Készítsen egy olyan programot, mely egy determinisztikus automatát minimalizál! Megoldás

  11. Készítsen egy olyan programot, amely adott szám esetén felsorolja az összes ennél nem hosszabb szót, mely az adott szóhosszt nem csökkentő grammatikában generálható! Megoldás

  12. Készítsen egy olyan programot, mely eldönti, hogy a megadott környezetfüggetlen grammatika tartalmaz-e ciklust, vagy sem. Megoldás

  13. Készítsen egy olyan programot, amely szimulálja a Turing-gépet! Megoldás

  14. Készítsen egy olyan programot, amely a Markov algoritmust szimulálja! Megoldás

2. fejezet - Megoldások

Családi kapcsolatok

  1.         :- op(500,xfx,'apja').
            Gyerek apja Apa:-
              Gyerek szuloje Apa,
              Apa ferfi.
          

    Elegendő azt megadni, hogy az apa férfi szülő.

  2.         :- op(500,xfx,'anyja').
            Gy anyja Anya:-
              Gy szuloje Anya,
              Anya no.
          
  3.       :- op(500,xf,'apa').
          Apa apa:-
            _Gy apja Apa.
          

    Felhasználhatjuk a korábbi feladatok megoldását, vagy az ott szereplő Gyerek változót anomimizálhatjuk.

  4.         :- op(500,xf,'anya').
              Anya anya:-
              _Gy anyja Anya.
          
  5.       :-op(500,xfx,'felesege').
          A felesege Feleseg:-
            A hazastarsa Feleseg,
            Feleseg no.
          
  6.     :-op(500,xfx,'ferje').
        A ferje Ferj:-
          A hazastarsa Ferj,
          Ferj ferfi.
        
  7.       :- op(500,xfx,'nagyszuloje').
          Gy nagyszuloje Nagyszulo:-
            Gy szuloje A,
            A szuloje Nagyszulo.
          
  8.       :- op(500,xfx,'dedszuloje').
          U dedszuloje Dedszulo:-
            U nagyszuloje N,
            N szuloje Dedszulo.
          
  9.         :- op(500,xfx,'ukszuloje').
            Gy ukszuloje Ukszulo:-
              Gy nagyszuloje N,
              N nagyszuloje Ukszulo.
            
  10.         :- op(500,xfx,'szepszuloje').
            Gy szepszuloje Szepszulo:-
              Gy ukszuloje U,
              U szuloje Szepszulo.
            
  11.         :- op(500,xfx,'ose').
            Leszarmazott ose Os:-
              Leszarmazott szuloje Os.
            Leszarmazott ose Os:-
              Leszarmazott szuloje Szulo,
              Szulo ose Os.
            

    Itt két lehetőség van. Ősei lesznek egy adott személynek a szülei, másrészt a szüleinek az ősei. Ezt a két definíciót fordított sorrendbe írva a program könnyen végtelen ciklusba kerülhet! Minden esetben próbáljuk az egyszerűbb esetet megadni elsőként!

  12.         :- op(500,xfx,'testvere').
            A testvere B:-
              A apja Apa,
              B apja Apa,
              A\=B,
              A anyja Anya,
              B anyja Anya,
              Apa\=Anya.
          

    Ennél a definíciónál vigyázni kell, mert a két közös szülő nem elegendő, a két testvér nem lehet ugyanaz a személy. Hasonlóképpen a két szülőnek is különbözni kell. Ez vagy a nemek figyelembe vételével, vagy az egyenlőség/unifikálhatóság tagadásával érhető el.

  13.         :- op(500,xfx,'feltestvere').
            A feltestvere B:-
              A szuloje Szulo,
              B szuloje Szulo,
              A\=B
              \+ A testvere B.
          

    Az egy közös szülő nem elegendő feltétel, azt is fel kell tenni, hogy nincs több ilyen szülő, amit legegyszerűbben úgy adhatunk meg, hogy a két személy nem testvér. Viszont ez lehetővé teszi, hogy a két személy egy legyen, amit továbbra sem engedünk.

  14.         :- op(500,xfx,'mostohatestvere').
            A mostohatestvere B:-
              A szuloje SzuloA,
              B szuloje SzuloB,
              SzuloA hazastarsa SzuloB,
              \+ A feltestvere B,
              \+ A testvere B
              A\=B.
          

    Ezt a predikátumot is több módon lehet definiálni. Fontos szempont, hogy a két gyereknek nincs közös szülője, viszont vannak olyan szülők, melyek házastársak.

  15.         :- op(500,xfx,'mostohaszuloje').
            Gy mostohaszuloje MSz:-
              Gy szuloje Sz,
              Sz hazastarsa MSz,
              \+ Gy szuloje MSz.
          

    A mostohaszülő az édes szülő házastársa, de ő maga nem valódi szülő.

  16.         :-op(500,xfx,'unokatestvere').
            U1 unokatestvere U2:-
              U1 szuloje SzuloA,
              U2 szuloje SzuloB,
              SzuloA testvere SzuloB.
          

    Sokak számára azok az unokatestvérek, akiknek a nagyszülei közösek. Viszont ez teljesül a testvérekre is. Az előző definícióval ez kikerülhető.

  17.         :-op(500,xfx,'masodunokatestvere').
            U1 masodunokatestvere U2:-
              U1 nagyszuloje N1,
              U2 nagyszuloje N2,
              N1 testvere N2.
          

    Ahogy az előbb, itt sem megfelelő egy közös dédszülő megléte.

  18.         :-op(500,xfx,'nagybatyja').
            Gy nagybatyja Nagybacsi:-
              Gy szuloje Sz,
              Sz testvere Nagybacsi,
              Nagybacsi ferfi.
    
            Gy nagybatyja Nagybacsi:-
              Gy szuloje Sz,
              Sz testvere T,
              T ferje Nagybacsi.
          

    Nem csak a szülő fiútestvére lehet nagybácsi, hanem a lánytestvér férje is.

  19.        :-op(500,xfx,'angya').
            Gy angya Angy:-
              Gy szuloje Sz,
              Sz testvere T,
              T felesege Angy.
          

    Az ángy viszont csak a szülő fiútestvérének a felesége lehet.

  20.         :-op(500,xfx,'nagynenje').
            Gy nagynenje Nagyneni:-
              Gy szuloje Sz,
              Sz testvere Nagyneni,
              Nagyneni no.
    
            Gy nagynenje Nagyneni:-
              Gy angya Nagyneni.
          

    A nagybácsihoz hasonlóan itt is létezik a két eset, amelyikből az egyik éppen az ángy.

  21.         :-op(500,xfx,'unokaoccse').
            N unokaoccse Unokaoccs:-
              N testvere T,
              Unokaoccs szuloje T,
              Unokaoccs ferfi.
          
  22.         :-op(500,xfx,'unokahuga').
            N unokahuga Unokahug:-
              N testvere T,
              Unokahug szuloje T,
              Unokahug no.
          
  23.         :-op(500,xfx,'sogora').
            A sogora Sogor:-
              A testvere B,
              B ferje Sogor.
    
            A sogora Sogor:-
              A hazastarsa B,
              B testvere Sogor,
              Sogor ferfi.
          
  24.         :-op(500,xfx,'sogornoje').
            A sogornoje Sogorno:-
              A testvere B,
              B felesege Sogorno.
    
            A sogornoje sogorno:-
              A hazastarsa B,
              B testvere Sogorno,
              Sogorno no.
          
  25.         :-op(500,xfx,'aposa').
            F aposa Apos:-
              F hazastarsa T,
              T apja Apos.
            
  26.         :-op(500,xfx,'anyosa').
            F anyosa Anyos:-
              F hazastarsa T,
              T anyja Anyos.
            
  27.         :-op(500,xfx,'menye').
            A menye Meny:-
              T szuloje A,
              T felesege Meny.
            
  28.         :-op(500,xfx,'veje').
            A veje Vo:-
              T szuloje A,
              T ferje Vo.
          
  29.         :-op(500,xfx,'naszura').
            A naszura Naszuram:-
              Gy szuloje A,
              Gy hazastarsa T,
              T apja Naszuram.
          
  30.         :-op(500,xfx,'naszasszonya').
            A naszura Naszasszony:-
              Gy szuloje A,
              Gy hazastarsa T,
              T anyja Naszasszony.
          

Programtesztelés

Az alábbi feladatok megoldása során a trace parancs kimenetét olvashatóbbá tettük, a sorok végén jelöltük a megfelelő elemzési fa adott csúcsát (zöld számmal jelzett formuláját). Az levezetési fákon kékkel jelöltük az illesztő helyettesítéseket, valamint a megfelelő szabály betűjelét, amely a feladat kitűzésében szereplő programlisták sorainak végén található. Míg az alábbi listák csak az aktuális utasítást jelölik, a fán ábrázoltunk minden olyan utasítást, mely a veremben megtalálható, és sorra kell kerüljön a későbbiekben. A fa sikeres ágait minden rajzon pipával jelöltük.

  1. Lássuk először a trace kimenetét!

    Call: (1) a(A, B)         --1
    Call: (2) b(a, B)         --2
    Exit: (2) b(a, c(a))
    Call: (2) b(c(a), a)      --3
    Fail: (2) b(c(a), a)
    Redo: (2) b(a, B)         --2
    Exit: (2) b(a, b)
    Call: (2) b(b, a)         --4
    Fail: (2) b(b, a)
    Redo: (2) b(a, B)         --2
    Exit: (2) b(a, b)
    Call: (2) b(b, a)         --5
    Fail: (2) b(b, a)
    Redo: (1) a(A, B)         --1
    Call: (2) b(A, A)         --6
    Exit: (2) b(c(**), c(**)) --7
    Exit: (1) a(c(**), a)
    A = c(**),
    B = a ;
    Redo: (2) b(A, A)         --6
    Exit: (2) b(b, b)         --8
    Exit: (1) a(b, a)
    A = b,
    B = a

    Ebben a feladatban az jelenthet nehézséget, hogy a hatossal jelölt b(A,A) formulát a c-vel jelölt b(X,c(X))-el kell illeszteni, tehát végeredményben X=c(X) teljesülését várjuk el. X helyébe sorra behelyettesítve ezt az értéket végül a c(c(c(...))) végtelen szerkezethez jutunk, melyet a Prolog rendszer c(**) formában jelöl.

  2. Lássuk először a trace kimenetét!

    Call: (1) p(A, B, C)    --1
    Call: (2) r(B, B)       --2
    Exit: (2) r(B, B)
    Call: (2) r(C, C)       --3
    Exit: (2) r(C, C)
    Exit: (1) p(a, B, C)
    A = a ;
    Redo: (2) r(C, C)       --3
    Exit: (2) r(b, b)
    Exit: (1) p(a, B, b)
    A = a,
    C = b ;
    Redo: (2) r(B, B)       --2
    Exit: (2) r(b, b)
    Call: (2) r(C, C)       --4
    Exit: (2) r(C, C)
    Exit: (1) p(a, b, C)
    A = a,
    B = b ;
    Redo: (2) r(C, C)       --4
    Exit: (2) r(b, b)
    Exit: (1) p(a, b, b)
    A = a,
    B = b,
    C = b ;
    Redo: (1) p(A, B, C)    --1
    Call: (2) r(C, A)       --5
    Exit: (2) r(A, A)
    Exit: (1) p(A, a, A)
    A = C,
    B = a ;
    Redo: (2) r(C, A)       --5
    Exit: (2) r(b, a)
    Exit: (1) p(a, a, b)
    A = a,
    B = a,
    C = b ;
    Redo: (2) r(C, A)       --5
    Exit: (2) r(C, b)
    Exit: (1) p(b, a, C)
    A = b,
    B = a

    Ami megzavarhatja a feladatmegoldót, az ebben az esetben a következő: mind az ötössel jelölt formulában, mint a j-vel jelölt programsorban található A-val jelölt változó. Viszont ezek teljesen függetlenek, a programlistában bármilyen változónév állhatna ezen a helyen, ezért is jelöltük az alábbi ábrán az illesztő helyettesítésben ezt a változót A'-vel.

  3. Lássuk először a trace kimenetét!

    Call: (1) z(A)          --1
    Call: (2) y(Y, A)       --2
    Exit: (2) y(1+2, 2+1)
    Call: (2) 2+1\=1+2      --3
    Exit: (2) 2+1\=1+2
    Exit: (1) z(2+1)
    A = 2+1 ;
    Redo: (2) y(Y, A)       --2
    Exit: (2) y(2, 1+1)
    Call: (2) 1+1\=2        --4
    Exit: (2) 1+1\=2
    Exit: (1) z(1+1)
    A = 1+1 ;
    Redo: (2) y(Y, A)       --2
    Exit: (2) y(1, 1)
    Call: (2) 1\=1          --5
    Fail: (2) 1\=1
    Redo: (1) z(A)          --1
    Call: (2) y(A, Y)       --6
    Exit: (2) y(1+2, 2+1)
    Call: (2) 1+2=\=2+1     --7
    Fail: (2) 1+2=\=2+1
    Redo: (2) y(A, Y)       --6
    Exit: (2) y(2, 1+1)
    Call: (2) 2=\=1+1       --8
    Fail: (2) 2=\=1+1
    Redo: (2) y(A, Y)       --6
    Exit: (2) y(1, 1)
    Call: (2) 1=\=1         --9
    Fail: (2) 1=\=1

    A jellemző hiba ebben az esetben a unifikáció és egyenlőség keverése. Az unifikáció (=) akkor teljesül, ha létezik illesztő helyettesítés, mellyel a két tag azonossá tehető. Ha ilyen nincs, akkor a \= teljesül. Esetünkben a 2+1 a +(2,1) szerkezetnek felel meg, ami nem esik egybe a +(1,2) szerkezettel, míg az 1\=1 esetén mindkét oldalon azonos term szerepel, így az unifikáció teljesül. Az egyenlőtlenség (=\=) esetén mindkét oldal értékének kiszámítása az első lépés, és ha ezek az értékek különböznek, akkor teljesül az egyenlőtlenség.

    [Megjegyzés]Megjegyzés

    Ha valamiért az egyenlőtlenség vizsgálatakor az egyik oldalnak még nincs értéke, például egy értékkel nem rendelkező változó miatt, akkor a program hibajelzéssel leáll.

  4. Lássuk először a trace kimenetét!

    Call: (1) d(A)          --1
    Call: (2) e(A, A)       --2
    Exit: (2) e(a, a)
    Exit: (1) d(a)
    A = a ;
    Redo: (2) e(A, A)       --2
    Exit: (2) e(a, a)
    Exit: (1) d(a)
    A = a ;
    Redo: (1) d(A)          --1
    Call: (2) f(A, a, A)    --3
    Exit: (2) f(b, a, b)
    Call: (2) f(b, b, b)    --4
    Fail: (2) f(b, b, b)

    A kettessel jelölt formula második alternatívájának feldolgozásakor előkerülő vágás megakadályozza a eme formula harmadik alternatívája kifejtését. A hármassal jelölt formula első alternatívájánál előforduló vágások a pedig ennek a formulának a második alternatíváját rejtik el.

    [Megjegyzés]Megjegyzés

    Az ábrákon a piros folt kezdőpontja a vágást tartalmazó utasítássorozat, és eltart az általa kizárt, pirossal jelölt alternatívá(k)hoz.

  5. Lássuk először a trace kimenetét!

    Call: (2) q(A)          --1
    Call: (3) s(a, A)       --2
    Call: (10) A=a
    Exit: (10) a=a
    Exit: (3) s(a, a)
    Exit: (2) q(a)
    A = a ;
    Redo: (2) q(A)          --1
    Call: (3) s(A, a)       --3
    Exit: (3) s(b, a)
    Exit: (2) q(b)
    A = b

    A kettessel jelölt formula kibontásánál szereplő vágás a második alternatíváját, a hármassal jelölt formula kibontásánál szereplő vágás pedig az összes követő alternatíváját kizárja a feldolgozásból.

  6. Lássuk először a trace kimenetét!

    Call: (1) x(A, B)       --1
    Call: (2) w(A, B)       --2
    Call: (3) s(A, B)       --3
    Exit: (3) s(1, 0)
    Call: (3) 1<0           --4
    Fail: (3) 1<0
    Redo: (3) s(A, B)       --3
    Exit: (3) s(0, 1)
    Call: (3) 0<1           --5
    Exit: (3) 0<1
    Exit: (2) w(0, 1)
    Call: (2) w(1, 0)       --6
    Call: (3) s(1, 0)       --7
    Exit: (3) s(1, 0)
    Call: (3) 1<0           --8
    Fail: (3) 1<0
    Redo: (3) s(1, 0)       --7
    Redo: (2) w(1, 0)       --6
    Call: (3) s(1, 0)       --9
    Exit: (3) s(1, 0)
    Call: (3) 1=:=0         --10
    Fail: (3) 1=:=0
    Redo: (3) s(1, 0)       --9
    Fail: (1) x(A, B)

    Az igazi érdekességét a feladatnak az adja, hogy az ötössel és hatossal jelölt formulák közötti részben két vágás is előfordul. Ezek közül az első az s/2 további alternatíváit törli, míg a második pedig a w/2 alternatíváit.

  7. Lássuk először a trace kimenetét!

    Call: (1) x(A, B)       --1
    Call: (2) w(A, B)       --2
    Call: (3) s(A, B)       --3
    Exit: (3) s(1, 0)
    Call: (3) 1<0           --4
    Fail: (3) 1<0
    Redo: (3) s(A, B)       --3
    Exit: (3) s(0, 1)
    Call: (3) 0<1           --5
    Exit: (3) 0<1
    Exit: (2) w(0, 1)
    Call: (2) w(1, 0)       --6
    Call: (3) s(1, 0)       --7
    Exit: (3) s(1, 0)
    Call: (3) 1<0           --8
    Fail: (3) 1<0
    Redo: (3) s(1, 0)       --7
    Redo: (2) w(1, 0)       --6
    Call: (3) s(1, 0)       --9
    Exit: (3) s(1, 0)
    Call: (3) 1=:=0         --10
    Fail: (3) 1=:=0
    Redo: (3) s(1, 0)       --9
    Fail: (1) x(A, B)
    [Megjegyzés]Megjegyzés

    Tagadás esetén a rendszer a tagadott állítást külön megpróbálja levezetni. Ezeket az elkülönülő levezetéseket jelöltük az ábrákon sárga téglalapokkal. Ha ebben a téglalapban ott a pipa, akkor a levezetés sikeres, azaz a tagadása sikertelen.

    Az ábra bal felén látható, hogy a minden tagadott állítást sikerült levezetni, így a tagadások meghiúsultak. Csak a másik ágon kapunk megoldásokat.

  8. Lássuk először a trace kimenetét!

    Call: (2) t(A)          --1
    Call: (2) u(A, A)       --2
    Exit: (2) u(a, a)
    Call: (2) u(Y, a)       --3
    Exit: (2) u(a, a)
    Redo: (2) u(A, A)       --2
    Exit: (2) u(b, b)
    Call: (2) u(Y, b)       --4
    Exit: (2) u(a, b)
    Redo: (2) u(A, A)       --2
    Fail: (2) u(A, A)
    Fail: (2) t(A)

    Ebben a feladatban a tagadott állítás változót tartalmaz, tehát ha bármilyen értékére ennek a változónak teljesül az állítás, akkor a tagadás sikertelen. Így az eredeti feladatnak nincs megoldása.

  9. Lássuk először a trace kimenetét!

    Call: (7) v(A, B)       --1
    Call: (8) u(A, A)       --2
    Exit: (8) u(b, b)
    Fail: (7) v(A, B)

    Az előző feladathoz hasonlóan itt is tartalmaz szabad változót a tagadás, de mivel a tagadott formulának lesz megoldása, a feladatnak már nem.

Listakezezelés

  1. Az üres listát monotonnak tekintjük. Nem üres lista esetén a lista fejét (ami mindenképp létezik) leválasztjuk. A farokkal és a fejjel egy hasonló nevű, de kétargumentumú predikátumot hívunk meg.

    monoton_n([]).
    monoton_n([X|Xs]):-
      monoton_n(Xs,X).

    Ha a farok üres lista, kész vagyunk. Ellenkező esetben a korábbi fejnek kisebbnek kell lennie mint az mostani fejnek. Ha ez teljesül, a mostani fejet választjuk le, és maradék farokkal rekurzívan hívjuk meg a predikátumot.

    monoton_n([],_).
    monoton_n([Y|Ys],X):-
      X < Y,
      monoton_n(Ys,Y).    

    Mivel a predikátum két szabályában az első változó egyszer üres lista volt, egyszer pedig nem üres lista, így nincsenek egyik ágnak sem alternatívái.

  2. A megoldás az előző megoldás variánsa.

    monoton_cs([]).
    monoton_cs([X|Xs]):-
      monoton_cs(Xs,X).
    monoton_cs([],_).
    monoton_cs([Y|Ys],X):-
      X > Y,
      monoton_cs(Ys,Y).
    1. Ha a páros hosszú listából két elemet törlünk, továbbra is páros hosszú listát kapunk.

      paros_hosszu1([]).
      paros_hosszu1([_,_|L]):-
        paros_hosszu1(L).
    2. Persze lehetőség van arra, hogy egyesével töröljünk elemeket, ekkor a páratlan/páros tulajdonságok között kell váltogatni:

      paros_hosszu2([]).
      paros_hosszu2([_|L]):-
        paratlan_hosszu2(L).
      paratlan_hosszu2([_|L]):-
       paros_hosszu1(L).
  3. Az előző megoldás második része helyett azt is felhasználhatjuk, hogy a páratlan listából két elemet törölve újra páratlan listát kapunk. Mivel a predikátum mindkét ágában nem üres lista szerepel, így vágással tudunk megszabadulni az alternatíváktól.

    paratlan_hosszu([_]):-!.
    paratlan_hosszu([_,_|L]):-
     paratlan_hosszu(L).
  4. Milyen esetek fordulhatnak elő két lista metszetének meghatározása során?

    Két input listánk van. Az egyiken végighaladva kapjuk meg majd a megoldást. A mi választásunk az első listára esett.

    1. A legegyszerűbb eset, ha ez a lista üres (C eset), mert üres halmazzal bármely halmazt elmetszve üres halmazt kapunk.

      metszet([],_Ys,[]).

      Ha az első lista feje előfordul a másik listában is, akkor a metszetben is szerepelnie kell. A metszet további részét pedig a farok és a másik lista metszete adja (A eset). Az előfordulást a memberchk/2 segítségével ellenőrizzük:

      metszet([X|Xs],Ys,[X|Zs]):-
        memberchk(X,Ys),
        metszet(Xs,Ys,Zs).

      Ha a fej nem fordul elő a másik listában, akkor a metszetben sem szerepelhet (B eset). Itt a tagadást a \+ konstrukció segítségével fejezzük ki.

      metszet([X|Xs],Ys,Zs):-
        \+ member(X,Ys),
        metszet(Xs,Ys,Zs).
    2. Ezt a megoldást fel lehet gyorsítani akkumulátorváltozó használatával.

      Hogyan használhatjuk az akkumulátorváltozókat a metszet kiszámítására?

      Ennek a változónak (Acc) az értéke kezdetben legyen üres lista, és folyamatosan gyűjtjük bele a metszetbe tartozó elemeket.

      metszet2(Xs,Ys,R):-
        metszet2(Xs,Ys,[],R).

      Ha az első lista végére értünk (C eset), az akkumulátorváltozóban ott van a metszet tartalma, csak fordított sorrendben. Ezért meg kell fordítani.

      metszet2([],_Ys,Acc,R):-
        reverse(Acc,R).

      Ha az első lista nem üres, tehát van egy feje, akkor dönteni kell, hogy ez a fej bekerül-e a metszetbe, vagy sem. Továbbra is a memberchk/2 predikátumot használjuk, valamint a -> konstrukciót, amellyel if-then-else szerkezet alakítható ki. Ha a fej szerepel a másik listában, akkor bővíteni kell vele az akkumulátorváltozót (A eset), egyébként figyelmen kívül kell hagyni (B eset).

      metszet2([X|Xs],Ys,Acc,R):-
        memberchk(X,Ys) ->
          metszet2(Xs,Ys,[X|Acc],R)
        ;
          metszet2(Xs,Ys,Acc,R).
  5. Unió esetén a megoldás hasonló az előzőhöz. Csupán az eltérésekre hívjuk fel a figyelmet.

    1. A legegyszerűbb eset, ha az első lista üres (D eset), mert az unióban a másik lista elemei szerepelni fognak.

      unio([],Ys,Ys).

      Ha az első lista feje előfordul a másik listában is, akkor onnan úgyis bekerül az unióba, tehát figyelmen kívül hagyhatjuk (B eset). Az unió további részét pedig a farok és a másik lista uniója adja.

      unio([X|Xs],Ys,Zs):-
        member(X,Ys),!,
        unio(Xs,Ys,Zs).

      Ha a fej nem fordul elő a másik listában, akkor mindenképpen be kell rakni az unióba (A eset).

      unio([X|Xs],Ys,[X|Zs]):-
        \+ member(X,Ys),
        unio(Xs,Ys,Zs).

      A vágás a második szabályból elhagyható lenne (vagy akár a tagadott ellenőrzés a harmadikból), viszont így tudatosítjuk a programmal, hogy felesleges lesz visszatérnie a harmadik szabályra.

    2. Az akkumulátorváltozós eset is hasonló. Itt is üres listával kezdünk.

      unio2(L1,L2,R):-
        unio2(L1,L2,[],R).

      Ha az első lista végére értünk (C eset), az akkumulátorváltozóban ott van minden olyan elem, mely nem szerepelt a második listában. Ezért ez a lista kibővítve a másik listával adja a megoldást.

      unio2([],Ys,Acc,R):-
        reverse(Acc,RAcc),
        append(RAcc,Ys,R). 

      Ha az első lista nem üres, tehát van egy feje, akkor dönteni kell, hogy ez a fej bekerül-e az unióba, vagy sem. Ha a fej szerepel a másik listában, akkor elhagyjuk (B eset), egyébként bővítjük vele az akkumulátorváltozót (A eset).

      unio2([X|Xs],Ys,Acc,R):-
        member(X,Y2) -> 
          unio2(Xs,Ys,Acc,R)
        ;  
          unio2(Xs,Ys,[X|Acc],R).
  6. A különbség meghatározása is hasonlít az előző két megoldáshoz.

    1. A legegyszerűbb eset, ha az első lista üres (C eset), mert ekkor a különbség üres lista.

      kulonbseg([],Ys,[]).

      Ha az első lista feje előfordul a másik listában is, akkor nem kerül bele a különbségbe (B eset).

      kulonbseg([X|Xs],Ys,Zs):-
        member(X,Ys),!,
        kulonbseg(Xs,Ys,Zs).

      Ha a fej nem fordul elő a másik listában, akkor mindenképpen be kell rakni a különbségbe (A eset).

      kulonbseg([X|Xs],Ys,[X|Zs]):-
        \+ member(X,Ys),
        kulonbseg(Xs,Ys,Zs).
    2. Az akkumulátorváltozós eset is hasonló. Itt is üres listával kezdünk.

      kulonbseg2(Xs,Ys,R):-
        kulonbseg2(Xs,Ys,[],R).

      Ha az első lista végére értünk (C eset), az akkumulátorváltozóban ott van a különbség minden eleme.

      kulonbseg2([],_Ys,Acc,R):-
        reverse(Acc,R). 

      Ha az első lista nem üres, tehát van egy feje, akkor dönteni kell, hogy ez a fej bekerül-e a különbségbe, vagy sem. Ha a fej szerepel a másik listában, akkor elhagyjuk (B eset), egyébként bővítjük vele az akkumulátorváltozót (A eset).

      kulonbseg2([X|Xs],Ys,Acc,R):-
        member(X,Ys) ->
          kulonbseg2(Xs,Ys,Acc,R)
        ;
          kulonbseg2(Xs,Ys,[X|Acc],R).
  7. Szimmetrikus differencia esetén egyik legegyszerűbb megoldás a két kiinduló lista mindkét különbségét képezni, majd ezeket összefűzni (pl. append/3). Itt most egy másik megoldást mutatunk be, mely egy olyan predikátumot használ, amely két lista formájában adja vissza azokat az elemeket, melyek mindkét listában szerepelnek, s azokat, melyek csak az elsőben. Ezt a predikátumot kétszer használva megkaphatjuk a szimmetrikus differenciát. (Apróbb változtatásokkal megkapható a különbség, a metszet és az unió is.)

    Két akkumulátorváltozót használunk, melyeket üres listaként inicializálunk, és elsőként a két input listát vizsgáljuk meg. Ezután a második listával és a közös elemekkel indítjuk újra a módszert, így megkapjuk azokat az elemeket is, melyek csak L2 listában szerepelnek.

    szim_diff(Xs,Ys,Zs):-
      szd(Xs,Ys,[],Kozos,[],Elsoben),
      szd(Ys,Kozos,[],_,[],Masikban),
      append(Elsoben,Masikban,Zs).

    Ha feldolgoztuk az első listát, akkor az eredményeket megfordítva (helyes sorrendben) adjuk vissza.

    szd([],_,Kozos,RK,Elter,RE):-
      reverse(Kozos,RK),
      reverse(Elter,RE).

    Nem üres lista esetén az a kérdés, hogy ez az elem szerepel-e a másik listában, vagy sem. Ha igen, akkor a közös tárolóba tesszük, egyébként az eltérőket tárolóba.

    szd([X|Xs],Ys,AccK,Kozos,AccE,Elter):-
      member(X,Ys) -> 
        szd(Xs,Ys,[X|AccK],Kozos,AccE,Elter)
      ;
        szd(Xs,Ys,AccK,Kozos,[X|AccE],Elter).
  8. A legegyszerűbb a helyzet az üres halmazzal.

    :- op(500,xfx,'reszhalmaza').
    [] reszhalmaza [].

    A részhalmaz megalkotásához választani kell egy fejet, s meg kell határozni egy farkat. Mindkettő a másik halmazból származhat. A farokra ezt rekurzívan hívjuk meg, a fejet pedig választhatjuk az eredeti lista fejeként,

    [X|Xs] reszhalmaza [X|Ys]:-
      Xs reszhalmaza Ys.

    vagy pedig az eredeti farkából valahonnan.

    Xs reszhalmaza [_|Ys]:-
      Xs reszhalmaza Ys.
  9. A megoldás egyszerűsítése érdekében az adott elemre vonatkozó feltételt külön implementáljuk:

    kozott(A,X,F):-
      A<X,
      X<F.
    1. Üres lista esetén már kész is vagyunk.

      intervallumban([],_,_,[]).

      Ha a feltétel teljesül, a megfelelő elemek közé fog tartozni az aktuális is.

      intervallumban([X|Xs],A,F,[X|M]):-
        kozott(A,X,F),!,
        intervallumban(Xs,A,F,M).

      Ha a feltétel nem teljesül, akkor figyelmen kívül kell hagyni az elemet.

      intervallumban([X|Xs],A,F,M):-
        \+ kozott(A,X,F),
        intervallumban(Xs,A,F,M).
    2. Az akkumulátorváltozós megoldás is hasonló. Plusz változóval kell indítani a predikátumot, és itt is kész vagyunk üres lista esetén.

      intervallumban2(LL,A,F,L):-
        intervallumban2(LL,A,F,[],L).
      intervallumban2([],_,_,Acc,L):-
        reverse(Acc,L).

      Ha a lista nem üres, az a kérdés, hogy az aktuális elemre teljesül-e a feltétel vagy sem. Ennek megfelelően felvesszük az akkumulátorváltozóba az elemet, vagy nem.

      intervallumban2([X|Xs],A,F,Acc,L):-
        kozott(A,X,F) ->
          intervallumban2(Xs,A,F,[X|Acc],L)
        ;
          intervallumban2(Xs,A,F,Acc,L).
  10. Halmazműveletek monoton listákkal

    A monoton növekvő tulajdonság miatt egyszerre fogyaszthatjuk mindkét listát, nincs szükség a memberchk/2 használatára.

    Ha valamely lista üres, a metszet is üres lista lesz. A megoldáshoz így csak a már eltárolt elemeket kell előszedni.

    metszet3([],_Ys,Acc,R):-
      reverse(Acc,R).
    metszet3(_Xs,[],Acc,R):-
      !,
      reverse(Acc,L3).

    Ha a két lista feje (a legkisebb elemek) azonos, akkor ez az elem bekerül a metszetbe is.

    metszet3([X|Xs],[X|Ys],Acc,L):-
      !,
      metszet3(Xs,Ys,[X|Acc],L).

    Ha az egyik fej kisebb a másiknál, már biztosan nem kerül be a metszetbe.

    metszet3([X|Xs],[Y|Ys],Acc,L):-
      X<Y,!,
      metszet3(Xs,[Y|Ys],Acc,L).
    metszet3([X|Xs],[Y|Ys],Acc,L):-
      Y<X,
      metszet3([X|Xs],Ys,Acc,L).
  11. Ha valamely lista üres, az unió a másik lista, és ezt kell összefűzni a tárolt elemekkel.

    unio3([],Ys,Acc,R):-
      reverse(Acc,Cca),
      append(Cca,Ys,R).
    unio3(Xs,[],Acc,R):-
      !,
      reverse(Acc,Cca),
      append(Xs,Cca,R).

    Ha van egy közös elem, akkor az az unióban is szerepelni fog.

    unio3([X|Xs],[X|Ys],Acc,L):-
      !,
      unio3(Xs,Ys,[X|Acc],L).

    Ha az egyik fej kisebb mint a másik, akkor azt mindenképpen be kell rakni az unióba.

    unio3([X|Xs],[Y|Ys],Acc,L):-
      X<Y,!,
      unio3(Xs,[Y|Ys],[X|Acc],L).
    unio3([X|Xs],[Y|Ys],Acc,L):-
      Y<X,
      unio3([X|Xs],Ys,[Y|Acc],L).
  12. Ha az első lista üres, akkor a különbség is üres lista. Ha a másik lista üres, akkor a teljes lista a különbség. Ezeket kell kiegészíteni a tárolt elemekkel.

    kulonbseg3([],_Ys,Acc,R):-
      reverse(Acc,R).
    kulonbseg3(Xs,[],Acc,R):-
      !,
      reverse(Acc,Cca),
      append(Xs,Cca,L3).

    Ha van egy közös elem, akkor az a különbségben nem fog szerepelni. Sőt a második lista kisebb feje sem szerepel a különbségben.

    kulonbseg3([X|Xs],[X|Ys],Acc,R):-
      !,
      kulonbseg3(Xs,Ys,Acc,R).
    kulonbseg3([X|Xs],[Y|Ys],Acc,R):-
      !,
      Y<X,
      kulonbseg3([X|Xs],Ys,Acc,R).

    Ha az első fej kisebb mint a második, akkor azt mindenképpen be kell rakni a különbségbe.

    kulonbseg3([X|Xs],[Y|Ys],Acc,R):-
      X<Y,
      kulonbseg3(Xs,[Y|Ys],[X|Acc],R).
  13. A két lista különbségeit összefűzve ismét megoldáshoz juthatnánk, viszont mi most végigvisszük a teljes megoldást.

    Ha valamely lista üres, akkor a másik lista a szimmetrikus differencia. Ezeket kell kiegészíteni a tárolt elemekkel.

    szim_diff3([],Ys,Acc,R):-
      reverse(Acc,Cca),
      append(Cca,Ys,R).
    szim_diff3(Xs,[],Acc,R):-
      !,
      reverse(Acc,Cca),
      append(Xs,Cca,R).

    Ha van egy közös elem, akkor az a szimmetrikus differenciában nem fog szerepelni.

    szim_diff3([X|Xs],[X|Ys],Acc,R):-
      !,
      szim_diff3(Xs,Ys,Acc,R).

    Ha a két fej különbözik, a kisebb már szerepelni fog a megoldásban.

    szim_diff3([X|Xs],[Y|Ys],Acc,R):-
      X<Y,!
      szim_diff3(Xs,[Y|Ys],[X|Acc],L).
    szim_diff3([X|Xs],[Y|Ys],Acc,L):-
      Y<X,
      szim_diff3([X|Xs],Ys,[Y|Acc],L).
  14. A megoldás során akkumulátorváltozóba gyűjtjük a már megfordított elemeket. Ha az bemenő paraméter elfogy, akkor az akkumulátorváltozót visszaadva megkapjuk a megoldást.

    fordit(L,LL):-
      fordit(L,[],LL).
    fordit([],Acc,Acc).

    Attól függően, hogy a lista feje (X) lista vagy nem az, a következőt kell tenni: lista esetén rekurzívan kell rá alkalmazni a predikátumot, és az így visszakapott megfordított fejet (XX) az akkumulátorváltozóban tárolni, és folytatni az eljárást az Xs farokkal. Ha a fej nem lista, akkor nem kell megfordítani, egyébként ugyanaz a teendő.

    fordit([X|Xs],Acc,LL):-
      is_list(X) -> 
      fordit(X,[],XX), fordit(Xs,[XX|Acc],LL);
      fordit(Xs,[X|Acc],LL).
    1. A laposítás során a rekurzió igen egyszerű megoldáshoz vezet. Ha az aktuális elem nem lista, akkor nincs mit laposítani.

      laposit(X,[X]) :- 
        \+ is_list(X).

      Térjünk át a listákra. Az üres listával nincs baj.

      laposit([],[]).

      Egy általános lista esetén lehet laposítani a fejet és a farkat is. Ezek után már csak össze kell fűzni az eredményeket.

      laposit([X|Xs],Zs) :- 
        laposit(X,Y), 
        laposit(Xs,Ys), 
        append(Y,Ys,Zs).
    2. Akkumulátorváltozókkal is célhoz érünk, csak ekkor az akkumulátorváltozó értékének megfordítottját kell visszaadnunk eredményként.

      laposit2(L,LL):-
        laposit2(L,[],LL).
      laposit2([],Acc,LL):-
        reverse(Acc,LL).

      Általános esetben a lista feje alapján dől el, hogy mit kell tenni. Ha ez lista, akkor hozzáfűzhetjük a farokhoz, és ennek eredményét kell tovább laposítani. Ha a fej nem lista, akkor az akkumulátorváltozóba mentjük a fejet, és folytatjuk az eljárást a lista farkával

      laposit2([X|Xs],Acc,LL):-
        is_list(X) ->
        append(X,Xs,XXs), 
        laposit2(XXs,Acc,LL);
        laposit2(Xs,[X|Acc],LL).
  15. A feladat kitűzésében nem szerepelt, hogy az eredményeknek az eredeti lista részlistáinak kell lenniük. Emiatt a legegyszerűbb megoldás az lesz, hogy egyik listába a páratlan, a másik listába a páros sorszámú elemeket tesszük.

    1. A legfeljebb egy elemű listák esetén könnyű megoldani a feladatot.

      felez([],[],[]).
      felez([X],[X],[]):-!.

      Ha legalább kételemű a lista, akkor a két első elemet szétosztjuk a másik két lista között.

      felez([X,Y|Zs],[X|Xs],[Y|Ys]):- 
        felez(Zs,Xs,Ys).
    2. Akkumulátorlisták esetén két új paramétert kell bevezetni, melybe gyűjtjük a páratlan és páros elemeket. Ha bemenő lista már majdnem elfogyott, akkor csak az első listába jut elem.

      felez2(L,L1,L2):- 
        felez2(L,[],L1,[],L2). 
      felez2([],L1,R1,L2,R2):- 
        reverse(L1,R1), 
        reverse(L2,R2). 
      felez2([X],L1,R1,L2,R2):- 
        reverse([X|L1],R1), 
        reverse(L2,R2).
  16. A megoldás során sorra vesszük a lista elemeit. Ha a soron következő elem nagyobb, mint a megelőző, akkor az hozzávehetjük az aktuális monoton növekvő sorozathoz, amit egy akkumulátorváltozóban tárolunk. Ha a viszont ez már nem teljesül, akkor ezt a növekvő sorozatot hozzá kell fűzni a többi már összegyűjtött monoton sorozatokhoz (másik akkumulátorváltozó), és egy új sorozatot kezdünk. Ha a lista elfogyott, akkor az akkumulátorváltozóink tartalmát vissza kell adni, de mindet megfelelő irányba kell fordítani.

    darabol([X|Xs],LL):- 
      darabol(Xs,[X],[],LL). 
    darabol([],Acc,LAcc,LL):- 
      reverse(Acc,Cca), 
      reverse([Cca|LAcc],LL).

    Általános esetben a monotonitás megléte alapján döntünk a két eset között.

    darabol([X|Xs],[A|Cc],LAcc,LL):- 
      X>A -> darabol(F,[X,A|Cc],LAcc,LL); 
      reverse([A|Cc],Cca), 
      darabol(Xs,[X],[Cca|LAcc],LL).
  17. A lista a sztringeket karakterek sorozataként tartja számon, s egy karaktert ASCII kódja reprezentál. Mivel a szóköz karakternél kell feldarabolni a kiinduló listát, a 32-es elemeket keressük a listában. Az előző megoldáshoz hasonlóan két segédváltozóra lesz szükség, az egyik a szó már meglévő betűit, míg í másik a már feldarabolt szavak listáját tartalmazza. Indításkor a sztring első betűjét elhelyezzük az első segédváltozóban.

    darabol2([X|Xs],LL):- 
      darabol2(Xs,[X],[],LL).

    Ha az input kiürült, az utolsó szót meg kell fordítani, mert a segédváltozóban fordítva szerepel, s hozzá kell venni a már összegyűjtött szavakhoz. Mivel itt is fordított a sorrend, újabb fordításra van szükség.

    darabol2([],Acc,LAcc,LL):-
      reverse(Acc,Cca),
      reverse([Cca|LAcc],LL).

    Ha az input lista nem üres, akkor az első eleme dönti el, hogy hozzávegyük-e ezt a betűt a szóhoz, vagy pedig vágni kell. Ez utóbbi esetben az utolsó szót irányba kell fordítani, és hozzávenni a korábbi szavakhoz.

    darabol2([X|Xs],Acc,LAcc,LL):-
       X\=32 -> 
       darabol2(Xs,[X|Acc],LAcc,LL); 
       reverse(Acc,Cca), 
       darabol2(Xs,[],[Cca|LAcc],LL).
  18. Mivel a feladat hasonló az őt kettővel megelőzőhöz, a megoldás is az lesz. Akár lehetne az ott megadott megoldást is használni, viszont mivel az azonos elemek sorrendje tetszőleges, nem kell annyit forgatni.

    Mint ott, itt is két segédváltozót használunk, az első az azonos elemeket, a másik ezek listáit tartalmazza. Az egészet a kezdő elemmel indítjuk el.

    darabol3([X|Xs],LL):-
      darabol3(Xs,[X],[],LL).

    Ha az input lista kiürült, akkor az utolsó sorozatot hozzá kell adni a többihez, és irányba fordítani.

    darabol3([],Acc,LAcc,LL):-
      reverse([Acc|LAcc],LL).

    Általános esetben az alapján megyünk tovább, hogy megegyezik-e a soron következő elem (X) az utolsóval (A), vagy sem. Ha igen, hozzáfűzzük a korábbiakhoz, ellenkező esetben a listát átrakjuk a többihez, és új sorozatot indítunk a soron következő elemmel

    darabol3([X|Xs],[A|Cc],LAcc,LL):- 
      X=A -> 
      darabol3(Xs,[X,A|Cc],LAcc,LL); 
      darabol3(Xs,[X],[[A|Cc]|LAcc],LL).
  19. Mivel a lista elején ott van az, amit a maradék végére kell illeszteni, egy összefűzés elég a megoldáshoz

    forgat([X|L],LX):- 
      append(L,X,LX).
  20. Természetesen ezt a programot igen sok más módszerrel meg lehet oldani. Abban viszont közösek, hogy legalább kétszer mindenképpen végig kell menni a listán.

    1. A lista végén van az, amit le kell választanunk. Megfordítva a listát, ehhez az elemhez könnyen hozzáférünk, de a maradékot vissza kell forgatni, hogy a megoldást megkapjuk.

      forgat1(LX,[X|L]):-
        reverse(LX,[X|R],
        reverse(R,L). 
    2. A hagyományos módszerrel eljuthatunk a lista végéig, és a harmadik paraméterben ezt adjuk vissza.

      forgat2(LX,[X|L]):- 
        forgat2(LX,L,X). 
      forgat2([X],[],X):-!. 

      A második paraméterben pedig összegyűjtjük az átlépett elemeket.

      forgat2([Y|Ys],[Y|L],X):- 
        forgat2(Ys,L,X).
    3. Használhatunk segédváltozót is, ebbe kerülnek az átlépett elemek, kezdetben pedig a lista feje.

      forgat3([Y|Ys],[X|L]):- 
        forgat3(Ys,[Y],L,X).

      Ha a lista kiürült, az összegyűjtött elemek listáját (Acc) megfordítjuk, és eme elem elé kell berakni az eredeti lista utolsó elemét (X)

      forgat3([],[X|Acc],Cca,X):- 
        reverse(Acc,Cca). 

      Ha pedig van elem a listában, mentsük el a segédváltozóban.

      forgat3([Z|Zs],Acc,L,X):- 
        forgat3(Zs,[Z|Acc],L,X).
    1. Ha üres a lista, nincs mit cserélni.

      csere([],_,_,[]).

      Ha a fejben a cserélendő elem szerepel, akkor le kell cserélni.

      csere([X|Xs],X,Y,[Y|Ys]):- 
        !,
        csere(Xs,X,Y,Ys).

      Ha más elem van a fejben, akkor figyelmen kívül kell hagyni.

      csere([X|Xs],M,C,[X|Ys]):-
        X\=M,
        csere(Xs,M,C,Ys).
    2. Használjunk egy segédváltozót arra, hogy a megvizsgált (és esetleg lecserélt) elemeket összegyűjtsük.

      csere2(L,M,C,LL):- 
        csere2(L,M,C,[],LL).

      Ha a lista kiürült, a segédváltozóban szinte ott a megoldás.

      csere2([],_,_,Acc,LL):-
         reverse(Acc,LL).

      Általános esetben az alapján kell cserélnünk, hogy a keresett elem (M) szerepel-e a fejben (X), vagy sem.

      csere2([X|Xs],M,C,Acc,LL):- 
        X=M -> 
        csere2(Xs,M,C,[C|Acc],LL); 
        csere2(Xs,M,C,[X|Acc],LL).

Logikai programok

  1. Elsőként ellenőrizzük, hogy a megadott formula predikátumváltozó, vagy annak tagadása

    literal(A):-
      atomic(A),!.
    literal(~A):-
      atomic(A).      

    Noha a program szerzője tudja, hogy az alábbi szabályokban egymást kizáró esetek lettek megadva, vágásokkal ezt érdemes a Prolog tudomására is hozni! Ha a részformulákat már negatív normálformára hoztuk, akkor a formula nnf alakja is egyértelmű.

    Ha egy formula literál, akkor már negatív normálformában is van.

    nnf(A,A):-
      literal(A),!. 

    A dupla tagadásokat nyugodtan eleminálhatjuk, de lehet hogy a tagadások mögött még találhatunk teendőt.

    nnf( ~(~A), UjA):-
      nnf(A,UjA),!. 

    A konjunkción és diszjunkción egyszerűen keresztülléphetünk, és csak a részformulákat kell rendbe rakni.

    nnf(   A /\ B,  UjA /\ UjB):-
      nnf(A,UjA),
      nnf(B,UjB),!.
    nnf(   A \/ B,  UjA \/ UjB):-
      nnf(A,UjA),
      nnf(B,UjB),!.

    Ha ugyanezek tagadás mögött szerepelnek, akkor a de Morgan szabályokat kell használnunk.

    nnf( ~(A /\ B), NemA \/ NemB):-
      nnf(~A,NemA),
      nnf(~B,NemB),!.
    nnf( ~(A /\ B), NemA \/ NemB):-
      nnf(~A,NemA),
      nnf(~B,NemB),!.

    Implikáció nem szerepelhet a formulában, így a jól ismert összefüggések használhatóak ezek eltávolítására.

    nnf(   A => B,  NemA \/ UjB):-
      nnf(~A,NemA),
      nnf(B,UjB),!.
    nnf( ~(A => B), UjA /\ NemB):-
      nnf(A,UjA),
      nnf(~B,NemB),!.

    Hasonló módszerekkel lehet eltüntetni az ekvivalenciákat is.

    nnf(A <=> B, (UjA /\ UjB) \/ (NemA /\ NemB)):-
      nnf(A,UjA), nnf(~A,NemA),
      nnf(B,UjB), nnf(~B,NemB),!.
    nnf( ~(A <=> B), (UjA /\ NemB) \/ (NemA /\ UjB)):-
      nnf(A,UjA), nnf(~A,NemA),
      nnf(B,UjB), nnf(~B,NemB),!.

    Noha nem feladat, de mivel az egyenlő precedencia miatt a Prolog nem jelöli a számunkra szükséges zárójeleket, kiírjuk a negatív normálformában szereplő formulákat megfelelően zárójelezve.

    Mivel a zárójelezés szükségessége mindig a környezettől függ, nyilván tartjuk, hogy mi volt az ezt megelőző összekötőjel. A normálforma tulajdonságai miatt ez csak konjunkció (a) vagy diszjunkció (o) lehet. Ha a formula egy literál, nyugodtan kiírható, nincs szükség újabb zárójelekre.

    kiirNnf(_,A):-
     literal(A), write(A).

    Csak akkor kell zárójelet írnunk, ha az ezt megelőző szinten ellenkező összekötő jel volt.

    kiirNnf(o,A /\ B):-
      write('('),
      kiirNnf(a,A),
      write(' /\\ '),
      kiirNnf(a,B),
      write(')').
    kiirNnf(a,A \/ B):-
      write('('),
      kiirNnf(o,A),
      write(' \\/ '),
      kiirNnf(o,B),
      write(')').

    Mi a teendő, ha ugyanolyan jel jön, amilyen a környezetében is volt? Az asszociativitás miatt ekkor nem kell zárójelet írni, elegendő kiírni a csak a két részformulát és az összekötő jeleket.

    kiirNnf(a,A /\ B):-
      kiirNnf(a,A),
      write(' /\\ '),
      kiirNnf(a,B),!.
    kiirNnf(o,A \/ B):-
      kiirNnf(o,A), 
      write(' \\/ '), 
      kiirNnf(o,B),!.

    Már csak azt kellene kideríteni, hogy kezdetben milyen környezet van? Legyen olyan, amilyen a fő összekötő jele a formulának:

    kiirNnf(A \/ B):-
      kiirNnf(o,A \/ B),!.
    kiirNnf(A /\ B):-
      kiirNnf(a,A /\ B),!.
  2. A konjunktív normálforma elemi diszjunkciók konjunkciója. Ezért ezt is tudni kell ellenőrizni! Ha a fő összekötőjel diszjunkció akkor a részformuláknak is ezeknek kell lenniük.

    elemi_disz(A \/ B):-
      elemi_disz(A),
      elemi_disz(B),!.

    Speciálisan a literál is elemi diszjunkció.

    elemi_disz(A):-
      literal(A),!.

    Ha a fő összekötőjel konjunkció, akkor a részformuláknak is konjunktív normálformájúaknak kell lenniük.

    knf(A /\ B):-
      knf(A),knf(B),!.

    Ha a fő összekötőjel már diszjunkció, akkor eljutottunk az elemi diszjunkciókhoz, így a részformulák is ilyenek.

    knf(A \/ B):-
      elemi_disz(A),elemi_disz(B),!.

    Speciálisan egy magában álló literál is konjunktív normálformában van:

    knf(A):-
      literal(A),!.

    A teljesség kedvéért legyen itt a diszjunktív normálforma tesztelése is!

    elemi_konj(A /\ B):-
      elemi_konj(A),
      elemi_konj(B),!.
    elemi_konj(A):-
      literal(A),!.
    dnf(A \/ B):-
      dnf(A),dnf(B),!.
    dnf(A /\ B):-
      elemi_konj(A),elemi_konj(B),!.
    dnf(A):-
      literal(A),!.
  3. Arra kell gondolnunk, hogy a negatív normálformájú formula nem más mint egy fa. Itt a csúcsokban konjunkció vagy diszjunkció szerepel, míg a levelekben literál. Attól lesz a formula konjunktív normálformájú, hogy a felső csúcsokban konjunkció, míg az alsókban diszjunkció található.

    Ennek megfelelően mialatt bejárjuk a fát a gyökerétől kezdve, ha konjunkcióra akadunk, akkor azt nem kell bántanunk, mivel jó helyen van. Csupán a részfáit/részformuláit kell megint ilyen alakra hozni.

    knf((X /\ Y),(A /\ B)) :-
       knf(X,A),
       knf(Y,B),!.

    Akkor válik érdekessé a dolog, ha diszjunkcióra akadunk. Az kellene elérni, hogy lejjebb ne legyen konjunkció. Ha mindkét részformuláját sikerül konjunktív normálformájúvá alakítani, akkor a bennük szereplő konjunkciók felkerülnek a részformulák gyökerébe. Ekkor már nem marad más teendő, mint a disztributív szabályt felhasználva felcseréljük az összekötő jelek sorrendjét. Viszont az is előfordulhat, hogy a gyökérbe már konjunkció van, de még a lejjebb lévő szinteken (a diszjunkciók alatt) maradt még belőlük. Ezért ekkor újra be kell járni a formulát. Ha nem sikerül a disztributív szabályt alkalmazni, vagyis nincs a részformulában konjunkció, akkor nincs más teendő, véget érhet a predikátum végrehajtása.

    knf((X \/ Y),G) :-
       knf(X,A), 
       knf(Y,B),!,
       (
         diszt_o((A \/ B),F) 
         -> 
           knf(F,G)
         ; 
           G = A \/ B ).

    Egy dolog marad, a leveleknél abbahagyni a folyamatot.

    knf(X,X):-
      literal(X),!.

    Lássuk ennek a párját, a diszjunktív normálforma előállítását!

    dnf((X \/ Y),(A \/ B)) :-
       dnf(X,A),
       dnf(Y,B),!.
    dnf((X /\ Y),G) :-
       dnf(X,A),
       dnf(Y,B),!,
       (
         diszt_a((A /\ B),F)
        -> 
          dnf(F,G)
        ;
          G = A /\ B ).
    dnf(X,X):-
      literal(X),!.
  4. Nézzük milyen módon lehet egyszerűsíteni egy konjunktív normálformulát! Az elemi diszjunkció egyszerűsíthető úgy, hogy benne minden literál csak egyszer fordul elő, a többszörös előfordulásokat töröljük. Másrészt, ha egy elemi diszjunkcióban szerepel egy literál, meg a tagadása is, akkor ezek diszjunkciója a konstans igazat adja, így a konjunktív normálformából elhagyható ez az elemi diszjunkció. Mivel az előfordulás egyszerűbben vizsgálható listák esetén mint formula-fákban, így a konkjunktív normálformájú formulát listák listájává alakítjuk. Egy elemi diszjunkció fog egy listát megadni, ahol a literálok lesznek az elemek.

    Nézzük milyen egyszerűsítések lehetségesek!

    1. Többszörös literálok törlése

      Egy literál csak akkor fog bekerülni a listába, ha ott még nem szerepel. Ezzel a többszörös előfordulást ki is zártuk.

      elemi2list(A,Acc,E):-
        literal(A),!,
        (member(A,Acc)-> E=Acc; E=[A|Acc]).

      Általános esetben az eddig már összegyűjtött literálok listáját (Acc) előbb az első részformula literáljaival, majd a második részformula literáljaival bővítjük.

      elemi2list(A \/ B,Acc,E):-
        elemi2list(A,Acc,Acc1),
        elemi2list(B,Acc1,E),!.

      Térjünk át a konjunktív normálformulákra! Itt már a literál-listák listájának egy része is kész van, ezt kell bővíteni a egyik, majd a másik részformula megfelelő listáival.

      knf2list(A /\ B,Acc,E):-
        knf2list(A,Acc,Acc1),
        knf2list(B,Acc1,E),!.

      Ha a formulában már eljutottunk az elemi diszjunkciókig, akkor az ehhez tartozó literál-listát kell elkészíteni, majd hozzávenni az eddig összegyűjtött listákhoz.

      knf2list(A \/ B,Acc,[E|Acc]):-
        elemi2list(A \/ B,[],E),!.

      Speciális esetben, az egyedül álló literálok esetén a literál-lista egyelemű lesz:

      knf2list(A,Acc,[[A]|Acc]):-
        literal(A).

      A teljesség kedvéért kerüljön ide a diszjunktív normálforma listákká alakítása is.

      elemi2list(A /\ B,Acc,E):-
        elemi2list(A,Acc,Acc1),
        elemi2list(B,Acc1,E),!.
      dnf2list(A \/ B,Acc,E):-
        dnf2list(A,Acc,Acc1),
        dnf2list(B,Acc1,E),!.
      dnf2list(A /\ B,Acc,[E|Acc]):-
        elemi2list(A /\ B,[],E),!.
      dnf2list(A,Acc,[[A]|Acc]):-
        literal(A).
    2. Ellentétpárok kezelése

      Tehát sikerült már átalakítani a formulát listákká. Nézzük sikerül-e a lista egy-egy elemét törölni, mert ellentétes literálok szerepelnek benne! Talán azzal sikerül leginkább gyorsítani a megoldáson, ha a lista elemeit külön szedjük pozitív és negatív — azaz tagadás nem tartalmazó, vagy tartalmazó — literálokra. Ehhez két listába gyűjtjük az elemeket, és ha kiürült a lista, kész is vagyunk.

      pn([],P,N,P,N):-!.

      Ellenkező esetben vizsgáljuk meg az aktuális elemet, és helyezzük a megfelelő halmazba!

      pn([X|Xs],AccP,AccN,P,N):-
        (
          atomic(X)
          -> 
            AP=[X|AccP],
            AN=AccN
          ;
            ~NX =X,
            AN=[NX|AccN],
            AP=AccP
        ),
        pn(Xs,AP,AN,P,N).

      Nincs más hátra, mint végigmenni a listák listáján, és mindegyiknél megvizsgálni, tartalmaz-e ellentétes párokat vagy sem? Ha a listák listája kiürült, kész is vagyunk.

      ellentet([],L,L).

      Ha van egy ellentétes pár, azaz azonos elem a pozitív és negatív listában, akkor a megfelelő listát (L) kihagyhatjuk a listák listájából.

      ellentet([L|Ls],Acc,E):-
        pn(L,[],[],P,N),
        member(X,P),
        memberchk(X,N),!,
        ellentet(Ls,Acc,E).

      Ha ilyen ellentétpár nincs, akkor a lista megmarad.

      ellentet([L|Ls],Acc,E):-
        ellentet(Ls,[L|Acc],E).
    3. "A kishal megeszi a nagyot"

      Az előbbi néven szoktam nevezni az A ~ A∧(A∨B) összefüggést, illetve a duálisát. Ennek végrehajtásához végig kell mennünk a literál-listákon, és teszteni, hogy közülük valamelyik nem része-e valamely másiknak. Ha igen, akkor közülük a nagyobb törölhető. Megoldható lenne, hogy a minden literál-lista esetén ide-oda ellenőrizzük a tartalmazást, viszont mi most egy másik módszert követünk: az aktuális listát összehasonlítjuk a már megvizsgált listákkal és a később sorra kerülő listákkal is.

      Ha már minden listát megvizsgáltunk, akkor kész is vagyunk.

      kisnagy([],Acc,E):-
        reverse(Acc,E),!.

      Ha még nem, akkor az L aktuális listát összevetjük a hátralevő listákkal (Ls), valamint a már megvizsgáltakkal (Acc).

      kisnagy([L|Ls],Acc,E):-
        reszelst(Ls,L,[],UjLs),
        reszelst(Acc,L,[],UjAcc),
        kisnagy(UjLs,[L|UjAcc],E).

      Az összevetés a következőképpen megy: megnézzük, hogy az aktuális Y listának része-e az X lista, vagy sem. Ha igen, akkor nincs szükség az Y-ra, nyugodtan elhagyhatjuk, egyébként meg kell tartanunk, ezért elmentjük az akkumulátorváltozóba.

      reszelst([Y|Ys],X,Acc,E):-
        resze(X,Y) ->
          reszelst(Ys,X,Acc,E)
        ;
          reszelst(Ys,X,[Y|Acc],E).

      Természetesen ha kiürült a lista, kész is vagyunk.

      reszelst([],_,Acc,E):-
        reverse(Acc,E).

      Csak az maradt ki, hogy hogyan ellenőrizhető a részhalmaz tulajdonság. Rendszerint ezt úgy oldjuk meg, hogy a kisebb halmaz minden eleme eleme a nagyobb halmaznak is. Mivel a Prologban nehézkes egy ciklust szervezni, a következő a megoldás: nincs olyan eleme a kisebb halmaznak, amely ne lenne eleme a nagyobbnak.

      resze(A,B):- 
        \+ (
          member(X,A),
          \+ memberchk(X,B)
        ).

    Ezek után nincs más hátra, mint megfelelő sorrendben összekötni a predikátumokat:

    minimalizal(K,Min):-
      knf2list(K,[],L),
      ellentet(L,[],N),
      kisnagy(N,[],Min).
  5. A knf2list predikátum felbontja a formulát a listákra. Ha diszjunktív normálformát szeretnénk eredményül, akkor egy ilyen listából formulát kell készíteni. Egy egyszerű lista egy elemi konjunkciónak felel meg.

    Ha a lista már csak egy literált tartalmaz, akkor az saját maga alkotja az elemi konjunkciót.

    list2elemiK([A],A):-!.

    Ha a ennél hosszabb a lista, akkor a lista farkából alkotott konjunkciót még ehhez hozzá kell kapcsolni.

    list2elemiK([A|As],A /\ AA):-
      list2elemiK(As,AA).

    Miután az elemi konjunkciók konstruálása kész, jöhet a diszjunktív normálforma! Ha a listák listája csak egyelemű, akkor a belőle képezhető elemi konjunkció alkotja a normálformát.

    list2dnf([L],D):-
      list2elemiK(L,D),!.

    Ellenkező esetben a lista fejéből képzett elemi konjunkciót kell összekapcsolni a lista farkából képzett normálformával.

    list2dnf([L|Ls],D \/ Ds):-
      list2elemiK(L,D),
      list2dnf(Ls,Ds).

    A teljesség kedvéért nézzük hogyan készíthető konjunktív normálforma literál-listák listájából!

    list2elemiD([A],A):-!.
    list2elemiD([A|As],A \/ AA):-
      list2elemiD(As,AA).
    list2knf([L],K):-
      list2elemiD(L,K),!.
    list2knf([L|Ls],K \/ Ks):-
      list2elemiD(L,K),
      list2knf(Ls,Ks).

    Most már csak az a kérdés, hogyan lesz a konjunktív normálformának megfelelő listák listájából diszjunktív normálformának megfelelő listák listája. Logikából ismert módszer a "végigszorzás", ami szerint mondjuk az (A∨¬B)∧(C∨¬D∨E) formulára úgy gondolunk, mint a (A+-B)(C+-D+-E) képletre, melyből a disztributivitással AC+A(-D)+A(-E)+(-B)C+(-B)(-D)+(-B)(-E) nyerhető. Ennek megfelelően az előbbi formula (A∧C)∨(A∧¬D)∨(A∧¬E)∨(-B∧C)∨(¬B∧¬D)∨(¬B∧¬E) nyerhető. Felmerülhet, milyen elemi konjunkciók szerepelnek itt? Olyanok, melyben minden egyes tag az eredeti formula egy-egy elemi diszjunkciójában szerepelt. Így nincs más dolgunk, mint a literál-listák mindegyikéből egy-egy elemet kiválasztani.

    valaszt([L|Ls],Acc,Es):-
      member(E,L),
      valaszt(Ls,[E|Acc],Es).

    Ha a literál-listák listája elfogyott, kész is vagyunk.

    valaszt([],Acc,Es):-
      reverse(Acc,Es).

    Ha szeretnénk az összes ilyen kombinációt megkapni, azt a bagof/3 beépített predikátummal könnyedén elérhetjük, amint azonnal be is mutatjuk.

    Ezek után nem maradt más, mint összekapcsolni az eddigieket.

    knf2dnf(K,D):-
      knf2list(K,[],KL),
      bagof(L,valaszt(KL,[],L),DL),
      list2dnf(DL,[],D).
  6. Hogyan is történik egy változótiszta alak elkészítése? Alapvetően azt kell elérnünk, hogy a szabad és kötött változók eltérő elnevezésűek legyenek, valamit hogy két kvantor ne legyen azonos nevű. Ha minden kvantort más és más elnevezéssel illetünk, akkor a második kritériumot teljesítjük. Az első teljesüléséhez folyamatosan nyilvántartjuk, hogy az adott részformula mely kvantorok hatáskörében van, és ennek megfelelő elnevezéseket alkalmazunk. A szabad változó neve különbözik ezektől az elnevezésektől, így nem fogunk neki más elnevezést adni. Az egyszerűség kedvéért a változóink x1,x2,... elnevezést kapnak, így ajánlott nem szerepeltetni ilyen változókat az inputban.

    A program a formula felépítésének fog megfelelni, minden összekötőjelre külön-külön szabályt kell megadnunk. Nyilvántartjuk a bemenő formulát, a menetközben konstruált helyettesítéseket, az eredményül kapott formulát, valamint egy számláló értékét a részformula feldolgozásának kezdetén és végén, hogy az indexeket ez alapján határozhassuk meg.

    A tagadás, konjunkció, diszjunkció és implikáció nincs hatással a kvantorokra. Egyszerűen a részformulákat kell feldolgozni, és hagyni, hogy a számláló tovább vigye a már megkapott értékét.

    valtozotiszta(~A,Helyett,~UjA,Kezd,Veg):-
      valtozotiszta(A,Helyett,UjA,Kezd,Veg),!.
    valtozotiszta(A \/ B,Helyett,UjA \/ UjB,Kezd,Veg):-
      valtozotiszta(A,Helyett,UjA,Kezd,Koztes),
      valtozotiszta(B,Helyett,UjB,Koztes,Veg),!.
    valtozotiszta(A /\ B,Helyett,UjA /\ UjB,Kezd,Veg):-
      valtozotiszta(A,Helyett,UjA,Kezd,Koztes),
      valtozotiszta(B,Helyett,UjB,Koztes,Veg),!.
    valtozotiszta(A => B,Helyett,UjA => UjB,Kezd,Veg):-
      valtozotiszta(A,Helyett,UjA,Kezd,Koztes),
      valtozotiszta(B,Helyett,UjB,Koztes,Veg),!.

    Az ekvivalencia fekete bárány. Ha ugyanis a prenex alak elkészítése lesz a cél, célszerű ezt az összekötőjelet lecserélni, és az így kapott formulával folytatni a feldolgozást.

    valtozotiszta(A <=> B,Helyett,F,Kezd,Veg):-
       valtozotiszta((A => B)/\(B =>A),Helyett,F,Kezd,Veg),!. 

    A kvantorok esetén azonos módon kell eljárnunk. A számláló aktuális értéke alapján generálunk egy újabb azonosítót, és a számlálót növeljük. A helyettesítések köze felvesszük régi elnevezését a kvantorváltozónak, valamint az új alakját, és rekurzívan folytatjuk a feldolgozást a részformulájával.

    valtozotiszta(mind(X,A),Helyett,mind(XK,UjA),Kezd,Veg):-
      K1 is Kezd+1,
      atom_concat(x,Kezd,XK),!,
      valtozotiszta(A,[X/XK|Helyett],UjA,K1,Veg).
    valtozotiszta(van(X,A),Helyett,van(XK,UjA),Kezd,Veg):-
      K1 is Kezd+1,
      atom_concat(x,Kezd,XK),!,
      valtozotiszta(A,[X/XK|Helyett],UjA,K1,Veg).

    Mi történik akkor, ha eljutottunk a predikátumokig? Ebben az esetben minden term esetén végre kell hajtani a helyettesítést. Ehhez a =.. predikátummal felbontjuk a predikátumot predikátumnévre és változók listájára. Végrehajtjuk a helyettesítést a változókon és ugyanezzel a predikátummal újra összeállítjuk a predikátumot a megváltoztatott változókkal.

    valtozotiszta(P,Helyett,UjP,N,N):-
      P=..[Pred|Valtozok],
      helyettesit(Valtozok,[],UjValtozok,Helyett),
      UjP=..[Pred|UjValtozok],!.
    [Megjegyzés]Megjegyzés

    Az egyszerűség kedvéért mi most csak változókat engedünk meg termekként, de apróbb változtatással elérhető, hogy bármilyen term szerepelhessen a formulában, csak az alábbi programrészletben a nem a helyettesítést kell meghívni, hanem a változótiszta formára alakítást.

    A helyettesítéshez minden egyes változó esetén meg kell vizsgálni, hogy szerepel-e a helyettesítések listájában, vagy sem. Ha szerepel, a párjára kell kicserélni, egyébként meg kell hagyni.

    helyettesit([V|Vs],Acc,Valtozok,Helyett):-
      member(V/H,Helyett) ->
        helyettesit(Vs,[H|Acc],Valtozok,Helyett);
        helyettesit(Vs,[V|Acc],Valtozok,Helyett).

    Ha már elfogytak a változók, a helyettesítés elkészült

    helyettesit([],Acc,Valtozok,_):-
      reverse(Acc,Valtozok),!.
  7. A prenex alak elkészítéséhez a formula belsejében szereplő kvantorokat a formula elejére kell kihozni az egyoldalas kiemelés segítségével. Mi nem pont ezt az útvonalat követjük, hanem külön listába gyűjtjük a formula kvantorait, és külön kezeljük a kvantoroktól megfosztott részformulákat. Miután sikerült a teljes formulát feldolgozni, már csak vissza kell rakni a kvantormentes formulára a kvantorokat.

    A programunk megint a formula felépítése szerint működik. Ha tagadott formuláról van szó, akkor a kvantorok tagadáson keresztüli kiemelése a kvantoros de Morgan szabály szerint azzal jár, hogy a kvantorok megfordulnak, amit nekünk is meg kell tennünk az összegyűjtött kvantorokkal.

    prenex(~A,~UjA,N1):-
      prenex(A,UjA,K1),
      fordit(K1,[],N1),!.

    Konjunkció és diszjunkció esetén ezzel nincs probléma.

    prenex(A \/ B,UjA \/ UjB,K):-
      prenex(A,UjA,K1),
      prenex(B,UjB,K2),
      append(K1,K2,K),!.
    prenex(A /\ B,UjA /\ UjB,K):-
      prenex(A,UjA,K1),
      prenex(B,UjB,K2),
      append(K1,K2,K),!.

    Az implikáció esetén vigyázni kell az előtagból kiemelt kvantorokkal, ezek is fordulnak.

    prenex(A => B,UjA => UjB,K):-
      prenex(A,UjA,K1),
      prenex(B,UjB,K2),
      fordit(K1,[],N1),
      append(N1,K2,K),!.

    A kvantorok esetén fel kell jegyezni a kvantort, és folytatni a folyamatot a részformulával.

    prenex(mind(X,A),UjA,[m/X|K]):-
      prenex(A,UjA,K),!.
    prenex(van(X,A),UjA,[v/X|K]):-
      prenex(A,UjA,K),!.

    Abban az esetben, ha predikátumhoz értünk, akkor az már biztos kvantormentes, így nincs semmi teendőnk. Azt, hogy valami predikátum-e, azt az előző megoldásban látott módszerrel teszteljük.

    prenex(P,P,[]):-
      P=.._.

    Következzen a kvantorok forgatása. Az univerzális kvantorból egzisztenciális lesz, és viszont.

    fordit([m/X|L],Acc,E):-
      fordit(L,[v/X|Acc],E),!.
    fordit([v/X|L],Acc,E):-
      fordit(L,[m/X|Acc],E),!.

    Ha pedig kiürült a lista, kész vagyunk. Viszont arra fokozottan kell figyelni, hogy az eredeti sorrendet nem változtathatjuk meg, mert az egészen más formulákhoz vezethet.

    fordit([],Acc,E):-
      reverse(Acc,E).

    Nem maradt más hátra, mint visszahelyezni a kvantorokat. Miután mi a legbelső kvantort kívánjuk először visszarakni, így a kvantorlistát megfordítjuk.

    kvantoroz(K,F,UjF):-
      reverse(K,K1),
      kvantor(K1,F,UjF).

    Az m és v kód határozza meg hogy milyen kvantorral bővítjük a formulát, illetve ha kiürült a lista elkészültünk.

    kvantor([m/X|Ks],F,UjF):-
      kvantor(Ks,mind(X,F),UjF),!.
    kvantor([v/X|Ks],F,UjF):-
      kvantor(Ks,van(X,F),UjF).
    kvantor([],F,F).

Rekurzív adatszerkezetek

  1. A keresőfát egy alsó és felső határral közelítjük. A fa minden eleme e két határ közé kell, hogy essen. A keresőfa-tulajdonság miatt például a bal oldali részfában a gyökérnél kisebb, ám az előbbiek miatt az alsó határnál nagyobb értékek szerepelnek, azaz rekurzívan folytathatjuk az ellenőrzést.

    Mivel nem ismerjük, hogy a fa milyen elemeket tartalmaz, a kezdeti korlátaink értékének minusz és plusz végtelenként kell viselkednie. Ezt úgy érjük el, hogy az összehasonlítás relációját lecseréljük egy sajátra, mely speciálisan kezeli az üres értéket (u). Ez kisebb és nagyobb is mindennél:

    kisebb(u,_):-!.
    kisebb(_,u):-!.

    Minden más esetben pedig a szokásos reláció a mérvadó:

    kisebb(X,Y):- X<Y.

    A predikátum meghívásakor bevezetjük a segédváltozókat, melyek kezdeti értéke üres:

    keresofa(T):-
      keresofa(T,u,u).

    Ezek után következhet az esetek felsorolása:

    • A fa üres, ekkor keresőfának tekintjük.

      keresofa(e,_,_):-!.
    • Ha levél, akkor a megadott két határ között kell lennie az értéknek.

      keresofa(l(X),Also,Felso):-
        kisebb(Also,X), kisebb(X,Felso),!.
    • Ha méretesebb fa, akkor a részfáinak is teljesíteniük kell a tulajdonságot, valamint a határok a gyökértől függően változnak.

      keresofa(n(X,Bal,Jobb),Also,Felso):-
        kisebb(Also,X),
        kisebb(X,Felso),
        keresofa(Bal,Also,X),
        keresofa(Jobb,X,Felso),!.
  2. A 2-3 fa esetén vannak olyan levelek, melyek két értéket tartalmaznak, illetve olyan csomópontok, melyek szintén két elemet tartalmaznak, és három részfa indul ki belőlük. Ezek jelölésére a továbbiakban az ll/2 és nn/5 függvényszimbólumokat használjuk.

    Az előző feladat megoldásához hasonlóan két segédváltozót használunk a határok tárolására, valamint egy újabb változópárt a fa magasságának számításához.

    kh_fa(T):- 
      kh_fa(T,u,u,0,_). 

    Így öt esetet kell megvizsgálni.

    • Üres fával nincs probléma.

      kh_fa(e):-!.
    • Egy elemet tartalmazó levélnél csak a korlátoknak kell megfelelni. A levél negyedik argumentumban szereplő mélységi szintje már megadja a fa magasságát.

      kh_fa(l(X),Also,Felso,H,H):-
        kisebb(Also,X),
        kisebb(X,Felso),!.
    • Kételemű levél esetén a két elemnek is megfelelő sorrendben kell lennie.

      kh_fa(ll(X,Y),Also,Felso,H,H):-
        kisebb(Also,X),
        X<Y,
        kisebb(Y,Felso),!.
    • Kettős elágazásnál a két részfának is teljesítenie kell a tulajdonságot, valamint a gyökér értékét is fel kell használni az új korlátok megadásánál. A gyökér alatt elemek mélységi szintje a gyökerénél eggyel nagyobb. A H értéke vagy már adott, vagy a B részfa bejárásakor meghatározásra kerül, így a J részfa magasságának ugyanakkorának kell lennie, mint a B-nek.

      kh_fa(n(X,B,J),Also,Felso,L,H):-
        kisebb(Also,X),
        kisebb(X,Felso),
        L1 is L+1,
        kh_fa(B,Also,X,L1,H),
        kh_fa(J,X,Felso,L1,H),!.
    • Hármas elágazánál is hasonló az eset, így a három részfa korlátaira, valamint a két elem sorrendjére kell figyelni

      kh_fa(nn(X,Y,B,K,J),Also,Felso,L,H):-
        kisebb(Also,X),
        X<Y,
        kisebb(X,Felso),
        L1 is L+1,
        kh_fa(B,Also,X,L1,H),
        kh_fa(K,X,Y,L1,H),
        kh_fa(J,X,Felso,L1,H),!.
  3. Ha a fa üres, akkor nem beszélhetünk maximális elemről. Így négy esetre kell megfogalmazni a megoldás menetét.

    • Ha egyelemű levélről van szó, akkor a levélben lévő elem a maximális.

      max_kh(l(X),X):-!.
    • Kételemű levél esetén a nagyobb elem a fa maximális eleme.

      max_kh(ll(_,Y),Y):-!.
    • Kettős elágazás esetén a maximális elem a jobb oldali részfában található. A fa kiegyensúlyozottsága miatt ez a részfa nem lehet üres, mindenképpen tartalmaz elemet.

      max_kh(n(_,_,J),X):-
        max_kh(J,X),!.
    • Hármas elégazásnál is a jobb oldali részfa maximális eleme lesz a keresett.

      max_kh(nn(_,_,_,_,J),X):-
        max_kh(J,X),!. 
  4. Egyes szerzőknél a kupac teljes fa, mi ettől a feltételtől eltekintünk az egyszerűség kedvéért, csak azt vesszük figyelembe, hogy a gyökér tartalmazza a fa legkisebb elemét, s ez teljesül a részfákra is. A korábbi megoldásokhoz hasonlóan fel kell használni egy alsó korlátot, melynek kezdetben üres (u) értéket adunk.

    kupac(T):-
      kupac(T,u). 

    Ezek után végig kell venni a bináris fa három esetét.

    • Az üres fa kupacnak tekinthető.

      kupac(e,_):-!. 
    • Levél esetén a levélben levő elemnek nagyobbnak kell lenni a korlátnál.

      kupac(l(X),Also):-
        kisebb(Also,X),!.
    • Általános esetben a gyökérben levő elem lesz a következő alsó korlát.

      kupac(n(X,Bal,Jobb),Also):-
        kisebb(Also,X),
        kupac(Bal,X),
        kupac(Jobb,X),!. 
  5. Egy tetszőleges bináris fát kell kupaccá alakítani. A fa bejárása és a kupac lépésről lépésre történő felépítése helyett egy másik utat követünk, amely közel áll a hagyományos megoldáshoz: a fa részfáit kupaccá alakítjuk, így azok gyökereiben találhatóak a minimális elemeik, és ezeket hasonlítjuk össze a fa gyökerében található elemmel. A három elem közül a minimális kerül a fa gyökerébe, és ha ez a minimális elem valamely részfában volt, akkor a fa gyökerének elemét kell beszúrni a megfelelő részfába. Mivel ez esetleg elrontja a részfa kupac-ságát, ezt a beszúrást rekurzív módon folytatni kell.

    Az egységes kezelés miatt megadunk egy segédpredikátumot, amely visszaadja a gyökérelemet.

    • Levél esetén a levélelemet,

      minimum(l(X),X):-!.
    • általános esetben a gyökérben levő elemet kell visszaadni

      minimum(n(X,_,_),X):-!.

    Lássuk a beszúrást!

    • Levél esetén nincs mit tenni.

      beszur_k(l(_),X,l(X)):-!.
    • Meg kell határozni a részfák gyökérelemeit: Bx és Jx. Ezek és X viszonya dönti el a további teendőket. Ha X a legkisebb, a fa nem változik meg. Egyébként a legkisebb elem fájába kell beszúrni X-et, és az eredeti fa gyökerébe a legkisebb elemet szúrjuk.

      beszur_k(n(_,B,J),X,T):-
        minimum(B,Bx),
        minimum(J,Jx),!,
        ( Bx<Jx -> 
          (X<Bx -> T=n(X,B,J); 
           beszur_k(B,X,Tb),
           T=n(Bx,Tb,J));
          (X<Jx -> T=n(X,B,J); 
           beszur_k(J,X,Tj),
           T=n(Jx,B,Tj))). 

    Most már következhet a fő predikátum.

    • Üres kupac esetén nincs mit tenni.

      kupacol(e,e):-!.
    • Levél esetén sincs teendő.

      kupacol(l(X),l(X)):-!. 
    • Az általános esetben támaszkodhatunk a segédpredikátumra. Miután a részfákat rekurzívan kupaccá alakítottuk, a régi gyökeret megpróbáljuk a fába beszúrni.

      kupacol(n(X,B,J),T):-
        kupacol(B,B2),
        kupacol(J,J2),
        beszur_k(n(_,B2,J2),X,T),!.
  6. Mivel a kupac nem keresőfa, itt nem a jobb oldali ágat kell követni. Mivel a gyökérelem kisebb minden más elemnél, és ez igaz minden részfára is, így a maximális elem csak valamely levélben lehet.

    max_k(l(X),X):-!. 

    A fa szerkezete miatt nem lehetséges, hogy mindkét részfája üres legyen. Ha valamely részfa üres, akkor a másik részfában található meg a maximális elem. Ha egyik részfa sem üres, összegyűjtjük a két részfa maximális elemeit, és közülük a nagyobbat választjuk.

    max_k(n(_,B,J),M):-
      (B = e -> max_k(J,M); 
       max_k(B,Bm)),
       (J = e -> max_k(B,M); 
        max_k(J,Jm),
        M is max(Bm,Jm)),!. 
  7. Mivel az AVL-fa egy keresőfa, a keresőfa tesztelése programjának a kiegészítése lesz. Egy plusz paraméterre lesz szükségünk, ami nem lesz más, mint a fa magassága. Ezt akkor kapjuk meg, amikor a részfákat már bejártuk.

    avl(T):-
      avl(T,u,u,_):-!. 
    • Üres fát AVL-fának tekintjük, és a magassága 0 lesz

      avl(e,_,_,0):-!.
    • A levél magasságát 1-nek tekintjük. Természetesen figyelni kell a korlátokra.

      avl(l(X),Also,Felso,1):-
        kisebb(Also,X),
        kisebb(X,Felso),!.
    • Általános esetben a keresőfa tulajdonságok ellenőrzése során kiderülnek a részfák magasságai: N1 és N2. Az AVL-tulajdonság szerint ezek nem térhetnek el nagyon egymástól. Végül képesek vagyunk meghatározni a fa magasságát is.

      avl(n(X,B,J),Also,Felso,N):-
        kisebb(Also,X),
        kisebb(X,Felso),
        avl(B,Also,X,N1),
        avl(J,X,Felso,N2),
        abs(N1-N2)<2,
        N is max(N1,N2)+1,!. 
  8. A hagyományos keresőfába beszúrást csak kicsit kell kiegészíteni, hogy AVL-fa beszúrást kapjunk. Üres fába, vagy levélbe szúrva nem sértjük meg helyben az AVL tulajdonságot. (Ez a kicsi fa persze lehet egy nagyobb fa része, melyben már megsérül a tulajdonság a beszúrással, de azt ott kell majd kezelni.)

    beszur_avl(X,e,l(X)):-!. 
    beszur_avl(X,l(Elem),n(Elem,l(X),e):-X<Y,!. 
    beszur_avl(X,l(Elem),n(Elem,e,l(X),e):-X>Y. 

    Általános esetben a beszúrás rekurzívan megy, de végül vizsgálni kell a javítás szükségességét.

    beszur_avl(X,n(Elem,Bal,Jobb),T):- 
     X<Elem ->  
       beszur_avl(X,Bal,UjBal), 
       javit_avl(n(Elem,UjBal,Jobb),T); 
       beszur_avl(X,Jobb,UjJobb), 
       javit_avl(n(Elem,Bal,UjJobb),T),!.

    Javítani a fán csak akkor kell, ha sérül a tulajdonság.

    javit_avl(T,T2):-
      avl(T) -> T2=T; 
      avl_forgat(T,T2).

    Eljutottunk végül a megoldandó feladathoz. De először a fa magasságára írunk egy predikátumot, mert ez az a tulajdonság, melyre szükség van a javítások kiválasztásakor. Természetesen ezt a predikátumot is meg lehet írni további segédváltozók segítségével, ahogy az a 2-3 fák tesztelésénél történt.

    magas(e,0):-!. 
    magas(l(_),1):-!. 
    magas(n(_,B,J),M):- 
     magas(B,M1), 
     magas(J,M2), 
     M is max(M1,M2)+1,!.

    Az AVL fák kijavítására egyszeres, vagy dupla forgatást alkalmazunk. A dupla forgatás megadható két egyszerű forgatás egymás után végrehajtásával, de számunkra a direkt megadás egyszerűbbnek tűnik. Minden esetben a fa gyökere az, ahol az AVL-tulajdonság sérül.

    • Első esetben a fa jobbra dől, míg a jobb oldali részfa egyensúlyban van, vagy szintén jobbra dől. A pontosvessző a vagy kapcsolatot jelzi, így nem kell kétszer leírni szinte ugyanazt.

      avl_forgat(n(X,A,n(Y,B,C)),n(Y,n(X,A,B),C)):-
        magas(A,M),
        magas(B,M),
        magas(C,M1),
        (M1 is M+1;M1 is M),!. 
    • A második eset az első tükörképe

      avl_forgat(n(X,n(Y,A,B),C),n(Y,A,n(X,B,C))):-
        magas(A,M1),
        magas(B,M),
        magas(C,M),
       (M1 is M+1;M1 is M),!. 
    • Harmadik esetben míg a fa jobbra dől, a jobb oldali részfája már balra. Ekkor dupla forgatásra van szükség, a részfát jobbra, a forgatás utáni fát balra kellene forgatni.

      avl_forgat(n(X,A,n(Y,n(Z,B,C),D)),n(Z,n(X,A,B),n(Y,C,D))):-
        magas(A,M),
        magas(B,Bm),
        magas(C,Cm),
        magas(D,M),
        M1 is M-1, 
       (Bm=M,Cm=M;Bm=M1,Cm=M;Bm=M,Cm=M1),!.
    • Az utolsó eset a harmadik tükörképe

      avl_forgat(n(X,n(Y,A,n(Z,B,C)),D),n(Z,n(X,A,B),n(Y,C,D))):-
        magas(A,M),
        magas(B,Bm),
        magas(C,Cm),
        magas(D,M),
        M1 is M-1,
       (Bm=M,Cm=M;Bm=M1,Cm=M;Bm=M,Cm=M1),!. 
  9. Az eredményül várt fa egy keresőfa, csak nem mindegy, hogy az elemek milyen sorrendben kerülnek bele, Mivel egy ilyen fába levélként egyszerű egy elemet beszúrni, a gyökeret, vagyis a legnagyobb gyakoriságú elemet a legelsőként kellene beszúrni egy üres fába, s sorra az egyre csökkenő gyakoriságok szerint. Innen jöhet az ötlet, hogy először rendezzük a listát a gyakoriságok alapján, és ezután jöhet elemek egyszerű keresőfába szúrása.

    sulyra_keszit(L,Fa):-
      beszur_rendez(L,R),
      faepites(R,e,Fa).

    A rendezésre például használhatjuk a beszúró rendezést, ahol a lista farkát rendezzük, majd ebbe a rendezett listába szúrjuk be a helyére a lista fejét.

    beszur_rendez([],[]).
    beszur_rendez([X|Xs],Ys) :- 
      beszur_rendez(Xs,Zs), 
      beszur(X,Zs,Ys). 

    A beszúrás kicsit eltér az általános esettől, mert a lista elemei párok, ahol a pár második tagját kell figyelembe venni.

    beszur(X,[],[X]).
    beszur(X:Fx,[Y:Fy|Ys],L) :- 
      Fx < Fy ->
        beszur(X:Fx,Ys,Zs), 
        L=[Y:Fy|Zs];
      L=[X:Fx,Y:Fy|Ys]. 

    Ha elfogyott a lista, kész vagyunk.

    faepites([],T,T). 

    Ha van feje a listának, a már készülő fába kell beszúrni, és folytatni az eljárást a lista farkával.

    faepites([X|Xs],T,T2):-
      faba_szur(X,T,T1),
      faepites(Xs,T1,T2).

    A beszúrásnál sorra kell venni a lehetőségeket a fa felépítése szerint, ahogy az az AVL-fa programjánál is szerepelt.

    faba_szur(X:Fx,e,l(X:Fx)).

    Az összehasonlítás során már nem a gyakoriságokat, hanem a kulcsokat kell figyelni. Mivel ezek nem számkonstansok, így más összehasonlítást kell használni. Ha levéllel van dolgunk, már kész is vagyunk, ellenkező esetben a rekurzióra kell hagyatkozni.

    faba_szur(X:Fx,l(Y:Fy),T):-
      X@<Y -> 
        T=n(Y:Fy,l(X:Fx),e); 
      T=n(Y:Fy,e,l(X:Fx)).
    faba_szur(X:Fx,n(Y:Fy,B,J),T):-
      X@<Y -> 
        faba_szur(X:Fx,B,BX), 
        T=n(Y:Fy,BX,J); 
      faba_szur(X:Fx,J,JX), 
      T=n(Y:Fy,B,JX). 
  10. Nincs más dolgunk, mint a lista elemeinek megfelelő csúcsokat beszúrni a fába. Kezdetben ez a fa üres.

    dontesi_diagram(L,T):-
      dontesi_diagram(L,e,T). 

    Ha a listánk kiürült, kész is vagyunk.

    dontesi_diagram([],T,T).

    Ellenkező esetben a lista fejét a fába szúrjuk, majd ezt rekurzívan folytatjuk a lista farkával is.

    dontesi_diagram([X|Xs],T1,T):-
      dszur(X,T1,T2),
      dontesi_diagram(Xs,T2,T). 

    Üres fába egyszerűen be lehet szúrni elemeket.

    dszur([X],e,l(X)):-!.

    Ha a listában fejként szereplő szám még nem az utolsó, akkor ez egy elágazást jelöl a fában, és aszerint, hogy ez 0 vagy 1 kell az eljárást folytatni a bal vagy a jobb oldalon. Erre mind üres lista,

    dszur([0|Xs],e,n(B,e)):-
      []\=Xs,
      dszur(Xs,e,B),!. 
    dszur([1|Xs],e,n(e,J)):- 
      []\=Xs, 
      dszur(Xs,e,J),!. 

    mind nem üres lista esetén fel kell készülni.

    dszur([0|Xs],n(B,J),n(UjB,J)):-
       []\=Xs,
       dszur(Xs,B,UjB),!.
    dszur([1|Xs],n(B,J),n(B,UjJ)):-
      []\=Xs,
      dszur(Xs,J,UjJ),!.
  11. A megoldás egyik legfontosabb lépése, hogy eldöntsük, hogy milyen adatszerkezetet használjunk. Ez a fa egy általános fa, tetszőleges számú elágazással minden pontjában, így a megszokott bináris fa nem alkalmazható. Persze elvileg mindenki megtanulja, hogyan lehet bináris fával modellezni egy általános fát, mi listák segítségével fogjuk ábrázolni. A romane, romanus, romulus, rubens, ruber és rubicon szavakat tartalmazó fa a következő lesz: ["r", ["om", ["an", ["e"], ["us"]], ["ulus"]], ["ub", ["e", ["ns"], ["r"]], ["icon"]]]. Látható, hogy a közös részek vannak a gyökérben, az eltérések újabb és újabb ágakat indítanak.

    A szavak fába szúrásához szükség lesz két szó összehasonlítására. Ekkor meghatározzuk a közös prefixet. Ha valamely szó elfogyott, vagy már különböző betűk következnek, akkor kész vagyunk, egyébként fel kell jegyezni a közös betűket.

    hasonlit(Xs,Ys,Kozos):-
      hasonlit(Xs,Ys,[],Kozos).
    hasonlit(_,[],Acc,Kozos):-
      reverse(Acc,Kozos),!. 
    hasonlit([],_,Acc,Kozos):-
      reverse(Acc,Kozos),!.
    hasonlit([X|F1],[X|F2],Acc,Kozos):-
      !,
      hasonlit(F1,F2,[X|Acc],Kozos).
    hasonlit([X|_],[Y|_],Acc,Kozos):-
      X\=Y,
      reverse(Acc,Kozos),!.

    Ezek után következhet a radix-fába beszúrás predikátuma. Üres fába beszúrni egyszerű:

    beszur([],L,[L]).

    Egy elemet csak egyszer szerepelhet a fában, így ha megegyezik a gyökérben található elemmel, kész vagyunk.

    beszur([[Xs]|F],[Xs],[[Xs]|F]):- !.

    Ha a gyökérben található és a beszúrandó szónak üres a közös prefixe, akkor az új fa gyökere üres lesz. A lexikografikus elrendezés mondja meg, hogy milyen sorrendben fog szerepelni a két szó:

    beszur([[X|Xs]|F],[Y|Ys],[[],[[X|Xs]|F],[Y|Ys]]):- X<Y. 
    beszur([[X|Xs]|F],[Y|Ys],[[],[Y|Ys],[[X|Xs]|F]]):- X>Y. 

    További lehetőség, hogy van közös prefix, és az maga a gyökérben szereplő szó. Ekkor a lista farkában kell valahol elhelyezni a szavunk maradékát.

    beszur([Xs|F],Ys,[Xs|FF]):-
      append(Xs,[Z|Zs],Ys),
      beszurFarok(F,[Z|Zs],FF).

    Fordított esetben a beszúrandó szó kerül a gyökérbe, és a korábbi gyökeret kell átalakítani egy kicsit.

    beszur([Xs|F],Ys,[Ys,[],[[Z|Zs]|F]]):-
      append(Ys,[Z|Zs],Xs).

    Nem maradt más eset, mint amikor a közös prefix valódi prefixe mindkét szónak. Viszont ekkor is fontos a sorrend.

    beszur([Xs|F],Ys,[[K|Ks]|[[[U|Us]|F],[[V|Vs]]]]):-
      hasonlit(Xs,Ys,[K|Ks]),
      append([K|Ks],[U|Us],Xs),
      append([K|Ks],[V|Vs],Ys),
      U<V,!. 
    beszur([Xs|F],Ys,[[K|Ks]|[[V|Vs],[[U|Us]|F]]]):-
      hasonlit(Xs,Ys,[K|Ks]),
      append([K|Ks],[U|Us],Xs),
      append([K|Ks],[V|Vs],Ys),
      U>V,!.

    Adósak maradtunk azzal, hogyan kell a lista farkába beszúrni a megfelelő szót. Ha a farok üres, nincs probléma.

    beszurFarok([],Ys,[Ys]).

    Ha nincs közös prefixe a lista fejéhez tartozó szónak a beszúrandóhoz, akkor a listában kell elhelyezni vagy fejként az új szót, vagy valahova hátrább.

    beszurFarok([[[X1|X2]|Xs]|F],[Y|Ys],[[Y|Ys],[[X1|X2]|Xs]|F]):-
      X1>Y.
    beszurFarok([[[X1|X2]|Xs]|F],[Y|Ys],[[[X1|X2]|Xs]|FF]):-
      X1<Y,
      beszurFarok(F,[Y|Ys],FF).

    Ha viszont van közös prefix, akkor ebbe a részfába kell a korábban ismertetett módon beszúrni.

    beszurFarok([[X|Xs]|F],Ys,[Zs|F]):-
      hasonlit(X,Ys,[_|_]),
      beszur([X|Xs],Ys,Zs).

    Ezzel kész is a program, de egy bonyolult fát listák listájának listájaként nehéz áttekinteni. Ráadásként következzen egy program, amely a karakterlistákat olvasható elnevezésekre cserélni le.

    csere([],[]).
    csere(S,Xs):-
      is_list(S),
      S=[SS|_],
      atomic(SS),
      atom_chars(Xs,S).
    csere([X|Xs],[Y|Ys]):-
      csere(X,Y),
      csere(Xs,Ys). 

    Végül a programot a következőképp próbálhatjuk ki: beszur(["romane"],"romanus",L1),beszur(L1,"romulus",L2), beszur(L2,"rubens",L3), beszur(L3,"ruber",L4), beszur(L4,"rubicon",L5), csere(L5,YY).

Prog1 feladatok

  1. A megoldást több oldalról is meg lehet közelíteni.

    1. Egyik lehetőség a feladatban szereplő sorozatok generálása, majd azok összeszámlálása. Lehetőség van egyszerre csak egy sorozatot elkészíteni, vagy szinkronban generálhatjuk az összest is. Én az előbbi mellett maradok az egyszerűség kedvéért. Tehát generálni kívánunk egy olyan sorozatot, mely n hosszúságú, 0 és 1 elemeket tartalmaz, és nincs benne egymás mellett két egyes. Ehhez a lista(+Lista,+Hossz,-Eredmeny) predikátumot fogjuk elkészíteni, a Hossz a hátralévő elemek számát jelöli, a Lista-ban gyűjtjük az elemeket, és az Eredmeny tartalmazza majd a végeredményt. Ha a számláló nullára csökkent, kész vagyunk.

      lista(L,0,L).

      Ellenkező esetben a számlálót csökkenteni kell és be kell szúrni egy új elemet a listába (természetesen a fejbe, hisz oda egyszerűbb). Arra kell csupán figyelni, hogy ha a korábbi listafej 1-et tartalmazott, újabb egyest nem szúrhatunk eléje.

      lista([],N,L):- N>0,N1 is N-1, lista([0],N1,L). 
      lista([],N,L):- N>0, N1 is N-1, lista([1],N1,L). 
      
      lista([0|Xs],N,L):- N>0, N1 is N-1, lista([0,0|Xs],N1,L). 
      lista([0|Xs],N,L):- N>0, N1 is N-1, lista([1,0|Xs],N1,L). 
      
      lista([1|Xs],N,L):- N>0, N1 is N-1, lista([0,1|Xs],N1,L). 

      Ezek után nem marad más, össze kell számolni az ilyen listák számát. Szerencsére van több beépített predikátum is a Prolog-ban mellyel ez megoldható:

      sorozat(N,Db):- 
        setof(L,lista([],N,L),LL),
        length(LL,Db).
    2. Más megközelítés lehet, ha nem generáljuk a sorozatot, csak a számokat figyeljük. Jelölje f(n) az n hosszúságú listák számát! Az f(n-1) ezek szerint az n-1 hossúságú sorozatok számát jelöli. Minden ilyen sorozat elé írhatunk 0-t, mert az nem tiltott, viszont 1-et már nem mindegyik elé írhatunk, mert van köztük olyan, amely egyessel kezdődik. Hány n+1 hosszúságú sorozat lehet? Minden n hosszúságú sorozat elé írhatunk 0-t, ez már f(n), illetve a 0-val kezdődőek elé pedig 1-et, ez pedig f(n-1). Magyarul f(n+1)=f(n)+f(n-1), ahol f(1)=2 és f(2)=3. Ez pedig nem más, mint a Fibonacci sorozat egy variánsa. Tehát nincs más dolgunk, mint ennek a programját megírni!

      fib(N,F):- 
        N>0 -> fib(N,1,1,F); F=0.
      
      fib(0,F,_,F). 
      fib(N,A,B,F):- 
        N>0,
        N1 is N-1,
        C is A+B,
        fib(N1,C,A,F).
  2. A hullámok listájában szereplő számok minden esetben ugyanazok, így érdemes azokat konstansként tárolni. Ha a konstansban szerepel az összes egymást követő érték, akkor a hullám magasságának megfelelően kell kivágni belőle a szükséges részt. Ez megadja a felfutó szakaszt. A lefutó szakasz ennek fordítottja lesz.

    h_fel_le(N,LR):-
      N<10,
      length(L,N),
      append(L,_,[1,22,333,4444,55555,666666,7777777,88888888,999999999]),
      reverse(L,[_|R]),
      append(L,R,LR). 

    Ezek után nincs más, mint megfelelő számú hullámot egymás után illeszteni. A hullámok számolására egy segédváltozót vezetünk be.

    hullam(Ampl,Db,L):-
      hullam(Db,Ampl,[],L).
    
    hullam(0,_,L,L):-!.
    hullam(N,Ampl,Acc,L):-
      N>0,
      N1 is N-1,
      h_fel_le(Ampl,LR),
      append(LR,[0|Acc],LRAcc),
      hullam(N1,Ampl,LRAcc,L). 
  3. A permutáció listájából egy függvényt készítünk, s e függvény megfordítottja lesz az inverz permutáció függvénye. A függvényt párokkal jelöljük. A függvény és megfordítottja tárolására, valamint az index számolására segédváltozókat vezetünk be.

    nem_egyertelmu(P):-
      nem_egyertelmu(P,1,[],[]).
    nem_egyertelmu([X|Xs],I,P,Rp):-
      I1 is I+1,
      nem_egyertelmu(Xs,I1,[I:X|P],[X:I|Rp]).

    Ha ez a két függvény megegyezik, akkor a permutáció nem különböztethető meg az inverzétől. Ez nem jelent mást, mint a két függvényt tartalmazó lista egymás permutációi.

    nem_egyertelmu([],_,P,Rp):-
      !,
      permutation(P,Rp).
  4. A szám végén álló nullák száma a prímtényezős felbontásban szereplő 2 és 5 kitevőitől függ. Mivel a 2 kitevője faktoriálisok esetén nagyobb, mint az 5 kitevője, ezért csak ez utóbbi érdekes. Vannak olyan számok, melyekben az 5 kitevője egynél nagyobb, mint például 25 vagy 125. Épp ezért a faktoriális alapját jelentő számot újra és újra elosztjuk öttel, hogy megtudjuk az 5 kitevőjének értékét. A hányadosokat végül összegezzük.

    zaro_nulla(N,Db):-
      zaro_nulla(N,0,Db).
    zaro_nulla(0,Db,Db).
    zaro_nulla(N,I,Db):-
      N>0,
      N1 is N//5,
      I1 is I+N1,
      zaro_nulla(N1,I1,Db).
  5. A Goldbach sejtés prímekről szól, így első dolgunk a prím predikátum definiálása. A prím olyan szám, amely 1-en és önmagán kívül más számmal nem osztható. Szerencsére nem kell minden számot megvizsgálni, csak a szám négyzetgyökéig, mert ha addig nincs osztó, akkor utána sincs. Magyarul indítanunk kell egy ciklust kettőtől a szám négyzetgyökéig.

    prim(N):-
      nemoszt(N,2).
    
    nemoszt(N,X) :-
      N < X*X.
    nemoszt(N,X) :-
      0 =\= N mod X,
      X1 is X+1,
      nemoszt(N,X1).

    Ezek után nincs más dolgunk, mint indítani egy ciklust 2-től a szóban forgó szám feléig, s megvizsgálni, hogy a ciklusváltozó, valamint a maradék egyaránt prímszám, vagy sem. Utóbbi esetben venni kell a következő értéket.

    goldbach(N,P1,P2):-
      goldbach(N,2,P1,P2).
    goldbach(X,N,N,N1]):-
      prim(N),
      N1 is X-N,
      prim(N1),!.
    goldbach(X,N,P1,P2):-
      N1 is N+1,
      X >= N1+N1,
      goldbach(X,N1,P1,P2). 
  6. A maximális osztó megkapható, ha a minimális osztóval elosztjuk az eredeti számot. A minimális osztót pedig egy ciklussal határozhatjuk meg, amely 2-től az eredeti számig mehet.

    max_oszto(N,Oszto):-
      max_oszto(N,2,Oszto).
    max_oszto(N,N,1).
    max_oszto(N,I,Oszto):-
      0 is N mod I -> Oszto is N // I;
      I1 is I+1, max_oszto(N,I1,Oszto). 
  7. A feladat nem tartalmaz semmi különlegeset. Viszont egy-két jól megválasztott beépített predikátummal rövidíthetünk a programunkon. A sumlist/2 összegzi a listában szereplő számokat, a length/2 pedig a lista hosszát adja meg. E két predikátum segítségével meghatározhatjuk az átlagot, s az átlagtól függő összegzést egy másik predikátummal valósítjuk meg.

    index_szum(L,Szumma):-
      sumlist(L,LS),
      length(L,N),
      N>0,
      Atlag is LS/N,
      indexek(L,Atlag,1,0,Szumma).

    Ebben két segédváltozó lesz a segítségünk: az egyik az index értékét, míg másik a részösszeget tárolja.

    indexek([],_,_,Szumma,Szumma).
    indexek([X|Xs],Atlag,I,Acc,Szumma):-
      I1 is I+1,
      ( X>Atlag -> IAcc is I+Acc, indexek(Xs,Atlag,I1,IAcc,Szumma); 
      indexek(Xs,Atlag,I1,Acc,Szumma)).
  8. Az egyszerűbb végrehajtás érdekében bevezetünk egy segédváltozót, melybe a félkész megoldást tároljuk. Lényegében végigmegyünk a megadott szavak listáján, míg a feladat oroszlánrészét a beszur_ana/3 predikátum fogja elvégezni.

    anagramma(L,LL):-
      anagramma(L,[],LL).
    anagramma([],LL,LL).
    anagramma([X|Xs],L,LL):-
      beszur_ana(X,L,L2),
      anagramma(Xs,L2,LL).

    Az anagrammák listájában már listákat alkotnak az anagrammák. Az anagramma kapcsolat ekvivalencia reláció, így elég az új szót a lista fejével összehasonlítani. Két szó csak akkor anagramma, ha betűlistáik permutációi egymásnak. Ennek vizsgálatára pedig létezik beépített predikátum. Ha egy szó egyetlen csoportnak sem anagrammája, új csoportot kell kezdeni.

    beszur_ana(X,[],[[X]]).
    beszur_ana(X,[[Y|Ya]|Ys],Z):-
      permutation(X,Y) -> Z=[[X,Y|Ya]|Ys]; 
      beszur_ana(X,Ys,Z2),Z = [[Y|Ya]|Z2]. 
  9. A feladatban valójában intervallumok unióját kell kezelnünk. A megoldás során végig kell mennünk az intervallumok sorozatán, s ha találunk két illeszkedő intervallumot, akkor azokat össze kell olvasztanunk. Erre a beszur_u/4 predikátumot fogjuk használni. Végül pedig ki kell választani a uniók közül a leghosszabbat. Az uniók tárolására egy segédváltozót fogunk használni.

    leghosszabb(L,I):-
      leghosszabb(L,[],I).
    leghosszabb([],L,I):-
      valaszt(L,_,0,I).
    leghosszabb([X|Xs],L,I):-
      beszur_u(L,X,[],L2),
      leghosszabb(Xs,L2,I).

    Az uniók összeolvasztásakor már adott egy uniólista, egy új intervallum. Felhasználunk egy segédváltozót, melyben már az átnézett, és az új intervallumtól diszjunkt uniókat tároljuk. Ha a lista elfogyott, kész vagyunk, és a diszjunkt halmazhoz nyugodtan hozzávehetjük a beszúrandó szakaszt is. Általános esetben figyelni kell, van-e valami a két figyelembe vett szakasz metszetében, vagy sem. Ha nincs, akkor mehetünk tovább a listán. Ha van, akkor a két szakaszt össze kell olvasztani, amelyre a min/2 és max/2 beépített függvények remekül használhatóak, és tovább kell haladni a listán.

    beszur_u([],X,Acc,[X|Acc]).
    beszur_u([Ya:Yf|Ys],Xa:Xf,Acc,L):-
      ( Xf<Ya; Yf<Xa) -> beszur_u(Ys,Xa:Xf,[Ya:Yf|Acc],L); 
      Ka is min(Xa,Ya),
      Kf is max(Xf,Yf),
      beszur_u(Ys,Ka:Kf,Acc,L). 

    A leghosszabb szakasz kiválasztásához természetesen végig kell menni a listán, és folyamatosan összehasonlítani az eddig leghosszabb szakaszt az aktuálissal. Ha a hosszát tároljuk, akkor felesleges számításoktól kímélhetjük meg magunkat. Mire a lista végére érünk, a szakaszt is megtaláljuk.

    valaszt([],I,_,I).
    valaszt([Xa:Xf|Xs],Y,Hossz,I):-
      Xh is Xf-Xa,
     (Xh>Hossz -> valaszt(Xs,Xa:Xf,Xh,I);
      valaszt(Xs,Y,Hossz,I)). 
  10. Az intervallumok metszete hasonló feladat, mint az unió meghatározása (lásd az előbbi feladatot). Itt viszont nincs szükség egy lista tárolására, csak egyetlen intervalluméra, s azt kell a többi intervallumnak megfelelően csonkolni. Ha az intervallum teljesen elfogyna, akkor azt a fail/0 beépített predikátum segítségével jelezzük a felhasználónak.

    metszete([X|Xs],I):-
      metszete(Xs,X,I).
    metszete([],I,I).
    metszete([Xa:Xf|Xs],Ia:If,I):-
      (Xf<Ia;If<Xa) -> fail;
      Ma is max(Xa,Ia),
      Mf is min(Xf,If),
      metszete(Xs,Ma:Mf,I). 
  11. Mivel a szavak rendezésekor a szó hosszát kell felhasználni, a folytonos újraszámlálás helyett ezt feljegyezzük a szóval együtt. Megfelelő formátum esetén a beépített keysort/2 megoldja a rendezést. így nem marad más, mint leválasztani a hosszat a szóról.

    szolista(L,R):-
      hossza(L,[],NL),
      keysort(NL,RL),
      szepit(RL,[],R). 

    Egy ciklusban végigmegyünk a megadott listán, s minden szó esetén kiszámítjuk a hosszát.

    hossza([],L,L).
    hossza([X|Xs],Acc,L):-
      length(X,N),
      hossza(Xs,[N-X|Acc],L).

    A felesleg leszedésekor is végig kell nézni a teljes listát, s a segédváltozóba eltárolni az értékes részt. Természetesen ezt végül megfelelős sorrendbe kell tenni.

    szepit([],L,R):-
      reverse(L,R).
    szepit([_-X|Xs],Acc,L):-
      szepit(Xs,[X|Acc],L).
  12. Miután számunkra a sztring karakterlistának minősül, és ennek megfelelően dupla idézőjelet használunk és nem szimplát, így a sub_string(Szoveg,A,_,_,Minta),!megoldás számunkra nem megfelelő. Fel kell használnunk a jól ismert prefix/2 predikátumot.

    prefix([],_).
    prefix([X|Xs],[X|Ys]):-
      prefix(Xs,Ys).

    Ezek után nem marad más, mint a T szöveg kezdő karaktereit szépen egyesével leszedni, s megnézni, így már nem lesz-e prefixe a P minta. Ha igen, a folyamatosan növelt C számláló értékét kell visszaadni.

    elso_elofordulas(T,P,I):-
      elso_elofordulas(T,P,1,I).
    elso_elofordulas(T,P,C,I):-
      prefix(P,T) -> I is C; 
      T = [_|Tt],
      C1 is C+1,
      elso_elofordulas(Tt,P,C1,I). 
  13. A relatív prím reláció egyszerűen megfogalmazható a legnagyobb közös osztó fogalmával, s ezt a programunkban is felhasználhatjuk. Persze meg kell adni az utóbbi programját is (lnko/3).

    relativ_prim(A,B):-
      lnko(A,B,1),!.
    lnko(A,0,A).
    lnko(A,B,L) :- 
      B > 0,
      M is A mod B,
      lnko(B,M,L). 
  14. Mivel a megfelelő aritmetikai függvények implementálva lettek a Prolog nyelvben, így ezek használatával érünk leghamarabb célt. A kérdéses szám négyzetgyökének alsó és felső egészrészeinek négyzetét kell felhasználni, s kiszámolni a távolságát az eredeti számtól. A két érték közül a kisebbet kell visszaadni.

    negyzet_tavol(N,D):-
      D1 is N-floor(sqrt(N))^2,
      D2 is ceiling(sqrt(N))^2-N,
     ( D1<D2 -> D is D1 ; D is D2). 
  15. Megvalósítható az a megoldás, melyben a kijelölt számtól fel és le addig keresünk számokat, míg prímekre nem akadunk. Ehhez segítségre lehet a korábban megadott prímteszt predikátum. Ám mi most egy másik megoldást követünk. Első lépésben generáljuk a számtól kisebb páratlan prímszámokat (primlista/4), s e lista végén szerepelni fog D1, a számnál nem nagyobb prímszámok legnagyobbika. Ezután meghatározzuk a számnál nagyobb prímszámot (D2). Mivel a kov_prim/3 rutinunk csak páratlan számtól képes elindulni, egy kicsit játszadozni kell az indításával (N1). Végül ki kell választani a két szám közül a közelebbit.

    prim_tavol(N,D):- 
      N>2,!,
      primlista(N,[],3,L), 
      reverse(L,[D1|_]), 
      N1 is N-((N-1) mod 2), 
      kov_prim(N1,L,D2), 
     (N-D1<D2-N -> D is N-D1 ; D is D2-N). 
    prim_tavol(2,1).
    prim_tavol(1,2).

    A prímek listájának elkészítésekor már adott egy L lista, mely az Acc számlálónál kisebb prímeket tartalmazza. Ha ebben nincs osztója, akkor nincs is neki! Ha megtalált következő prím az N határnál nagyobb, megállhatunk, egyébként a megtalált P prímet fel kell venni a listába, és a következő P2 páratlan számmal folytatni a keresést.

    primlista(N,L,Acc,PL):-
      kov_prim(Acc,L,P),
      (P>N -> reverse(L,PL);
       P2 is P+2, primlista(N,[P|L],P2,PL)). 

    Ha az aktuális N szám nem osztható az L listában szereplő egyik számmal sem, akkor prímnek tekintjük, egyébként tovább lépünk a következő páratlanra.

    kov_prim(N,L,P):-
      nem_oszthato(L,N) -> P=N; 
      N2 is N+2,
      kov_prim(N2,L,P). 

    Azt pedig, hogy osztható-e a szám a lista valamelyik tagjával, csak úgy tudjuk meg, ha sorra végigmegyünk a lista elemein.

    nem_oszthato([],_).
    nem_oszthato([X|Xs],N):-
      0 =\= N mod X,
      nem_oszthato(Xs,N). 

Állapottér-reprezentáció problémái

  1. A feladatban a lista kezdőszeletét lehet megfordítani. Mind a szétvágást, mind az összeillesztést az append/3 predikátummal oldjuk meg. Mivel ez nem determinisztikus, így minden lépéslehetőséget generálni fog. A harmadik argumentumra az A* programja tart igényt.

    el(L1,L2,1):-
      append(Kezd,Veg,L1),
      reverse(Kezd,Dzek),
      append(Dzek,Veg,L2).

    Az 1-8 elemek rendezett permutációját meg lehet adni konstansként, de ha általánosabb megoldásra vágyunk, és a program tesztelése során rövidebb vagy később hosszabb listákkal is ki szeretnénk próbálni, akkor ez rendezett formában nem lesz más, mint egy növekvő sorozat kezdőszelete:

    cel(L):-
    append(L,_,[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]).      

    A megfelelően megválasztott heurisztika sokat gyorsíthat a megoldás megtalálásán. Mivel mi egy rendezett sorozatot szeretnénk, ha egymás mellett két nem egymás mellé kerülő szám áll, akkor azt nem szeretjük. Mivel egy lépéssel maximum csak egy ilyen esetet szüntethetünk meg, így ez a szám heurisztikának megfelelő.

    h(L,N):-
     elteres(L,0,N).
    elteres([_],N,N):-!.
    elteres([X,Y|Xs],Acc,N):-
      ((1 is abs(X-Y)) -> Acc1 is Acc; Acc1 is Acc+1),
      elteres([Y|Xs],Acc1,N).

    Az előadáson szereplő A* algoritmust változtatás nélkül alkalmazhatjuk.

    astar([[ _, G, Csucs|Ut]|_], [Csucs|Ut], G) :-
        cel(Csucs).
      astar([Ut |Utak], Megoldas, G) :-
        kibont(Ut, UjUtak),
        beszurmind(UjUtak, Utak, Utak2),
        astar(Utak2, Megoldas, G).

    Ugyanez igaz a nyílt csúcsok kibontásának algoritmusára is.

    kibont([_F, G, Csucs|Ut], UjUtak) :-
        findall([F2, G2, UjCsucs, Csucs|Ut],
                ( el(Csucs, UjCsucs, G3),
                  G2 is G+G3,
                  h(UjCsucs, H),
                  F2 is G2+H,
                  \+ memberchk(UjCsucs, [Csucs|Ut])
                ),
                UjUtak).

    Az Prolog rendszer indexelésének sajátosságai miatt a rendezett listák kezelésénél az előadáson ismertetett beszuregy/3 argumentumainak sorrendjét megváltoztattuk.

    beszurmind([],Utak,Utak).
      beszurmind([Ut|Utak],Utak2,Utak3) :-
        beszuregy(Utak2,Ut,Utak4),
        beszurmind(Utak,Utak4,Utak3).
      beszuregy( [], Ut, [ Ut ] ).
      beszuregy( [ [H2|Ut2]|Utak ], [H|Ut], MO  ) :-
        H =< H2 ->
          MO = [[H|Ut], [H2|Ut2]|Utak]
        ;
          beszuregy(Utak, [H|Ut],Utak2),
          MO= [[H2|Ut2]|Utak2].

    Ezek után ki is próbálhatjuk a programunkat: astar([[0,0,[4,5,6,3,7,1,8,2]]],L,Hossz).

    Ha nem jut eszünkbe semmi ötletes heurisztika, akkor próbálkozhatunk az iteratívan mélyülő kereséssel:

      ids(C, Ut) :-
        hatar(H),
        melyK(C, Ut, H).
      hatar(1).
      hatar(H) :-
        hatar(ElozoH),
        H is ElozoH + 1.

    Nem tettünk semmi mást, csak újra és újra meghívtuk az alábbi mélységi keresést.

      melyK(C, [C], _) :-
        cel(C).
      melyK(C, [C| Ut], Maxmely) :-
        Maxmely>0,
        el(C, C2,_),
        Max1 is Maxmely - 1,
        melyK(C2, Ut, Max1).
    

    A programunkat a következő paranccsal próbálhatjuk ki: ids(Perm,L).

  2. A sakktábla 64 mezője igen hosszú huszártúrát jelent. Ezért javasoljuk az 5x5-ös tábla, vagy a 3x3-as tábla (középső mező nélkül) használatát.

    Hagyományosan megtesszük a lépést, s megvizsgáljuk, hogy nem jutottunk-e ciklushoz. A hatékonyság növelése érdekében érdemes a már felhasznált mezőket törölni a táblából, és csak a megmaradt mezőkből válogatni.

    1. Generáljunk egy sakktáblát, ahol a mező indexei 1 és N közé esnek:

      tabla(L):-
        meret(N),
        findall(X/Y,
          (between(1,N,X), between(1,N,Y)),
          L).

      A huszár lóugrásban közlekedhet, és nem léphet le a tábláról.

      lep(X/Y,U/V):-
        (
         U is X+1,V is Y+2 ;
         U is X+1,V is Y-2 ;
         U is X+2,V is Y+1 ;
         U is X+2,V is Y-1 ;
         U is X-1,V is Y+2 ;
         U is X-1,V is Y-2 ;
         U is X-2,V is Y+1 ;
         U is X-2,V is Y-1
        ),
        meret(N),
        U =< N, V =< N, U>0, V> 0.

      Mivel az összes mezőt be kell járni, a nem meglátogatott mezők elfogyása esetén jutunk megoldáshoz. Ahogy rálépünk egy mezőre, egyből töröljük is a select/3 predikátum segítségével.

      mely([],C,L,[C|L]):-!.
      mely([X|Xs],C,Acc,L):-
        lep(C,C1),
        select(C1,[X|Xs],XXs),
        mely(XXs,C1,[C|Acc],L).

      Mindezek után már ki is próbálhatjuk a programot

      meret(3).

      A 3x3-as táblának törölni kell a közepét, s utána indítani a keresést: tabla(L), select(2/2,L,[C|L1]), mely(L1,C,[],Mo).

    2. Több száz éve ismert az a heurisztika, amely szerint mindig arra a szomszédra kell lépni a huszárral, amelynek a legkevesebb még meg nem látogatott szomszédja van.

      Minden lépés után újra és újraszámolni, hogy hány szomszédja maradt minden egyes mezőnek, igen munkaigényes feladat, ezért meghatározzuk a kezdeti szomszédszámot, s ezt csökkentjük minden egyes mező törlésekor. Ehhez lássuk a szomszédok számát!

      A sarkoknak csak két szomszédja van:

      szomszed(X,Y,2):-
      meret(N),
        (X=1;X=N),
        (Y=1;Y=N),!.

      A sarkok melletti mezőknél ez már háromra nő:

      szomszed(X,Y,3):-
        meret(N),
        N1 is N-1,
        (  ((X=1;X=N),(Y=2;Y=N1));
           ((Y=1;Y=N),(X=2;X=N1))),!.

      Az eddig nem említett szélső mezőknek 4 szomszédja van:

      szomszed(X,Y,4):-
        meret(N),
        N1 is N-1,
        ( ((X=1;X=N),Y>2,Y<N1);
          ((Y=1;Y=N),X>2,X<N1)),!.

      Ugyanez igaz a sarkok átlós szomszédjaira is:

      szomszed(X,Y,4):-
        meret(N),
        N1 is N-1,
        (X=2;X=N1),
        (Y=2;Y=N1),!.

      A szélső sor melletti mezőknél csak 2 szomszéd lógna ki a tábláról:

      szomszed(X,Y,6):-
        meret(N),
        N1 is N-1,
        (  ((X=2;X=N1),Y>2,Y<N1);
           ((Y=2;Y=N1),X>2,X<N1)),!.

      A belső mezőknél viszont már mind a nyolc szomszéd létezik:

      szomszed(X,Y,8):-
        meret(N),
        N1 is N-1,
        Y>=2,Y=<N1,
        X>2, X<N1,!.

      Ezek után generálhatjuk a sakktáblát leíró listát, ahol minden egyes mezőt a szomszédok számával, valamint az indexeivel írjuk le.

      tabla(L):-
        meret(N),
        findall(S/X/Y,(
          between(1,N,X),
          between(1,N,Y),
          szomszed(X,Y,S)
          ),L).

      Az előző megoldásban generáltunk egy lépést, s megnéztük, hogy azon a helyen van-e még üres mező. Most csináljuk fordítva! Kiválasztunk egy szabad mezőt (select/3), s megnézzük hogy eljuthatunk-e oda egy lépéssel (mellett/2). Ha nem, akkor másik mezőt keresünk.

      huszarlep(_,[],L,L).
      huszarlep(X/Y,L,Acc,Mo):-
        select(_/U/V,L,L0),
        mellett(X/Y,U/V),
        frissit(L0,U,V,[],L2),
        huszarlep(U/V,L2,[U/V|Acc],Mo).

      A két mező lóugrásnyi távolságát egyszerű ellenőrizni, a korábbi lep/2 rutint egyszerűsítjük. Ott erre az egyszerűsítésre nem volt lehetőség, mert az X/Y pároshoz kellett egy U/V párost generálni. Most ez a két változó már tartalmaz értéket, elég tesztelni a reláció meglétét:

      mellett(X/Y,U/V):-
        (   1 =:= abs(X-U), 2=:= abs(Y-V);
            2 =:= abs(X-U), 1=:= abs(Y-V)).

      Miután a select/3 törölte az aktuális mezőt, a szomszédjainak szomszédsági száma csökkent. Ezeket az értékeket a frissit/5 predikátum aktualizálja.

      frissit([],_,_,L,L).
      frissit([N/U/V|Ms],X,Y,Acc,L):-
        (   mellett(X/Y,U/V) ->
             N1 is N-1
            ;
             N1 =N),
        beszur(Acc,N1/U/V,Acc1),
        frissit(Ms,X,Y,Acc1,L).

      Mindenről volt már szó, csak még a heurisztikáról nem. A legkevesebb szomszédú mezőket kell előre venni a heurisztika szerint. Ezt oldjuk meg, hogy a szomszédok száma szerint növekvő sorrendben tároljuk a mezők listáját. A select/3 pedig ennek a listának az elejéről dolgozik. Ha véletlenül ez a heurisztika nem működik, akkor sincs nagy baj, mert ez nem determinisztikus predikátum, így backtrack esetén a nagyobb szomszédsági számmal szereplő mezőket is kipróbálja.

      beszur([],X,[X]).
      beszur([M/U/V|L],N/X/Y,L1):-
        N=<M -> 
          L1=[N/X/Y,M/U/V|L]
        ;
          beszur(L,N/X/Y,L2), 
          L1=[M/U/V|L2].

      A megoldás megadásához most is generálnunk kell a táblát, kiválasztani a kiinduló mezőt, amit törlünk a tábláról, és indulhat a lépéssorozat keresése: tabla2(T), select(2/1/1,T,T1), frissit(T1,1,1,[],L1), huszarlep(1/1,L1,[],L).

  3. A megoldáshoz nem szükséges a következő gondolatmenet, de a program tesztelésekor jól jön: ha almát és körtét adunk barackért, akkor az almák (és a körték) száma eggyel csökken, a barackok száma kettővel nő. Ha almákat kapunk barackért és körtéért, akkor az almák száma kettővel nő, a barackok száma eggyel csökken. Összesítve az A-C szám hármas maradéka nem változik a cserék során, s hasonló igaz a B-C számra is, amint A-B számra is. Ha a három szám közül kettő nullára változik (ez a célállapot), akkor a megfelelő különbség maradéka is 0. Magyarul, ha a három szám hármas maradékai különbözőek, nincs megoldása a feladatnak.

    Ezek után formalizálnunk kell a cseréket:

    el(A/B/C,D/E/F,1):-
      A>0,B>0,
      D is A-1,E is B-1,F is C+2.
    el(C/A/B,F/D/E,1):-
      A>0,B>0,
      D is A-1,E is B-1,F is C+2.
    el(B/C/A,E/F/D,1):-
      A>0,B>0,
      D is A-1,E is B-1,F is C+2.

    Valamint meg kell adnunk, hogy mi tekinthető célállapotnak:

    cel(A/B/C):-
      0=A, 0=B;
      0=A, 0=C;
      0=B, 0=C.

    Legegyszerűbb a mélységi keresés alkalmazása:

    cserel(A,B,C,L):-
      ids(A/B/C,L).

    Javasoljuk az ambiciózusabb olvasóknak, hogy heurisztikaként vizsgálják meg, hogy mely mennyiségek nullázhatóak, valamint melyik célállapot van a legközelebb és akár egy hegymászó kereséssel megkaphatják a megoldást!

  4. Ebben az esetben is elegendő megadni a lehetséges lépéseket és a célfeltételt, és máris alkalmazható a mélységi keresés. Ha a számok hosszának a különbségét tekintjük heurisztikaként, akkor az A* módszer gyorsan megoldást ad. Ám mi egy olyan megoldást adunk meg, mellyel lineáris bonyolultsággal kapható meg a megoldás.

    szamol(N,L):-
      negyes(N,[],L).
    negyes(N,Acc,L):-
       N=4 ->
         L=Acc
        ;
         N1 is N//10, N2 is 2*N,
         (
          0 =:= N mod 10 ->
           negyes(N1,[b|Acc],L)
          ;
           4 =:= N mod 10 ->
            negyes(N1,[a|Acc],L)
           ;
            negyes(N2,[c|Acc],L)
         ).

    Nem teszünk más, mint a 0-ra, vagy a 4-re végződő számok esetén töröljük az utolsó számjegyet, s minden más számot megduplázunk. Mivel a célból indulunk vissza a 4-ig, így a lépéseinket is meg kellett fordítanunk.

  5. A legegyszerűbb megoldás itt is az élek, valamint a cél megadása, és mélységi keresés alkalmazása. Az élek a szabályok egyszerű átírása, valamint hozzávettük, hogy a számok pozitívak.

    el(X/Y/Z,A/B/C,1):-
      A is X-2*Y+2*Z,
      B is 2*X-Y+2*Z,
      C is 2*X-2*Y+3*Z,
      A>0,B>0,C>0.
    el(X/Y/Z,A/B/C,1):-
      A is X+2*Y+2*Z,
      B is 2*X+Y+2*Z,
      C is 2*X+2*Y+3*Z,
      A>0,B>0,C>0.
    el(X/Y/Z,A/B/C,1):-
      A is -X+2*Y+2*Z,
      B is -2*X+Y+2*Z,
      C is -2*X+2*Y+3*Z,
      A>0,B>0,C>0.

    Egyetlen gond van ezekkel a lépésekkel: időnként a generált számhármast nem növekvő sorrendben kapjuk meg. Ezért rendezni kell a számhármast mielőtt tesztelnénk a célfeltételt:

    melyK(A/B/C, [A/B/C], _) :- 
        sort([A,B,C],L), cel(L).

    A korlátos mélységi keresés programjának további része változatlan.

    Ahelyett, hogy a célt argumentumként magunkkal cipeljük a feladat megoldása során, a memóriában tároljuk. Hogy ezt a Prolog rendszerünk ne vegye zokon, érdemes a tudomására hozni.

    :- dynamic(cel/3).

    Ezután már nincs más dolgunk, mint a kezdetben megadott számhármast növekvő sorrendben tároljuk, végrehajtjuk a keresést, és végül töröljük a tárolt célfeltételt.

    szamol(A,B,C,L):-
      sort([A,B,C],P),
      assert(cel(P)),
      ids(3/4/5,L),
       epretract(cel(P)).
  6. Egy számpár esetén igen nehéz egy megfelelő heurisztikát találni. Mivel a megoldás rendszerint elég hosszú lépéslista, az általános kereső algoritmusok lassan szolgáltatnak eredményt. Ha az utolsó lépéstől eltekintünk, az euklideszi algoritmus egy lassabb verziójának lépéseit kapjuk vissza. Innen jöhet az az ötlet, hogy a legnagyobb közös osztó meghatározására vezessük vissza a feladatot. Ha mindkét számpár esetén ez az érték megegyezik, akkor van megoldás, és az ide vezető utak kombinációja fogja ezt megadni.

    szamol(X,Y,U,V,L):-
      lnko(X,Y,[],[N/0|Ns]),
      lnko(U,V,[],[M/0|Ms]),!,
      N=M,
      reverse([N/0|Ns],R),
      append(R,Ms,L).

    A nagyobb számból ki kell vonnunk a kisebbet, mindaddig, amig a kisebb szám le nem nullázódik. Így kapható meg a legnagyobb közös osztó:

    lnko(A,B,Acc,L):-
      Acc1 = [A/B|Acc],
      (  A<B ->
          lnko(B,A,Acc1,L)
         ;
          B=0 ->
           L=Acc1
          ;
           A1 is A-B,
           lnko(A1,B,Acc1,L)
      ).
  7. A megoldás megadásában a lépések meghatározása tart legtovább. Az alábbi tábla jelölését vesszük alapul:

    Pegged tábla mezőinek elnevezése

    Jöjjenek a balra le lépések:

    el([1,1,C,0,E,F,G,H,I,J,K,L,M,N,O],[0,0,C,1,E,F,G,H,I,J,K,L,M,N,O]).
    el([A,1,C,1,E,F,0,H,I,J,K,L,M,N,O],[A,0,C,0,E,F,1,H,I,J,K,L,M,N,O]).
    el([A,B,C,1,E,F,1,H,I,J,0,L,M,N,O],[A,B,C,0,E,F,0,H,I,J,1,L,M,N,O]).
    el([A,B,1,D,1,F,G,0,I,J,K,L,M,N,O],[A,B,0,D,0,F,G,1,I,J,K,L,M,N,O]).
    el([A,B,C,D,1,F,G,1,I,J,K,0,M,N,O],[A,B,C,D,0,F,G,0,I,J,K,1,M,N,O]).
    el([A,B,C,D,E,1,G,H,1,J,K,L,0,N,O],[A,B,C,D,E,0,G,H,0,J,K,L,1,N,O]).

    Jobbra fel lépések:

    el([0,1,C,1,E,F,G,H,I,J,K,L,M,N,O],[1,0,C,0,E,F,G,H,I,J,K,L,M,N,O]).
    el([A,0,C,1,E,F,1,H,I,J,K,L,M,N,O],[A,1,C,0,E,F,0,H,I,J,K,L,M,N,O]).
    el([A,B,C,0,E,F,1,H,I,J,1,L,M,N,O],[A,B,C,1,E,F,0,H,I,J,0,L,M,N,O]).
    el([A,B,0,D,1,F,G,1,I,J,K,L,M,N,O],[A,B,1,D,0,F,G,0,I,J,K,L,M,N,O]).
    el([A,B,C,D,0,F,G,1,I,J,K,1,M,N,O],[A,B,C,D,1,F,G,0,I,J,K,0,M,N,O]).
    el([A,B,C,D,E,0,G,H,1,J,K,L,1,N,O],[A,B,C,D,E,1,G,H,0,J,K,L,0,N,O]).

    Jobbra le lépések:

    el([1,B,1,D,E,0,G,H,I,J,K,L,M,N,O],[0,B,0,D,E,1,G,H,I,J,K,L,M,N,O]).
    el([A,B,1,D,E,1,G,H,I,0,K,L,M,N,O],[A,B,0,D,E,0,G,H,I,1,K,L,M,N,O]).
    el([A,B,C,D,E,1,G,H,I,1,K,L,M,N,0],[A,B,C,D,E,0,G,H,I,0,K,L,M,N,1]).
    el([A,1,C,D,1,F,G,H,0,J,K,L,M,N,O],[A,0,C,D,0,F,G,H,1,J,K,L,M,N,O]).
    el([A,B,C,D,1,F,G,H,1,J,K,L,M,0,O],[A,B,C,D,0,F,G,H,0,J,K,L,M,1,O]).
    el([A,B,C,1,E,F,G,1,I,J,K,L,0,N,O],[A,B,C,0,E,F,G,0,I,J,K,L,1,N,O]).

    Balra fel lépések

    el([0,B,1,D,E,1,G,H,I,J,K,L,M,N,O],[1,B,0,D,E,0,G,H,I,J,K,L,M,N,O]).
    el([A,B,0,D,E,1,G,H,I,1,K,L,M,N,O],[A,B,1,D,E,0,G,H,I,0,K,L,M,N,O]).
    el([A,B,C,D,E,0,G,H,I,1,K,L,M,N,1],[A,B,C,D,E,1,G,H,I,0,K,L,M,N,0]).
    el([A,0,C,D,1,F,G,H,1,J,K,L,M,N,O],[A,1,C,D,0,F,G,H,0,J,K,L,M,N,O]).
    el([A,B,C,D,0,F,G,H,1,J,K,L,M,1,O],[A,B,C,D,1,F,G,H,0,J,K,L,M,0,O]).
    el([A,B,C,0,E,F,G,1,I,J,K,L,1,N,O],[A,B,C,1,E,F,G,0,I,J,K,L,0,N,O]).

    Jobbra lépések:

    el([A,B,C,1,1,0,G,H,I,J,K,L,M,N,O],[A,B,C,0,0,1,G,H,I,J,K,L,M,N,O]).
    el([A,B,C,D,E,F,1,1,0,J,K,L,M,N,O],[A,B,C,D,E,F,0,0,1,J,K,L,M,N,O]).
    el([A,B,C,D,E,F,G,1,1,0,K,L,M,N,O],[A,B,C,D,E,F,G,0,0,1,K,L,M,N,O]).
    el([A,B,C,D,E,F,G,H,I,J,1,1,0,N,O],[A,B,C,D,E,F,G,H,I,J,0,0,1,N,O]).
    el([A,B,C,D,E,F,G,H,I,J,K,1,1,0,O],[A,B,C,D,E,F,G,H,I,J,K,0,0,1,O]).
    el([A,B,C,D,E,F,G,H,I,J,K,L,1,1,0],[A,B,C,D,E,F,G,H,I,J,K,L,0,0,1]).

    Balra lépések:

    el([A,B,C,0,1,1,G,H,I,J,K,L,M,N,O],[A,B,C,1,0,0,G,H,I,J,K,L,M,N,O]).
    el([A,B,C,D,E,F,0,1,1,J,K,L,M,N,O],[A,B,C,D,E,F,1,0,0,J,K,L,M,N,O]).
    el([A,B,C,D,E,F,G,0,1,1,K,L,M,N,O],[A,B,C,D,E,F,G,1,0,0,K,L,M,N,O]).
    el([A,B,C,D,E,F,G,H,I,J,0,1,1,N,O],[A,B,C,D,E,F,G,H,I,J,1,0,0,N,O]).
    el([A,B,C,D,E,F,G,H,I,J,K,0,1,1,O],[A,B,C,D,E,F,G,H,I,J,K,1,0,0,O]).
    el([A,B,C,D,E,F,G,H,I,J,K,L,0,1,1],[A,B,C,D,E,F,G,H,I,J,K,L,1,0,0]).

    Egy állás akkor megoldás, ha már csak egy figura van a táblán:

    cel(L):-sumlist(L,1).

    Mivel a feladat kitűzése már megad egy korlátot, tehát felesleges korlátos mélységi keresést alkalmazni.

    mely(C, [C]) :-
      cel(C).
    mely(C, [C|Ut]) :-
      el(C, C2),
      mely(C2, Ut).

    Ezután nem kell mást csinálnunk, mint meghívni egy mélységi keresést, és a programot tesztelhetjük is: pegged([1,1,1,1,1,1,1,1,1,1,1,1,0,1,1],L).

    pegged(C,L):-
           mely(C,L).
  8. A listában felsorolt számokból a felhasználtakat folyamatosan kitöröljük a select/3 segítségével. Ha egy sorban, oszlopban vagy átlóban adott két szám, akkor ebből a harmadikat is meghatározhatjuk, miután a sorösszeg, oszlopösszeg és átlóösszeg a megadott számok összegének harmada. Az ábrán látható jelöléseket követve, ha megadjuk A és B értékét, akkor ebből C meghatározható. Ha D értékét megadjuk, akkor sorra G, E, F, H valamint I is megkapható.

    Bűvös négyzet mezői

    A Prolog rendszer megteszi azt a szívességet, ha egy adott számot nem sikerül törölni a listából (miután az ott nem szerepel), akkor visszalép az utolsó elágazási pontig (A, B vagy D meghatározásáig) és más értékekkel próbálkozik.

    buvos(L,T):-
      sumlist(L,N),
      0 =:= N mod 3,
      Sor is N//3,
      select(A,L ,L1),
      select(B,L1,L2),
      C is Sor-A-B, select(C,L2,L3),
      select(D,L3,L4),
      G is Sor-A-D, select(G,L4,L5),
      E is Sor-C-G, select(E,L5,L6),
      F is Sor-E-D, select(F,L6,L7),
      H is Sor-B-E, select(H,L7,L8),
      I is Sor-A-E, L8 = [I],
      T=[[A,B,C],[D,E,F],[G,H,I]].

Nyelvtani feladatok

Az feladatok jelentős részénél feltételezzük, hogy sztring formában érkezik az input. Az alábbi segédpredikátum ezt konstansok listájává alakítja úgy, hogy a két, vagy három betűből álló mássalhangzókat felismeri. Sajnos a házsor alakú szavakban is kettős betűt tételez fel. Ez ellen egy írásjel beszúrásával lehet védekezni: ház!sor.

A dupla kettős betűk a következők:

karakter("ccs",[cs,cs]).
karakter("ddzs",[dzs,dzs]).
karakter("ddz",[dz,dz]).
karakter("ggy",[gy,gy]).
karakter("lly",[ly,ly]).
karakter("nny",[ny,ny]).
karakter("ssz",[sz,sz]).
karakter("tty",[ty,ty]).
karakter("zzs",[zs,zs]).

A kettős betűink a következőek:

karakter("cs",cs).
karakter("dzs",dzs).
karakter("dz",dz).
karakter("gy",gy).
karakter("ly",ly).
karakter("ny",ny).
karakter("sz",sz).
karakter("ty",ty).
karakter("zs",zs).

A többi karakternél már nem kell ennyire óvatosnak lenni

karakter("a",a).
karakter("á",a1).
karakter("b",b).
karakter("c",c).
karakter("d",d).
karakter("e",e).
karakter("é",e1).
karakter("f",f).
karakter("g",g).
karakter("h",h).
karakter("i",i).
karakter("í",i1).
karakter("j",j).
karakter("k",k).
karakter("l",l).
karakter("m",m).
karakter("n",n).
karakter("o",o).
karakter("ó",o1).
karakter("ö",o2).
karakter("ő",o3).
karakter("p",p).
karakter("q",q).
karakter("r",r).
karakter("s",s).
karakter("t",t).
karakter("u",u).
karakter("ú",u1).
karakter("ü",u2).
karakter("ű",u3).
karakter("v",v).
karakter("w",w).
karakter("x",x).
karakter("y",y).
karakter("z",z).

A szóközt, s az egyéb írásjeleket pedig töröljük:

karakter([X],[]):-
  memberchk(X," .?!,:/").

Ezek után már jöhet a konvertálást végző predikátum:

kodol([],L,R):-reverse(L,R).
kodol([Y|L],Acc,Xs):-
 karakter([Y|P],X),
 append(P,L1,L),!,
 (is_list(X) -> 
   append(X,Acc,Acc1)
  ; 
   Acc1=[X|Acc]),
   kodol(L1,Acc1,Xs).

A feladatokban szükség lesz a magánhangzók és mássalhangzók felismerésére, ezért megadjuk ezeket a predikátumokat is:

rovid_mgh(X):-
  memberchk(X,[a,e,i,o,o2,u,u2]).
hosszu_mgh(X):-
  memberchk(X,[a1,e1,i1,o1,o3,u1,u3]).
massalhangzo(X):-
  memberchk(X,[b,c,d,dz,dzs,f,g,gy,h,j,k,l,ly,m,n,p,q,s,sz,t,ty,v,w,x,y,z,zs]).
maganhangzo(X):-
  rovid_mgh(X);hosszu_mgh(X).
  1. A hexameter felismerésénél szükség van a hosszú és rövid szótagok megkülönböztetésére. Ehhez megadjuk a szótag magánhangzóját, valamint az utána előforduló, egymást követő mássalhangzók listáját. Ha a magánhangzó hosszú, akkor a szótag is. Rövid magánhangzó esetén csak akkor rövid a szótag, ha maximum egy mássalhangzó követi.

    rh(Acc,Mgh,T):-
      hosszu_mgh(Mgh) -> 
        T=h
      ;
        length(Acc,N),
        N>1 -> T=h; T=r.

    Ezek után következhet a szótagok osztályozása. Az első paraméter tartalmazza a sor betűinek listáját, Acc1 a mássalhangzók listáját, Acc2 a felismert szótagok listáját, Mgh pedig az utolsó előtti magánhangzót. Ha a sor kiürült, már csak az utolsó szótagot kell osztályozni.

    szotagol([],Acc1,Acc2,Mgh,Szotag):-
      rh(Acc1,Mgh,T),
      reverse([T|Acc2],Szotag).

    Ha az első betű magánhangzó vagy szóköz, és nem az első a szóban, akkor meghatározzuk az ezt megelőző szótag típusát. Ha viszont az első magánhangzó, akkor eltároljuk mint utolsó mássalhangzót. Abban az esetben, ha mássalhangzó az aktuális betű, akkor tároljuk a mássalhangzók listájában.

    szotagol([X|Xs],Acc1,Acc2,Mgh,Szotag):-
      (maganhangzo(X); X='-') ->
        (   maganhangzo(Mgh)->
         rh(Acc1,Mgh,T),
         szotagol(Xs,[],[T|Acc2],X,Szotag)
       ;
         szotagol(Xs,[],Acc2,X,Szotag))
     ;
       szotagol(Xs,[X|Acc1],Acc2,Mgh,Szotag).

    Mostantól már a verstani ismeretekre van szükség, a hexameterben található verslábakat kell megadnunk.

    spondeus --> [h,h].
    daktilus --> [h,r,r].
    trocheus --> [h,r].

    Az egyszerűség kedvéért az első kettőnek adunk egy összefoglaló nevet:

    mora4 --> daktilus.
    mora4 --> spondeus.

    Az utolsó versláb akár csonka is lehet:

    zaro --> trocheus.
    zaro --> spondeus.

    Nincs más hátra, mint a hexameter megadása, valamint a kipróbálása:

    hexameter --> mora4, mora4, mora4, mora4, daktilus, zaro.
    teszth:-
      kodol("férfiuról szólj nékem, múzsa, ki sokfele bolygott",[],L),
      szotagol(L,[],[],-,V),
      hexameter(V,[]).
  2. A pentameternél az előző megoldásban definiáltakat kell felhasználni, és alig egy-két predikátummal kibővíteni.

    pentameter --> daktilus, daktilus, csonka, daktilus, daktilus, csonka.
    csonka --> [h].
    csonka --> [r].
  3. Az anakreoni versekhez is az előbb definiált predikátumokra van szükség, valamint a következőkre:

    jambus --> [r,h].
    anakreoni7 --> jambus, jambus, jambus, csonka.
  4. Továbbra a korábban definiált predikátumokra támaszkodunk. Definiálnunk a kellő sormértékeket, bár sokan az első mindenféle variációit használják.

    szaffoiSor --> trocheus, trocheus, daktilus, trocheus, trocheus.
    adonisziSor --> daktilus, trocheus.

    Most már jöhet a versszak is:

    szaffoiStrofa([A,B,C,D]):-
      kodol(A,[],LA),szotagol(LA,[],[],-,VA),szaffoiSor(VA,[]),
      kodol(B,[],LB),szotagol(LB,[],[],-,VB),szaffoiSor(VB,[]),
      kodol(C,[],LC),szotagol(LC,[],[],-,VC),szaffoiSor(VC,[]),
      kodol(D,[],LD),szotagol(LD,[],[],-,VD),adonisziSor(VD,[]).
  5. Az a feladatunk, hogy eldöntsük, milyen rímképlete van a versnek: páros rím: [1,1,2,2,3,3,...], keresztrím: [1,2,1,2,3,4,3,4], stb. Sajnos a rím igen nehezen írható le. Rendszerint a hasonló hangzás is elég, így rím lesz az -ot/-ott, -gany/-gan... Azért, hogy a hasonló hangzást ne kelljen figyelembe venni, azt mondjuk, ha az utolsó magánhangzó és az azt követő betűk megegyeznek, akkor rímelnek a sorok, egyébként nem.

    rim(L,R):- 
      kodol(L,[],Bs),
      reverse(Bs,RL),
      vege(RL,[],R).

    Az utolsó magánhangzóig kell leválasztani a betűket:

    vege([X|Xs],Acc,R):-
      maganhangzo(X) -> R=[X|Acc]; vege(Xs,[X|Acc],R).

    Ezt a rim predikátumot kell minden sorra alkalmaznunk:

    rimkeplet([],Acc,Rs):-
      reverse(Acc,Cca),
      szamol(Cca,[],[],Rs).
    rimkeplet([L|Ls],Acc,Rs):-
      rim(L,R),
      rimkeplet(Ls,[R|Acc],Rs).

    Ha már megkaptuk a sorok végződéseinek a listáját, akkor abban kell azonos elemeket keresni. Ezt úgy tesszük meg, hogy minden egyes elemhez hozzárendeljük, hogy mikor találkoztunk vele először, s ezt az információt egy listában tároljuk. Ha ismerős elem kerül elénk, akkor az azonosító száma a listából kiolvasható, egyébként újként be kell szúrni a listába.

    szamol([],_,Acc,Rs):-
      reverse(Acc,Rs).
    szamol([X|Xs],L,Acc,Rs):-
      member(N/X,L) -> 
        szamol(Xs,L,[N|Acc],Rs)
      ;
        length(L,N),
        N1 is N+1,
        szamol(Xs,[N1/X|L],[N1|Acc],Rs).
  6. Haiku verseknél a szótagszám jelent egy megkötést. Mivel egy szótagban pontosan egy magánhangzó van, így ezeket kell megszámolnunk:

    szotagszam([],Acc,Acc).
    szotagszam([X|Xs],Acc,Db):-
      (maganhangzo(X) ->
        Acc1 is Acc+1
       ;
        Acc1 = Acc),
       szotagszam(Xs,Acc1,Db).

    Ezek után már csak az 5-7-5 szerkezetet kell garantálni:

    haiku([A,B,C]):-
      kodol(A,[],LA),szotagszam(LA,0,5),
      kodol(B,[],LB),szotagszam(LB,0,7),
      kodol(C,[],LC),szotagszam(LC,0,5),!.
  7. A szakirodalomban található megoldások a következő variánsai. Mivel a generáló nyelvtan környezetfüggő, plusz információ kell az egyes betűk számáról. Ezt egy lista segítségével tartjuk nyilván.

    1. Az első a betű leírásával létre is hozzuk ezt a listát.

      anbncn --> [a], szamol([]).

      Amint leírunk egy újabb a betűt, a lista hosszát is növeljük.

      szamol(X) --> [a], szamol([x|X]).

      Viszont nekikezdhetünk a b betűknek, s ekkor pont ugyanannyi b betűt kell leírni, amennyi a is volt, s hasonló igaz a c-kre is.

      szamol(X) --> szamolb(X), szamolc(X).

      Tehát ha egy b betűt leírtunk, a lista hossza eggyel csökken. Ha pedig a lista kiürült, akkor az utolsó b került sorra.

      szamolb([_|X]) --> [b], szamolb(X).
      szamolb([]) --> [b].

      Ugyanez a helyzet a c-kkel is.

      szamolc([_|X]) --> [c], szamolc(X).
      szamolc([]) --> [c].
    2. Az előbbi megoldás remekül fel is ismeri az kívánt szavakat; és jelzi, ha valami nem ilyen alakú szó. Viszont generáláskor végtelen ciklusba fut. Így érdemes egy H korlátot megadni: milyen nagy lehet az n értéke. Ha már számolunk, a leírt a betűk számát is számmal tartjuk nyilván.

      anbncn(H) --> [a], {H1 is H-1}, szamola(1,H1).

      Ha a korlátot nem léptük túl (nem csökkent nullára a számláló), akkor újabb a betűt írhatunk le, illetve bármikor átválthatunk egy másik betűre.

      szamola(N,H) --> [a], {N1 is N+1}, {H1 is H-1}, {H1>1}, szamola(N1,H1).
      szamola(N,_) --> szamolx(N,b), szamolx(N,c).

      Átváltáskor csak a leírt a betűk számára kell figyelni, s mivel annyira hasonlít a b és c számoló predikátum, akár össze is vonhatjuk.

      szamolx(N,X) --> [X], {N1 is N-1}, {N1>0}, szamolx(N1,X).
      szamolx(1,X) --> [X].
  8. A megoldás igen hasonlít az előzőhöz, viszont nem csak akkor lehet abbahagyni a b és c betűk írását, ha eljutottunk nulláig a számlálóval, hanem már korábban is. A korábban leírt a betűk száma felső korlátot jelent a b betűk számára, s a leírt b betűk száma korlátot jelent a c betűk számára.

    A kezdés hasonlóan történik, mint korábban:

    anbmck(H) --> [a], {H1 is H-1}, szamola2(1,H1).

    Amikor áttérünk a b betűkre, akkor N szigorú felső korlátot fog jelenteni.

    szamola2(N,H) --> {H>0}, [a], {N1 is N+1}, {H1 is H-1}, szamola2(N1,H1).
    szamola2(N,_) --> {N1 is N-1}, szamolb2(0,N1).

    A b betűk számolása nagyon hasonlít az a betűk számolására:

    szamolb2(N,H) --> {H>0}, [b], {N1 is N+1}, {H1 is H-1}, szamolb2(N1,H1).
    szamolb2(N,_) --> {N1 is N-1}, szamolc2(N1).

    A c betűk számát már nem kell figyelni, csak a megadott H korlátot ne lépjük túl!

    szamolc2(H) --> {H>1}, [c], {H1 is H-1}, szamolc2(H1).
    szamolc2(H) --> {H>0}, [c].
  9. Mint az előbb, most is bevezetünk egy korlátot, hogy a generálás ne okozzon végtelen ciklust. Most a korlát a betűk számára vonatkozik. Annak vizsgálatára, hogy mennyire tértünk el a betűk arányától, egy változó jelzi. Természetesen ennek kezdeti értéke 0.

    a2nbn(H) --> dupla(0,H).

    Ha egy a betűt írunk le, akkor mínusz irányban térünk el.

    dupla(N,H) --> {H>0}, [a], {N1 is N-1}, {H1 is H-1}, dupla(N1,H1).

    Ha egy b betűt írunk le, akkor kettővel pozitív irányba mozdulunk.

    dupla(N,H) --> {H>0}, [b], {N2 is N+2}, {H1 is H-1}, dupla(N2,H1).

    Csak kiegyensúlyozott esetben állhatunk meg.

    dupla(0,_) --> [].
  10. Ahhoz hogy egy betű ne szerepeljen duplán, elegendő észben tartani, hogy melyik volt utoljára. Ezért az általunk megadott korlátot ezzel a változóval kell kiegészíteni.

    nemdupla(H) --> utolso(x,H).

    A megadott ábécét beépítjük a programba:

    betu(a). betu(b). betu(c).

    Már csak az utolsó betű figyelésére, valamint a korlátra kell figyelni. Ha véget ér a szó, akkor egyikre sem:

    utolso(X,H) --> {H>0}, {betu(Y)}, [Y], {X\=Y}, {H1 is H-1}, utolso(Y,H1).
    utolso(_,_) --> [].
  11. Ebben az esetben arra kell figyelni, hogy a leírt a-k száma meghaladja a b-k számát, azaz a különbségük legyen pozitív. Továbbá most is kezelünk egy korlátot, amelynél hosszabb inputot nem fogadunk el. Kezdetben az eltérés 0:

    tobb(H) --> tobb(H,0).

    Bármelyik pillanatban megállhatunk:

    tobb(_,_) --> [].

    Egy a betű leírása csak javít az egyenlegen, így csak a hosszra kell odafigyelni.

    tobb(H,N) --> {H>0}, [a], {N1 is N+1}, {H1 is H-1}, tobb(H1,N1).

    A b betű esetén arra is figyelni kell, hogy az egyenleg ne menjen negatívba.

    tobb(H,N) --> {H>0}, {N>0}, [b], {N1 is N-1}, {H1 is H-1}, tobb(H1,N1).

CLPFD

[Megjegyzés]Megjegyzés

A megoldásaink rendszerint csak két predikátumból állnak. Viszont az egyikük igen hosszú, ezért a programlistát sorokra tördeljük, és közzéjük tesszük a magyarázatainkat. Példáinkban az SWI-Prolog részét jelentő könyvtárat használjuk, és ennek a szintaxisát követjük, ami egy-két utasításban eltérhet más rendszerek hasonló könyvtáraitól.

  1. Mivel a clpfd utasításokat szeretnénk használni, így be kell tölteni a szükséges könyvtárat

    :- use_module(library(clpfd)).  

    A feladatban a három szereplő nevét, foglalkozását és lakhelyét kell meghatároznunk. Mindegyikhez egy-egy változót rendelünk.

    csupaAB:- All = 
       [Aladar,Bela,Balazs,
        Asztalos,Badogos,Agronomus, 
        Budapest,Bekescsaba,Aszod], 

    Miután három személyről van szó, az 1,2 és 3 számok jelölnek egy-egy személyt, és az előbb felsorolt kilenc változóhoz kell ezekből a számokból egyet-egyet hozzárendelni, viszont az egyforma jellegű változók nem lehetnek azonos értékekűek.

      All ins 1..3,
      all_different([Aladar,Bela,Balazs]),
      all_different([Asztalos,Badogos,Agronomus]),
      all_different([Budapest,Bekescsaba,Aszod]),

    Most már következhet a feladat szövegében szereplő állítások megfogalmazása.

    Balázs nem budapesti.

      Balazs #\= Budapest,

    Az asztalos húga Balázs felesége. Mivel senki sem veszi el saját a húgát, így Balázs nem lehet asztalos.

      Asztalos #\= Balazs,

    Balázs minden rokona Budapesten lakik, így a sógora is, így az asztalos budapesti.

      Asztalos #= Budapest,

    A feladat szerint két személy neve, foglalkozása és lakhelye is ugyanazzal a betűvel kezdődik. Ezek egyike sem lehet az asztalos, mert róla már tudjuk, hogy budapesti. Ezért Balázs nem lakhat máshol, mint Békéscsabán; és foglalkozása sem lehet más, mint bádogos. Kizárásos alapon Aladár agronómus Aszódon.

      Balazs #= Badogos,
      Balazs #= Bekescsaba,
      Aladar #= Aszod,
      Aladar #= Agronomus,

    Miután eddig nem használtuk az egyes számokat, a három számjegy minden permutációjához egy-egy megoldás tartozik, amelyek viszont ekvivalensek. Ha rögzítjük a permutációt, ilyen nem fordulhat elő. Mivel Balázs nem budapesti így ezt a két változót rögzítjük.

      Balazs #=1,
      Budapest #=2, 

    Nincs más hátra, mint ellenőrizni, van-e a feltételeknek megfelelő megoldás, vagy sem.

      label(All),

    A már megkapott megoldást ki kellene írni.

      kiir(1,All),kiir(2,All),kiir(3,All).

    A kiírásnál a megoldásvektorból kikeressük az egymáshoz tartozó értékeket, s kiírjuk ezeket.

    kiir(N,[Aladar,Bela,Balazs,
            Asztalos,Badogos,Agronomus,
            Budapest,Bekescsaba,Aszod]):-
      nth1(N1,[Aladar,Bela,Balazs],N),
      nth1(N1,['Aladár','Béla','Balázs'],Nev),
      nth1(N2,[Budapest,Bekescsaba,Aszod],N),
      nth1(N2,['Budapest','Békéscsaba','Aszód'],Hely),
      nth1(N3,[Asztalos,Badogos,Agronomus],N),
      nth1(N3,['asztalos','bádogos','agronómus'],Munka),
      write(Nev),   write(' '),
      write(Hely),  write(' '),
      write(Munka), write(' '),nl.
  2. Ebben a feladatban öt személyről van szó, akinek a fegyvernemét, rendfokozatát kell meghatározni. Ennek megfelelően tizenöt változóról van szó, ahol mindegyik egytől ötig terjedő értékeket vehet fel.

    :- use_module(library(clpfd)).
    katonak:-
      All = 
      [Gyalogos,Tuzer,Repulo,Hirados,Hadmernok,
       Szazados,Ornagy1,Ornagy2,Ornagy3,Alezredes,
       Janos,Ferenc,Bela,Lajos,Andras],
      All ins 1..5,

    A permutációk kizárása érdekében az egyik típusú változókat rögzítjük, és ezzel biztosítjuk, hogy az értékek különbözőek.

      Gyalogos #=1, Tuzer #=2, Repulo #=3, Hirados #=4, Hadmernok#=5,

    A többi változó viszont mind különböző

      all_different([Szazados,Ornagy1,Ornagy2,Ornagy3,Alezredes]),
      all_different([Janos,Ferenc,Bela,Lajos,Andras]),

    (1) Két személy azonos rendfokozatú, így mindkettő őrnagy.

      Janos #\= Hadmernok,
      Janos #= Ornagy1,
      Hadmernok #= Ornagy2,

    (2) Ferenc nem híradós.

      Ferenc #\= Hirados,

    (3) A repülőtiszt, Béla, Lajos és Ferenc négy ember.

      all_different([Repulo,Bela,Lajos,Ferenc]),

    (4) Ugyancsak különbözik Lajos, a tüzér, a hadmérnök és a híradós.

      all_different([Lajos,Tuzer,Hadmernok,Hirados]),

    (5) Ferenc nem repülőtiszt és nem is hadmérnök.

      all_different([Ferenc,Repulo,Hadmernok]),

    (6) Miután (1)-ből tudjuk, hogy János őrnagy, ezért Lajos csak alezredes lehet. Másrészt Béla alacsonyabb fokozatú, mint Ferenc, így Béla százados.

      Lajos #= Alezredes,
      Bela #=Szazados,

    Az utolsó állításból csak az ötödik nevet ismertük meg. Az összes feltétel ismertetése után a megoldás ellenőrzése és a kiírás következik.

      label(All),
      kiir(1,All),
      kiir(2,All),
      kiir(3,All),
      kiir(4,All),
      kiir(5,All).

    Végül következzen a kiírás.

    kiir(N,[Gyalogos,Tuzer,Repulo,Hirados,Hadmernok,
            Szazados,Ornagy1,Ornagy2,Ornagy3,Alezredes,
            Janos,Ferenc,Bela,Lajos,Andras]):-
      nth1(I,[Gyalogos,Tuzer,Repulo,Hirados,Hadmernok],N),
      nth1(I,[gyalogos,tuzer,repulo,hirados,hadmernok],Fajta),
      nth1(J,[Szazados,Ornagy1,Ornagy2,Ornagy3,Alezredes],N),
      nth1(J,[szazados,ornagy,ornagy,ornagy,alezredes],Rang),
      nth1(K,[Janos,Ferenc,Bela,Lajos,Andras],N),
      nth1(K,['Janos','Ferenc','Bela','Lajos','Andras'],Nev),
      write(Nev),write(' '), write(Fajta), write(' '), write(Rang),nl.
  3. Négy személyhez tartozó név, foglalkozás, és járatszám a kérdéses, így van 12 változónk, mindegyik egytől négyig vehet fel értéket.

    :- use_module(library(clpfd)).
    csucsforgalom:-
      All = [Otvenotos,Tizenotos,Huszonotos,Harmincharmas,
             Aladar,Peter,Vilmos,Lajos,
             Lakatos,Villanyszerelo,Karpitos,Maros],
      All ins 1..4,
      Otvenotos #=1, Tizenotos #=2, Huszonotos #=3 ,Harmincharmas #=4,
      all_different([Aladar,Peter,Vilmos,Lajos]),
      all_different([Lakatos,Villanyszerelo,Karpitos,Maros]),

    (1) Vilmos nem a 15-ös villamoson ült.

      Vilmos #\= Tizenotos,

    (2) A 33-ason vasmunkás, azaz lakatos vagy marós utazott.

      Harmincharmas #= Maros #\/ Harmincharmas #= Lakatos,

    (3) A marós neve ugyanannyi betűből áll, amennyi a villamos száma jegyeinek összege. Az összeg lehet 6, 7, és 10, míg a nevek hossza 5 vagy 6. Tehát a közös érték a 6, így a villamos a 15-ös, vagy a 33-as, a marós neve pedig Aladár vagy Vilmos.

      Maros #= Aladar #\/ Maros #= Vilmos,
      Maros #= Tizenotos #\/ Maros #= Harmincharmas,

    (4) Lajos vagy az 55-ös, vagy a 33-as villamoson utazott.

      Lajos #= Harmincharmas #\/ Lajos #= Otvenotos,

    (5) A villanyszerelő nem lehet Vilmos

      Villanyszerelo #\= Vilmos,

    (6) Péter nem a 25-ösön ült.

      Peter #\= Huszonotos,

    (7) Lajos nem az 55-ösön utazott.

      Lajos #\= Otvenotos,

    Minden feltételt megadtunk, kereshetjük a megoldást, és ki is írhatjuk.

      label(All),
      kiir(1,All),
      kiir(2,All),
      kiir(3,All),
      kiir(4,All).

    A kiírás pedig már a szokásos.

    kiir(N,[Otvenotos,Tizenotos,Huszonotos,Harmincharmas,
            Aladar,Peter,Vilmos,Lajos,
            Lakatos,Villanyszerelo,Karpitos,Maros]):-
      nth1(I,[Otvenotos,Tizenotos,Huszonotos,Harmincharmas],N),
      nth1(I,[55,15,25,33],Szam),
      nth1(J,[Lakatos,Villanyszerelo,Karpitos,Maros],N),
      nth1(J,[lakatos,villanyszerelo,karpitos,maros],Munka),
      nth1(K,[Aladar,Peter,Vilmos,Lajos],N),
      nth1(K,['Aladar','Peter','Vilmos','Lajos'],Nev),
      write(Nev),write(' '), write(Szam), write(' '), write(Munka),nl.
  4. Négy személy, az általuk fogyasztott étel és ital a kérdéses, így 12 változó négy-négy különböző értéket vehet fel. A neveket rögzítjük, hogy ne kelljen a permutációkkal is foglalkozni. Természetesen az italok, és az ételek is különböznek, pontosabban az azokat megrendelő személyek.

    :- use_module(library(clpfd)).
    eszpresszo:-
      All = [Sandor, Gabor, Laszlo, Zoltan,
             Bor, Sor, Meggyle, Kola,
             Szendvics, Kremes, Pogacsa, Fagylalt],
      All ins 1..4,
      Sandor #=1, Gabor #=2, Laszlo #=3, Zoltan #=4, 
      all_different([Bor, Sor, Meggyle, Kola]),
      all_different([Szendvics, Kremes, Pogacsa, Fagylalt]),

    (1) Zoltán nem rendelt se bort, se sört.

      Zoltan #\= Sor, Zoltan #\= Bor,

    (2) Zoltán pogácsát sem rendelt. A pogácsát pedig nem a borhoz és nem a sörhöz rendelték.

      Zoltan #\= Pogacsa, Pogacsa #\= Sor, Pogacsa #\= Bor,

    (3) A fagylalt mellé nem rendeltek sört.

      Sor #\= Fagylalt,

    (4) László nem kért se fagylaltot, se krémest.

      Laszlo #\= Fagylalt, Laszlo #\= Kremes, 

    (4) Az már értelmezés kérdése, hogy ha László nem eszik édes dolgokat, akkor iszik-e ilyeneket vagy sem. Ha azt mondjuk, hogy nem is iszik, akkor ki kell bővíteni az alábbi sorral a programot. Viszont ezen a soron fog múlni, hogy a feladatnak nem lesz megoldása.

      Laszlo #\= Kola,

    (5) A pogácsa mellé nem meggylevet rendeltek.

      Pogacsa #\= Meggyle,

    (6) Sándor nem rendelt se pogácsát, se krémest, sőt bort sem.

      Sandor #\= Pogacsa, Sandor #\= Kremes, Sandor #\= Bor,

    Ezek után keressük a megoldást.

      label(All).

    Mivel ilyet az ellentmondások miatt nem találhatunk, így felesleges a kiíró rutint megadnunk. Vállalkozó kedvű olvasóink a korábbi példák alapján önállóan elkészíthetik.

  5. Négy barátnőt, dátumot és elfoglaltságot kell összekapcsolni, 12 változó négy-négy különböző érték. A napokat rögzítjük elkerülendő a permutációkat.

    :- use_module(library(clpfd)).
    feltekenyseg:-
      All = [Olga, Piri, Rozi, Sari,
             Hetfo, Kedd, Szerda, Csutortok,
             Fodrasz, Szabono, Konyvtar, Strand],
      All ins 1..4,
      Hetfo #=1, Kedd #=2, Szerda #=3, Csutortok #=4, 
      all_different([Olga, Piri, Rozi, Sari]),
      all_different([Fodrasz, Szabono, Konyvtar, Strand]),

    (a) A hét első napján nem lehetett Kati a strandon, viszont itt volt Sári ekkor.

      Hetfo #\= Strand, Kedd #\= Strand, Szerda #\= Strand,
      Hetfo #\= Sari, Kedd #\= Sari, Szerda #\= Sari,

    (b) Piri és Rozi nem voltak fodrásznál a héten.

      Piri #\= Fodrasz, Rozi #\= Fodrasz,

    (c) Olga kedden moziban volt, a szabónő pedig nem volt a városban.

      Olga #\= Kedd, Szabono #\= Kedd,

    (d) Kati csütörtökön nem tudott fodrászhoz menni.

      Csutortok #\= Fodrasz,

    (e) Piri és Rozi nem jár könyvtárba.

      Piri #\= Konyvtar, Rozi #\= Konyvtar,

    Kedden a strand zárva, legalábbis evezni lehetetlen.

      Kedd #\= Strand,

    Ezek után kereshetjük a megoldást, de olyan nincs az ellentmondások miatt.

      label(All).
  6. Három személy, három nap, három helyszín: kilenc változónk van, mind egytől háromig vehet fel értéket. A napokat rögzítjük az egyértelmű megoldás érdekében.

    :- use_module(library(clpfd)).
    kiszallas:-
      All = [SzeelHamos,KissKiraly,Latszathy,
             Hetfo, Kedd, Szerda,
             Also,Felso,Csala],
      All ins 1..3,
      Hetfo #=1, Kedd #=2, Szerda #=3,
      all_different([SzeelHamos,KissKiraly,Latszathy]),
      all_different([Also,Felso,Csala]),

    (1) Szeél-hámos nem megy Alsókisvárra.

      SzeelHamos #\= Also,

    (2) Kis-király ha elmegy Alsókisvárra, vagy Felsőnagyvárra, akkor nem kedden megy. Figyeljük meg a clp implikáció alkalmazásait!

      KissKiraly #= Also  #==> KissKiraly #\=Kedd,
      KissKiraly #= Felso #==> KissKiraly #\=Kedd,

    (3) Kis-király nem megy hétfőn Felsőnagyvárra.

      KissKiraly #= Felso #==> KissKiraly #\=Hetfo,

    (4) Alsókisvárra senki nem megy hétfőn.

      Also #\= Hetfo,

    (5) Szeél-Hámos nem mehet Felsőnagyvárra.

      SzeelHamos #\= Felso,

    (6) Csalavárra hétfőn nem szeret Szeél-Hámos menni.

      SzeelHamos #=Csala #==> SzeelHamos #\= Hetfo,

    A feltételek megadása után keressük a megoldást és ki is íratjuk.

      label(All),
      kiir(1,All),
      kiir(2,All),
      kiir(3,All).

    A kiírás rutinja igen hasonlít a korábbiakhoz.

    kiir(N,[SzeelHamos,KissKiraly,Latszathy,
             Hetfo, Kedd, Szerda,
             Also,Felso,Csala]):-
      nth1(N1,[SzeelHamos,KissKiraly,Latszathy],N),
      nth1(N1,['Szeél-Hámos Jeromos','Kiss-Király Kázmér',
               'Látszathy Lola'],Nev),
      nth1(N2,[Also,Felso,Csala],N),
      nth1(N2,['Alsókisvár','Felsőnagyvár','Csalavár'],Hely),
      nth1(N3,[Hetfo,Kedd,Szerda],N),
      nth1(N3,['hétfő','kedd','szerda'],Nap),
      write(Nev),   write(' '),
      write(Hely),  write(' '),
      write(Nap), write(' '),nl.
  7. A hat személynek, a sportágaknak és az egyesületeknek megfelelően 18 változóra van szükség, amelyek 1-6 értéket vehetnek fel. A személyeket rögzítjük.

    :- use_module(library(clpfd)).
    
    sportolok:-
      All = [Andras,Bela,Csaba,Dezso,Elemer,Ferenc,
             Fotball1,Fotball2,Magasugro1,Magasugro2,Uszo,Vizilabdazo,
             Vasas1,Vasas2,Ujpesti1,Ujpesti2,FTC1,FTC2],
      All ins 1..6,
      Andras #= 1,Bela #= 2,Csaba #= 3,Dezso #= 4,Elemer #= 5,Ferenc #= 6,
      all_different([Fotball1,Fotball2,Magasugro1,Magasugro2,Uszo,Vizilabdazo]),
      all_different([Vasas1,Vasas2,Ujpesti1,Ujpesti2,FTC1,FTC2]),

    Ennyi még nem elég, az azonos sportágak és egyesületek esetén is meg kell adni a sorrendet, hogy ne kapjuk meg ugyanazt a megoldást még egyszer.

      Fotball1 #< Fotball2,
      Magasugro1 #< Magasugro2,
      Vasas1 #<Vasas2,
      Ujpesti1 #< Ujpesti2,
      FTC1 #< FTC2,

    (1) Béla vizisportot űz, és nem az FTC-nél sportol.

      Bela #= Uszo #\/ Bela #= Vizilabdazo,
      Bela #\= FTC1, Bela #\= FTC2,

    (2) Az újpestiek nem labdajátékosok.

      Ujpesti1 #\= Fotball1, Ujpesti1 #\= Fotball1, Ujpesti1 #\= Vizilabdazo,
      Ujpesti2 #\= Fotball1, Ujpesti2 #\= Fotball1, Ujpesti2 #\= Vizilabdazo,

    (3) Csak Ferenc az, akinél a sport és az egyesület is kezdődhet f-fel.

      Ferenc #= Fotball1 #\/ Ferenc #= Fotball2,
      Ferenc #= FTC1 #\/ Ferenc #= FTC2,

    (4) Csaba nem űzhet vizes sportot.

      Csaba #\= Uszo, Csaba #\= Vizilabdazo,

    (5) A Vasas sportolói nem vizes sportokat űznek.

      Vasas1 #= Fotball1   #\/ Vasas1 #= Fotball2,
      Vasas2 #= Magasugro1 #\/ Vasas2 #= Magasugro2,

    (6) Elemér már nem labdarugó.

      Elemer #\= Fotball1, Elemer #\= Fotball2,

    (7) András és Elemér klubtársak.

      Andras #= FTC1 #<==> Elemer #= FTC2,
      Andras #= FTC2 #<==> Elemer #= FTC1,
      Andras #= Vasas1 #<==> Elemer #= Vasas2,
      Andras #= Vasas2 #<==> Elemer #= Vasas1,
      Andras #= Ujpesti1 #<==> Elemer #= Ujpesti2,
      Andras #= Ujpesti2 #<==> Elemer #= Ujpesti1,

    Minden feltételt felsoroltunk, a többi a rendszer dolga.

      label(All),
      kiir(1,All), kiir(2,All),
      kiir(3,All), kiir(4,All),
      kiir(5,All), kiir(6,All).

    A kiírás hasonló a korábbiakhoz.

    kiir(N,[Andras,Bela,Csaba,Dezso,Elemer,Ferenc,
             Fotball1,Fotball2,Magasugro1,Magasugro2,Uszo,Vizilabdazo,
             Vasas1,Vasas2,Ujpesti1,Ujpesti2,FTC1,FTC2]):-
      nth1(N1,[Andras,Bela,Csaba,Dezso,Elemer,Ferenc],N),
      nth1(N1,['András','Béla','Csaba','Dezső','Elemér','Ferenc'],Nev),
      nth1(N2,[Fotball1,Fotball2,Magasugro1,Magasugro2,Uszo,Vizilabdazo],N),
      nth1(N2,['futbalista','futbalista','magasugró','magasugró','úszó','vizilabdázó'],Sport),
      nth1(N3,[Vasas1,Vasas2,Ujpesti1,Ujpesti2,FTC1,FTC2],N),
      nth1(N3,['Vasas','Vasas','Újpesti Dózsa','Újpesti Dózsa','FTC','FTC'],Egyesulet),
      write(Nev),write(' '),
      write(Sport),write(' '),
      write(Egyesulet),nl.
  8. Ennél a feladatnál egy adott nyelvet többen is ismerhetnek, így a korábban alkalmazott módszerek nem megfelelőek. Ezért képzeletben egy olyan táblázatot készítünk, mely sorainak egy-egy személy, oszlopainak egy-egy ismert nyelv felel meg. A valóságban ezt 25 változóval oldjuk meg, az elnevezésükben az első betű a személyre, a második betű a nyelvre utal. Mivel ezek igaz/hamis változók, de az SWI-Prolog-hoz nem jár bináris clp könyvtár, a clpfd-t használjuk 0-1 értékekkel.

    :- use_module(library(clpfd)).
    
    nyelvismeret:-
      All = [MM,ML,MS,MF,MN,
             LM,LL,LS,LF,LN,
             SM,SL,SS,SF,SN,
             FM,FL,FS,FF,FN,
             NM,NL,NS,NF,NN],
      All ins 0..1,

    Mindenki ismeri a saját anyanyelvét:

      MM #=1, LL#=1, SS#=1, FF#=1, NN#=1,

    (1) Mindenki ismer idegen nyelveket. Itt azt használjuk ki, hogy az igaz értékeket eggyel, a hamisakat nullával jelöljük. Az idegennyelv-ismeret a többi nyelvhez tartozó változók valamelyikének nem nulla értékét jelenti, de ilyen esetben ezek összege sem lehet nulla.

      sum([SM,SL,SF,SN],#>,0),
      sum([FM,FL,FS,FN],#>,0),
      sum([NM,NL,NS,NF],#>,0),
      sum([ML,MS,MF,MN],#>,3),
      sum([LM,LS,LF,LN],#>,3),

    (2) Egyik nyelvet sem ismeri mindenki, így az előbb említett összeg nem értheti el az ötöt egyik nyelv esetén sem.

      sum([MM,LM,SM,FM,NM],#<,5),
      sum([ML,LL,SL,FL,NL],#<,5),
      sum([MS,LS,SS,FS,NS],#<,5),
      sum([MF,LF,SF,FF,NF],#<,5),
      sum([MN,LN,SN,FN,NN],#<,5),

    (3-5) Ezeket az információkat nem használtuk fel, feleslegesek.

    (6) A magyar és a lengyel három idegen nyelvet tud, így az (1)-ben megfogalmazott utolsó két sor pontosítható.

      sum([ML,MS,MF,MN],#=,3),
      sum([LM,LS,LF,LN],#=,3),

    (7-8) A finn meg a svéd eltávolításával a többieknek akad közös nyelvük. Vagyis első esetben közös nyelv lehet a magyar, a lengyel, a svéd vagy a német. Ezért egy perfekt diszjunktív normálformát készíthetünk logikai változóinkból. Viszont most nem logikai, hanem megfelelő aritmetikai műveleteket kell ugyanerre használnunk.

      MM*LM*SM*NM+ML*LL*SL*NL+MS*LS*SS*NS+MN*LN*SN*NN #=1,
      MM*LM*FM*NM+ML*LL*FL*NL+MF*LF*FF*NF+MN*LN*FN*NN #=1,

    (9) Csak hárman tudnak svédül.

      sum([MS,LS,SS,FS,NS],#=,3),

    (10) Ketten tudnak lengyelül, és ketten tudnak finnül.

      sum([MF,LF,SF,FF,NF],#=,2),
      sum([ML,LL,SL,FL,NL],#=,2),

    (11) A lengyel és a finn két nyelven tud egymással beszélni, a német nem tartozik ezek közé. Itt a (7) formalizálásakor alkalmazott módszert használjuk újra.

      LM*FM+LL*FL+LS*FS+LF*FF #=2,
      LN*FN #=0,

    (12) A magyar-svéd közös nyelvének leírásakor is ugyanezt alkalmazzuk.

    MM*SM+ML*SL+MS*SS+MF*SF+MN*SN #=1,

    Ezek után a már megszokott módszert követhetjük, csak a korábbi kiírásokat a megoldás elején emlegetett mátrixszal helyettesítjük.

      label(All),
      write('  MLFSN'),nl,
      write('M|'),write(MM),write(ML),write(MF),write(MS),write(MN),nl,
      write('L|'),write(LM),write(LL),write(LF),write(LS),write(LN),nl,
      write('F|'),write(FM),write(FL),write(FF),write(FS),write(FN),nl,
      write('S|'),write(SM),write(SL),write(SF),write(SS),write(SN),nl,
      write('N|'),write(NM),write(NL),write(NF),write(NS),write(NN),nl.
    

Euler projekt

  1. Sokan úgy kezdenek hozzá ennek a feladatnak a megoldásához, hogy egy ciklusban végigmennek a számokon, és ha a megfelelő feltételt kielégítik, akkor egy segédváltozó értékét növelik a számmal. Így a megoldás lineáris bonyolultságú. Viszont a hárommal osztható számok egy számtani sorozatot alkotnak, éppúgy, ahogy az öttel oszthatóak is. Ha viszont külön kezeljük ezeket a számokat, több számot kétszer is figyelembe veszünk. Ezek a tizenöttel oszthatóak, és ezek is egy számtani sorozatot alkotnak, tehát a számtani összegképletét kell három esetben használni, és a megoldáshoz jutunk konstans idő alatt.

    Az összegképlet kiszámítása a következőképpen történik tetszőleges felső határ és szám esetén:

    sorozat(Szam,Hatar,Osszeg):-
      Darab is Hatar//Szam+1,
      Osszeg is ((Darab-1)*Szam)*Darab//2.

    Nincs más hátra, csak kiszámolni a három, valamint az öt többszöröseinek összegét, és kivonni belőlük a tizenöt többszöröseinek összegét. A feladat kitűzésében szereplő határt már nem vehetjük figyelembe, ezért ezt eggyel csökkenteni kell.

    euler1(Hatar,Osszeg):-
      Hatar1 is Hatar-1,
      sorozat(3,Hatar1,O3),
      sorozat(5,Hatar1,O5),
      sorozat(15,Hatar1,O15),
      Osszeg is O3+O5-O15. 
  2. A feladat megoldása során újabb és újabb Fibonacci számokat kell generálni, valamint számolni kell a páros számok összegét is. Ehhez három segédváltozót vezetünk be. Az összeg kezdeti értéke 0, míg Fibonacci számok generálásához az 1,1 párosból indulunk ki.

    euler2(Hatar,Osszeg):-
      euler2(Hatar,1,1,0,Osszeg). 

    Ezek után a legutóbbi két értékből lehet képezni a következő Fibonacci számot. Ha ez páros, akkor be kell számítani az összegbe. Ha pedig már túlléptünk a megadott határon, akkor leállíthatjuk a ciklust.

    euler2(Hatar,A,B,Acc,Osszeg):-
      Hatar > A+B -> 
        C is A+B,
        (0 =\= C mod 2 -> Acc = Acc2; Acc2 is Acc + C),
        euler2(Hatar,C,A,Acc2,Osszeg);
      Osszeg=Acc. 
  3. A szám prímtényezős felbontásából igen könnyedén meghatározható a feladatra adandó válasz. Viszont nincs szükségünk a teljes felbontásra. Végighaladva a prímeken: ha osztható a szám vele, akkor osszuk el. Így a szám folyamatosan csökken, s már nem más, mint egy prímszám, akkor megállhatunk. Viszont a prímszámok tesztelése nem igazán egyszerű, könnyebb azt figyelni, mikor lett a szám 1, mert akkor az utolsó osztó lesz a keresett prím. Sőt nem is csak prímekkel fogunk osztani, hanem megpróbálkozunk a kettővel, és minden páratlan számmal. Ebből nem lesz baj, mert hiába lenne osztható a szám kilenccel, a hármas osztásnál addig osztottuk a számot, amíg lehetséges volt.

    A ciklus során meg kell jegyezni az aktuális lehetséges osztót, és az utolsó valódi osztót. Az előbbi kezdetben 2 lesz, az utóbbinak még nem lesz értéke

    euler3(Szam,MaxPrim):-
      oszt(2,Szam,_,MaxPrim).

    Ha az eredeti szám (Szam) értéke már 1, akkor az utolsó osztó (Acc) lesz az eredmény. Ellenkező esetben ha osztható a szám N-nel, azaz az aktuális osztóval, akkor osszuk el vele, s az új számmal dolgozzunk tovább. Ha nem osztható, akkor vegyük a következő lehetséges osztót, azaz a következő páratlan számot, vagy a hármast, és ezzel az új osztóval dolgozzunk tovább.

    oszt(N,Szam,Acc,MaxPrim):-
      1 = Szam -> 
        MaxPrim =Acc;
        0 =:= Szam mod N -> 
          Szam1 is Szam//N,
          oszt(N,Szam1,N,MaxPrim); 
        (N>2 -> N2 is N+2; N2=3),
        oszt(N2,Szam,Acc,MaxPrim). 
  4. Több módon is lehet ellenőrizni, hogy egy szám palindrom-e, vagy sem. Legegyszerűbben egy listát készítünk egy számból, és a lista palindrom voltát kell csak ellenőrizni:

    palindrom(N):-
      number_chars(N,L),
      reverse(L,L).

    A feladat nagyban épít arra, hogy mekkora számokkal dolgozhatunk, így ezeket az értékeket konstansként beépítettük a programba. Az általános program jócskán elbonyolódott volna.

    Lényegében egy dupla ciklust kell megvalósítani. Miután a szorzat kommutatív, elegendő, ha a két ciklusváltozó közül az első nem nagyobb a másiknál. Miután a maximális számot keressük, a nagyobb számok felől haladunk a kisebb számok irányába.

    euler4(N):-
      szorzat(999,999,0,N). 

    Az nem elég, hogy találunk egy palindrom számot, mert lehet, hogy létezik egy nagyobb úgy, hogy mindkét szorzója a most megtalált szám két osztója közé esik.

    Ha a külső ciklusváltozó (Y) már nem háromjegyű, vagy már találtunk egy palindrom számot (Acc), úgy hogy a külső ciklusváltozót nem tudjuk elég nagy számmal beszorozni, hogy ennél a palindromnál nagyobbat kaphassunk, akkor ezt kell eredményként visszaadni.

    Ha a belső ciklusváltozó (X) már túllépett a határon, csökkenteni kell a külső ciklusváltozót, s vele egyenlőre beállítani a belsőt is. Ellenkező esetben ki kell számolni a szorzatot, ellenőrizni, hogy palindrom-e, vagy sem. Előbbi esetben ennek még a korábbi palindromnál is nagyobbnak kell lennie. Ha mindkét feltétel teljesül, akkor elmenthetjük.

    A belső ciklusváltozót léptetni kell, s kezdődik újra minden.

    szorzat(X,Y,Acc,N):-
      (Y<100;Acc>0,Y*1000<Acc) -> 
        N=Acc;
        X<100 -> 
          Y1 is Y-1,
          X1 is Y1,
          szorzat(X1,Y1,Acc,N); 
          Z is X*Y,
          ((palindrom(Z),Z>Acc) -> Acc2=Z; Acc2=Acc),
          X1 is X-1,
          szorzat(X1,Y,Acc2,N). 
    1. Ha a Prolog verziónk tetszőleges nagy egész számokkal képes dolgozni (ilyen az SWI), akkor kikerülhetjük a prímhatványok szorzatára bontást. Mivel a Hatar változót lecsökkenthetjük 1-ig, így elég csak egy segédváltozót felhasználni a szorzat tárolására, amelynek értéke kezdetben legyen 1!

      euler5a(Hatar,N):-
        euler5a(Hatar,1,N). 

      Ha a Hatar-ral eljutottunk 1-ig, akkor az eddig kiszámoltakat kell visszaadni. Ellenkező esetben meg kell nézni a mostani számnak (Hatar), és az eddig kiszámolt szorzatnak (Szorzat) mi a legnagyobb közös osztója. Ha ez pont a mostani szám, akkor már osztható vele, ezzel a számmal nem kell foglalkozni. Egyébként ki kell számolni a legkisebb közös többszörösüket.

      Ezek után a Hatar értékét csökkentjük, és újrakezdjük a ciklust.

      euler5a(Hatar,Szorzat,N):-
        Hatar = 1 ->
         N = Szorzat; 
         lnko(Szorzat,Hatar,Lnko),
         (Lnko < Hatar -> 
            Szorzat1 is Szorzat* (Hatar//Lnko); 
            Szorzat1 = Szorzat),
          Hatar1 is Hatar-1,
          euler5a(Hatar1,Szorzat1,N). 
    2. Ha nem képes a Prolog verziónk nagyon nagy számokkal dolgozni, akkor megpróbálhatjuk a prímhatványokat kiszámolni, majd ezek közül a maximálisakat kiválogatni, és ezek szorzata adja meg az eredményt.

      euler5(Hatar,N):-
        felbont(Hatar,[],L),
        maximum(L,[],L2),
        szoroz(L2,1,N).

      Az 1, ..., N számok prímtényezős felbontásához egy ciklust készítünk, egy segédváltozónk lesz, mely a prímhatványokat tárolja. Az N lesz a ciklusváltozónk, s ha eléri az 1 értéket, az addig összegyűjtött hatványokat adjuk vissza. A ciklus magjában a prímtényezőkre bontás (ptb/5) kerül végrehajtásra.

      felbont(N,Acc,L):-
        N>1 ->
          ptb(N,2,0,Acc,Acc2),
          N1 is N-1,
          felbont(N1,Acc2,L);
        Acc=L.

      A prímtényezőkre bontásnál inputként szerepel a felbontandó szám (N), az aktuális osztó (Oszto), hányszor sikerült már elosztanunk az eredeti számot ezzel az osztóval (Db), a korábbi prímhatványok listája (Acc). Ha a szám már 1-re csökkent, kész is vagyunk, csak az utolsó osztások eredményével ki kell egészíteni a már összegyűlt listát (Acc). Ellenkező esetben van értelme osztani. Ha ez maradék nélkül megy, akkor az osztást végrehajtjuk, a Db számlálót növeljük. Ellenkező esetben újabb osztót keresünk. Ezt itt most eggyel növeléssel oldottuk meg, de használható lenne az oszt/4 esetén használt növelés is. Természetesen ebben az esetben is tárolni kell a korábbi osztások eredményét.

      ptb(N,Oszto,Db,Acc,L):-
        1 = N ->
         L=[Oszto/Db|Acc];
         0 =:= N mod Oszto ->
           N1 is N//Oszto,
           D1 is Db +1,
           ptb(N1,Oszto,D1,Acc,L);
         O1 is Oszto+1,
         ( Db=0 -> Acc2=Acc;Acc2=[Oszto/Db|Acc]),
         ptb(N,O1,0,Acc2,L).

      Ha már adott minden egyes szám prímtényezős felbontása, akkor a közös többszöröshöz azokat a prímhatványokat kell összeszedni, amelyek kitevői maximálisak az adott prím esetén. A könnyebb kezelhetőség kedvéért a prímhatványokat egy listába gyűjtöttük össze. Ha ez a lista kiürült a feldolgozás során, kész is vagyunk.

      maximum([],L,L).

      Egyéb esetben a listában szerepel legalább egy prímhatvány, ahol X a prím és Y a kitevő. Ha a maximálisnak tekintett prímhatványok listájában ez a prím előfordul Z kitevővel, akkor két eset lehetséges. Ha Y a kisebb, akkor figyelmen kívül kell hagyni, ha viszont ez a nagyobb, akkor le kell cserélni a prímhatványt. Ha véletlenül X nem szerepelne a prímhatványok listájában, akkor bele kell tenni.

      maximum([X/Y|Xs],Acc,L):-
        member(X/Z,Acc)-> 
          (Y<Z -> maximum(Xs,Acc,L); csere(X/Y,Acc,Acc2), 
          maximum(Xs,Acc2,L));
        maximum(Xs,[X/Y|Acc],L).

      Csere esetén addig kell a listában haladni, amíg az adott prímmel nem találkozunk. Ekkor csupán a kitevőt kell kicserélni, és kész vagyunk.

      csere(X/Y,[U/V|L],[U/W|L2]):-
         X=U ->
          W=Y, L=L2;
         V=W,csere(X/Y,L,L2).

      Ha véletlenül az eredmény még ábrázolható egész számként, akkor ilyen alakban is átszámolhatjuk a prímhatványok listáját/szorzatát.

      Ha a lista kiürült, kész vagyunk a számolással.

      szoroz([],Acc,Acc). 

      Ha nem, akkor végezzük el a hatványozást, és szorozzuk meg a segédváltozót vele!

      szoroz([X/Y|Xs],Acc,N):-
        Acc1 is X^Y*Acc,
        szoroz(Xs,Acc1,N).
  5. Majdnem megoldható az egész feladat egy ciklusban. Összeszámoljuk a számok összegét (Ossz), valamint a négyzetösszegüket (Negyzossz), s a keresett differencia (D) ezek után könnyedén kiszámolható:

    euler6(N,D):-
      szamol(N,0,0,Negyzossz,Ossz),
      D is Ossz*Ossz-Negyzossz.

    A ciklusban két segédváltozót használunk Acc1 és Acc2. A ciklusváltozót (N) folyamatosan csökkentjük, és amikor eléri a 0 értéket, adjuk vissza az addig számoltakat. Egyébként a segédváltozókat a ciklusváltozóval, illetve a négyzetével növeljük.

    szamol(N,Acc1,Acc2,No,O):-
      N=0->
       Acc1=No, Acc2 = O;
       Acc3 is Acc1 + N * N,
       Acc4 is Acc2 + N,
       N1 is N-1,
       szamol(N1,Acc3,Acc4,No,O). 
  6. Korábban már készítettünk egy prímlistát egy adott felső határig. Abban az esetben mindig a legutolsóként megtalált prímszámmal próbáltuk osztani a reménybeli prímeket, pedig ezekkel elég kis valószínűséggel oszthatóak. Jobban járnánk, ha a kis prímekkel tesztelnénk először az oszthatóságot. Ezért olyan adatszerkezetre lesz szükségünk, amelynek egyik végére pakolhatjuk az új prímeket, és a másik végén található számokkal próbáljuk tesztelni az oszthatóságot. Annak nincs értelme, hogy a listát oda-vissza forgassuk, ezért fogunk differencialistát használni.

    Segédváltozóban nyilvántartjuk a már előállított prímszámok számát, a lehetséges prímet, valamint a prímek listáját. Feltételezzük, hogy nem az első vagy a második prímszámot keressük. Ellenkező esetben az alábbi program kiegészíthető a megfelelő adatokkal.

    euler7(Hatar,P):-
      n_prim(Hatar,2,5,[3|L]-L,P). 

    Ha az aktuális szám (X) az egyik prímmel sem osztható, akkor prímre akatunk, az N számlálót növelni kell, a lista farkába (P) beszúrhatjuk az aktuális számot. Ha ezzel a prímmel elértük a kívánt prímet, akkor kész is vagyunk, egyébként folytassuk a következő páratlan számmal.

    n_prim(Hatar,N,X,PL-[P|L],Prim):-
      X2 is X+2,
      (nem_oszt(PL,N,X) ->
         N1 is N+1, P=X,
        (N1 = Hatar -> Prim=P; n_prim(Hatar,N1,X2,PL-L,Prim));
      n_prim(Hatar,N,X2,PL-[P|L],Prim)). 

    Ha végigpróbáltuk az összes prímet a listában (az N számláló 1-re csökkent), akkor a szám prímszám. Különben el kell osztani a soron következő prímmel. Ha nem osztható vele, akkor még van remény, a számlálót csökkenteni kell, és folytani a következő prímmel.

    nem_oszt([X|Xs],N,P):-
      N = 1 -> true;
        0 =\= P mod X,
        N1 is N-1,
        nem_oszt(Xs,N1,P). 
  7. Egy ezerjegyű számmal kell dolgozunk, mely a szam/1 predikátummal adunk meg. Jobban járunk, ha a számot számjegyekre bontjuk, és a számjegyekből listát képzünk. A number_codes/2 predikátum majdnem elvégzi ezt helyettünk, de a számjegyek listája helyett azok kódjainak listáját adja vissza. Ezért végig kell menni ezen a listán, s minden kódból le kell vonni a 0 számjegy kódját:

    szamma([],Ns,Ns).
    szamma([X|Xs],Acc,Ns):-
      Y is X-48,
      szamma(Xs,[Y|Acc],Ns).

    Ezek után öt egymás mellett álló szám szorzatát kell kiszámítani. A kész lista fejében keresünk öt elemet. Ha ez lehetséges, akkor összeszorozzuk, és összehasonlítjuk az eddigi maximummal. Ha már nincs öt elem a listában, akkor nincs miért tovább számolni, és az eddig összegyűjtött maximumot vissza kell adni.

    euler8(L,Acc,Szorzat):-
      L = [A,B,C,D,E|F] ->
        Pr is A*B*C*D*E,
        Acc2 is max(Pr,Acc),
        euler8([B,C,D,E|F],Acc2,Szorzat)
      ;
        Szorzat = Acc. 

    Ezek után nincs más hátra, mint összekapcsolni az előbbi részeket:

    euler8(Szorzat):-
      szam(X),
      number_codes(X,L),
      szamma(L,[],Ns),
      euler8(Ns,0,Szorzat).  
  8. A pitagoraszi számhármasok a következőképp állnak elő: a=d(n²-m²), b=d(2nm) és c=d(n²+m²), ahol n>m, valamint n és m relatív prímek. Mi a három szám összegét ismerjük, innen a+b+c=2dn(n+m). Ezek szerint nincs más dolgunk, mint megkeresni a megadott összeg felét osztó n-t, majd azt az m-et, ahol n+m a hányados osztója lesz.

    A d és m legkisebb értéke 1, így n maximum az összeg felének négyzetgyöke lehetne, így innen indul a ciklusunk. Ha osztóra lelünk, visszaadjuk, egyébként csökkentjük a ciklusváltozó (N0) értékét. A már megszokott if-then-else szerkezetet nem érdemes használni, mert lehet, hogy az első osztó nem rendelkezik mindazokkal a tulajdoságokkal, melyre számítunk, és ekkor a vágás miatt már nem jutunk el a valódi megoldáshoz.

    keres_n(N,O,N):-
      N>1,
      O mod N =:=0.
    keres_n(N0,O,N):-
      N0>1,
      N1 is N0-1,
      keres_n(N1,O,N). 

    Az m meghatározásakor hasonló rutinra van szükségünk, csak itt n+m-nek kell az osztónak lennie:

    keres_m(M,O,N,M):-
      M>0,
      O mod (N+M) =:=0.
    keres_m(M0,O,N,M):-
      M0>0,
      M1 is M0-1,
      keres_m(M1,O,N,M).

    Végül ezt a két keresést kell összehangolni. A backtrack minden lehetséges n-m párt kipróbál.

    euler9(Osszeg,Szorzat):-
      O2 is Osszeg//2,
      N0 is floor(sqrt(O2)),
      keres_n(N0,O2,N),
      O3 is O2/N,
      M0 is N-1,
      keres_m(M0,O3,N,M),
      D is O3/(M+N),
      Szorzat is D*D*D*(N*N-M*M)*2*N*M*(N*N+M*M). 
  9. A kétmilliónál kisebb prímek összeadásához több módon is hozzáfoghatunk. A legegyszerűbbnek egy ciklus tűnik, amelyben minden egyes számról megvizsgáljuk, hogy prímszám-e, és ha az, akkor hozzáadjuk a gyűjtőváltozóhoz. Ebben az esetben nem használjuk fel a korábban megtalált prímeket, és igen lassú lesz a program. Mi ehelyett azt a módszert használjuk, hogy egy listába szervezzük a prímeket, és egy új szám esetén csak azt kell ellenőrizni, hogy a korábban összegyűjtött prímekkel osztható-e vagy sem [Knuth]. Mivel a prímtesztet a lista elejéről érdemes kezdeni, de a prímet a lista végére érdemes illeszteni, egy differencialistát fogunk felhasználni, melyben a prímeket gyűjtjük. Végül ezen lista elemeit kell összeadni, és hozzájuk adni az eddig kimaradt kettest.

    euler10(Hatar,Osszeg):-
      primUT(Hatar,2,5,[3|L]-L,LL),
      sumlist(LL,O2),Osszeg is O2+2.

    A lista készítése során figyelni kell a felső határra, ami az eredeti feladatban a kétmillió. Számon tartjuk, hogy a lista hanyadik (N) elemét keressük és melyik számot (X) próbáljuk ki éppen. Ha ez a szám prímnek minősül (egyik eddigi prím sem osztja), akkor tárolni kell. Ha a következő páratlan szám már túl van a határon, akkor a listát lezárhatjuk, és visszaadjuk. Ellenkező esetben beírjuk a prímet (ha találtunk egyet), és folytatjuk a keresést.

    primUT(Hatar,N,X,PL-[P|L],LL):-
      X2 is X+2,
      (nem_oszt(PL,N,X) ->
         N1 is N+1, P=X,
        (X2 > Hatar -> L=[],LL=PL; primUT(Hatar,N1,X2,PL-L,LL));
      primUT(Hatar,N,X2,PL-[P|L],LL)).

    Mivel a listában prímek és értékkel nem rendelkező változók is vannak, számon tartunk egy határt (N), melyet nem fogunk túllépni. Erről eltekintve a megvalósítás ugyanaz, mint korábban.

    nem_oszt([X|Xs],N,P):-
      N = 1 -> true;
        0 =\= P mod X,
        N1 is N-1,
        nem_oszt(Xs,N1,P).
  10. Miután a Prolog nyelvben nem használatos a tömb adatszerkezet, csak a lista, így a mátrix nem más, mint listák listája. Az nth1/3 predikátummal ki tudjuk választani egy lista adott elemét, így kétszeri alkalmazásával megkaphatjuk a mátrix bármely elemét. Viszont miután egy elemet igen sok esetben meg kell vizsgálni, az a megoldás, hogy ezt a műveletet dupla ciklusba helyezzük, nem vezet hatékony kódhoz.

    Ehelyett azt a megoldást követjük, hogy a szükséges sort, oszlopot vagy átlót (szaggatott vonallal jelölve az ábrán) külön listába másoljuk, és ezután ebben keresünk megfelelő négyeseket. Ehhez hasonló feladat már szerepelt korábban, csak ott ötösök szorzatát kerestük. Az ottani euler8/3 predikátumnak az alábbi felel meg

    listaMax(L,Acc,M):-
      L=[A,B,C,D|E]-> 
        Acc1 is max(A*B*C*D,Acc),
        listaMax([B,C,D|E],Acc1,M)
      ;
        M=Acc.

    Mivel a mátrix soros szervezésű, a sorokban keresni igen könnyű.

    sorokMax([],Max,Max).
    sorokMax([X|Xs],Acc,Max):-
      listaMax(X,0,M),
      Acc1 is max(M,Acc),
      sorokMax(Xs,Acc1,Max). 

    Az oszlopokban kereséshez le kell választani a sorok listájáról egy oszlopot, azaz minden lista elejéről le kell szedni egy-egy elemet. Ha eltároljuk a megmaradt listafarkakat, akkor legközelebb is elég egy oszlopot leválasztani. Mivel segédváltozóba mozgattuk a farkakat, végül az egész maradékot fordítva adjuk vissza. Mivel a szorzás kommutatív, ez nem kutyulja össze az eredményt.

    listák listájának felbontása sorokra, oszlopokra, átlókra

    Mátrix megvalósítása listák listájával és hatékony feldolgozása soroknak, oszlopoknak és átlóknak.

    levalaszt([],Oszlop,Maradek,Oszlop,Maradek).
    levalaszt([[X|Xs]|Xss],Acc1,Acc2,Oszlop,Oszlopok):-
     levalaszt(Xss,[X|Acc1],[Xs|Acc2],Oszlop,Oszlopok).

    Ezek után az oszlopok átvizsgálása a következőképpen történik: amíg van leválasztható oszlop a tömbben, addig leválasztjuk, kiszámoljuk az így kapott lista maximális szorzatát, és folytatjuk az eljárást a maradékkal. Ha már kiürült a mátrix, azaz csak üres listák listája, akkor amit kiszámoltuk, visszaadjuk.

    oszlopokMax([[]|_],Max,Max):-!.
    oszlopokMax([[X|Xs]|Xss],Acc,Max):-
      levalaszt([[X|Xs]|Xss],[],[],Oszlop,Oszlopok),
      listaMax(Oszlop,0,M),
      Acc1 is max(M,Acc),
      oszlopokMax(Oszlopok,Acc1,Max).

    Ezek után következik a talán legnehezebb rész, az átlók kezelése, mivel az átlók eltérő hosszúak, és egy átló leválasztása után elég furcsa alakzat marad vissza, és amíg oszlopok esetén nem volt zavaró, hogy fejre állt a mátrix maradéka, itt ez már problémát okozna!

    A megoldás a leválaszt/5 predikátum egy továbbfejlesztett változata lesz. Ebbe bekerül egy számláló, amely számolja, hogy hány elemet kell leválasztani a listák elejéről. Kezdetben egyet, a bal felső sarokelemet, majd kettőt, a néhai bal felső szomszédjait, és így tovább:

    levalaszt([],_,Atlo,RMaradek,Atlo,Maradek):-
      reverse(RMaradek,Maradek),!.

    Ha túlléptünk az utolsó soron is, akkor a segédváltozóban tárolt maradékot vissza kell fordítani a fejéről a talpára.

    levalaszt([[]|Xss],N,Acc1,Acc2,Atlo,Maradek):-
      !,levalaszt(Xss,N,Acc1,Acc2,Atlo,Maradek).
    

    Ha az aktuális sorból már mindent eltávolítottunk, távolítsuk el ezt a sort is!

    levalaszt([[X|Xs]|Xss],N,Acc1,Acc2,Atlo,Maradek):-
      N1 is N-1,
      N1>0 -> 
      levalaszt(Xss,N1,[X|Acc1],[Xs|Acc2],Atlo,Maradek);
      Atlo=[X|Acc1],
      maradek([Xs|Acc2],Xss,Maradek).

    Általános esetben a lista feje az átlót tároló változóba, a farka pedig a maradékok közé kerül. A számlálót csökkentjük, és ha van még leválasztandó elem a következő sorokban, akkor rekurzívan dolgozunk tovább. Ellenkező esetben már kész az átló, a maradékot kellene eredeti állapotába visszaállítani. Egy része már a feje tetején van, a többivel még nem is foglalkoztunk. Azaz nincs más dolgunk, mint a leszedett részeket visszapakolni az eredeti helyére. Ez lényegében nem más, mint a reverse eredeti programja, csak nem üres akkumulátorváltozóval indítjuk el.

    maradek([],Oszlopok,Oszlopok).
    maradek([X|Xs],Acc,Oszlopok):-
      maradek(Xs,[X|Acc],Oszlopok).

    Ezután már jöhet az átlók programja, ami alig különbözik az oszlopok programjától.

    atlokMax([],_,Max,Max).
    atlokMax([X|Xs],N,Acc,Max):-
      levalaszt([X|Xs],N,[],[],Atlo,Maradek),
      listaMax(Atlo,0,M),
      Acc1 is max(M,Acc),
      N1 is N+1,
      atlokMax(Maradek,N1,Acc1,Max).

    Most következne a másik irányú átlók programja, de mivel ha az előbbi programot a mátrix tükörképére végrehajtva a többi átlót kapjuk meg, szinte semmit sem kell csinálnunk:

    masikAtlok(Xs,Acc,Max):-
      reverse(Xs,RXs),
      atlokMax(RXs,1,Acc,Max).

    Nem maradt más hátra, csak összekapcsolni ezeket a részeket. Indításkor beírhatjuk a mátrixot direkt, de ha az külön predikátumban lett definálva, követhetjük a következő indítást is: ?- tablazat(T), euler11(T,Max).

    euler11(T,Max):-
      sorokMax(T,0,M1),
      oszlopokMax(T,M1,M2),
      atlokMax(T,1,M2,M3),
      masikAtlok(T,M3,Max).
  11. A háromszögszámok kiszámítása reményeim szerint senkinek nem jelent problémát. Az osztók számának meghatározására ez már nem igaz. Persze el lehet menni egy ciklussal a szám négyzetgyökéig, s számolni, hogy hány osztóra akadtunk. Mivel igen nagy számokat kapunk, ez lassú megoldás lesz. Jobban járunk ha felhasználjuk, hogy az osztók száma a szám prímhatvány-felbontásából könnyedén meghatározható. A korábban ismertetett ptb/5 predikátum elkészíti a prímtényezős felbontást. Ebből viszont csak a hatványkitevőket kell felhasználnunk, mindegyiknél eggyel nagyobb számokat kell összeszoroznunk.

    osztok([],X,X).
    osztok([_/N|L],Acc,M):-
      Acc1 is Acc*(N+1),
      osztok(L,Acc1,M).

    Az euler12/4 predikátumban H jelöli az aktuális háromszögszámot, D a differenciát, mellyel a következő háromszögszám is megkapható, N pedig a keresett osztók számát. Ha az H osztói számának meghatározása után az derül ki, hogy már túlléptünk a határon, akkor visszaadhatjuk H-t, egyébként lépünk a következő számra.

    euler12(N,Result):-
     euler12(1,2,N,Result).
    euler12(H,D,N,Result):-
      ptb(H,2,0,[],L),
      osztok(L,1,M),
      (M>N-> Result=H;
      H1 is H+D,
      D1 is D+1,
      euler12(H1,D1,N,Result)).
  12. Ha a Prolog nem lenne képes tetszőleges hosszú számokkal dolgozni, akkor igen hosszú programot írhatnánk. Most viszont nincs más dolgunk, mint pár készen kapott predikátumot egymás után kapcsolni. A sumlist/2 elkészíti az L listában szereplő számok összegét. A number_chars/2 felbontja a számot a karaktereire, illetve össze is kapcsolja őket számmá. A lenght/2 segítségével egy megfelelő hosszúságú értékkel nem rendelkező listát generálunk, amit az append/3 fog feltölteni a számból kapott lista első tagjaival. És már csak ezt a listát kell visszaalakítani számmá.

    euler13(L,H,Hossz):-
      sumlist(L,N),
      number_chars(N,L2),
      length(L3,Hossz),
      append(L3,_,L2),
      number_chars(H,L3).

Formális nyelvek és fordítóprogramok algoritmusai

[Megjegyzés]Megjegyzés

A megoldások során Fülöp Zoltán könyvének jelöléseit, algoritmusait használjuk.

  1. Az egyszerűség kedvéért − ahelyett hogy az elemzendő karaktersorozatot sorra átadnánk paraméterként − inkább külön tároljuk, így egyszerűbben hozzáférünk.

    input(['i','*','i','+','$']).

    Az írásmód jelöli, hogy mit tekintünk terminális karakternek és mit nem, használjuk mi is ezt!

    trm(X):-  atom_codes(X,[C|_]), C>=97, C=<122.
    ntrm(X):- atom_codes(X,[C|_]), C>=65, C=<90.

    Lássuk milyen formában adhatjuk meg a nyelvtani szabályainkat! Az alábbi programrészlet egy egyszerű példa, az aritmetika nyelve. A balrekurzivitást már megszüntettük új nemterminálisok bevezetésével.

    grm('E',1,['T']).
    grm('E',2,['T','E*']).
    
    grm('E*',1,['+','T']).
    grm('E*',2,['+','T','E*']).
    
    grm('T',1,['F']).
    grm('T',2,['F','T*']).
    
    grm('T*',1,['*','F']).
    grm('T*',2,['*','F','T*']).
    
    grm('F',1,['i']).
    grm('F',2,['(','E',')']).

    A három argumentum közül az első a szabály bal oldalán szereplő nemterminális, míg a harmadik a szabály jobb oldala. Második argumentumként pedig az algoritmusban szereplő indexelést adtuk meg.

    Az elemzés során az egyes állapotokat egy négyessel írjuk le. Ennek megfelelően mi is egy lep/4 predikátumot fogunk felhasználni. Mivel minden lépés egyértelműen meghatározott, minden lépés során alkalmazzuk a vágást, mert nincs szükség visszalépésre. Ezek után nincs más hátra, mint megfogalmazni minden egyes lépést. Nem csinálunk semmi mást, csak átfogalmazzuk a szabályokat Prolog nyelvre

    • (1) Kiterjesztés: (q,i,α,Aβ)→(q,i,αA11β)

      lep(q,I,Alfa,[NagyA|Beta]):-
        ntrm(NagyA),
        grm(NagyA,1,Gamma1),
        append(Gamma1,Beta,GammaBeta),
        kiir(1,q,I,[NagyA:1|Alfa],GammaBeta),!,
        lep(q,I,[NagyA:1|Alfa],GammaBeta).
    • (2) Sikeres input illesztés: (q,i,α,aβ)→(q,i+1,αa,β) ha a=ai

      lep(q,I,Alfa,[KisA|Beta]):-
        trm(KisA),
        input(L), nth1(I,L,KisA),
        I1 is I+1,
        kiir(2,q,I1,[KisA|Alfa],Beta),!,
        lep(q,I1,[KisA|Alfa],Beta).
    • (3) Sikeres elemzés: (q,n+1,α,λ)→(t,n+1,α,λ)

      lep(q,InputHossz,_,[]):-
        input(L), length(L,InputHossz).
    • (4) Sikertelen input illesztés: (q,i,α,aβ)→(b,i,α,aβ) ha a≠ai

      Noha itt a negyedik értéknél mindenképp szerepel legalább egy betű, az valójában lehet λ is.Erre az esetre nekünk külön szabályt kell írnunk.

      lep(q,I,Alfa,[KisA|Beta]):-
        trm(KisA),
        input(L), nth1(I,L,KisB), KisA\=KisB,
        kiir(4,b,I,Alfa,[KisA|Beta]),!,
        lep(b,I,Alfa,[KisA|Beta]).
      
      lep(q,I,Alfa,[]):-
        input(L), length(L,N), N>I,
        kiir(4,b,I,Alfa,[]),!,
        lep(b,I,Alfa,[]).
    • (5) Backtrack az inputban: (b,i,αa,β)→(b,i-1,α,aβ)

      lep(b,I,[KisA|Alfa],Beta):-
        trm(KisA),
        I1 is I-1,
        kiir(5,b,I1,Alfa,[KisA|Beta]),!,
        lep(b,I1,Alfa,[KisA|Beta]).
    • (6 i) A-nak van j+1-dik alternatívája is: (b,i,αAjjβ)→(b,i,αAj+1j+1aβ)

      lep(b,I,[NagyA:J|Alfa],GBeta):-
        ntrm(NagyA),
        grm(NagyA,J,GammaJ),
        append(GammaJ,Beta,GBeta),
        J1 is J+1,
        grm(NagyA,J1,GammaJ1),
        append(GammaJ1,Beta,GammaBeta),
        kiir(6.1,q,I,[NagyA:J1|Alfa],GammaBeta),!,
        lep(q,I,[NagyA:J1|Alfa],GammaBeta).
    • (6 ii) i=1, A=S, és S-nek nincs több alternatívája, ekkor megállunk.

      lep(b,1,['E':J|_],[_]):-
        J1 is J+1,
        \+ grm('E',J1,_),!.
    • (6 iii) előző esetek nem teljesülnek: (b,i,αAjjβ)→(b,i,αAβ)

      lep(b,I,[NagyA:J|Alfa],GBeta):-
        ntrm(NagyA),
        grm(NagyA,J,GammaJ), append(GammaJ,Beta,GBeta),
        J1 is J+1,
        \+ grm(NagyA,J1,_),
        kiir(6.3,b,I,Alfa,[NagyA|Beta]),!,
        lep(b,I,Alfa,[NagyA|Beta]).

    Adósak maradtunk még az egyes állapotok kiírásával.

    kiir(N,S, I, Alfa, Beta):-
      reverse(Alfa,Afla),
      write(N), write(':'), 
      write('('), write(S), write(','), write(I), write(','),
      ki1(Afla), write(','),
      ki1(Beta), write(')'), nl.

    Annyi dolgunk van még, hogy a listákat kiírjuk:

    ki1([X|F]):- write(X), write(' '), ki1(F).
    ki1([]).

    Hogyan kell elindítani?

    kezd:-
      kiir(0,'q',1,[],['E']),
      lep('q',1,[],['E']).
  2. Az előző megoldásból felhasználhatjuk a kirás kiir/5 és ki1/1, az inputot leíró input/1, valamint a terminális és nemterminális szimbólumokat leíró trm/1 predikátumokat.

    A nyelvtani szabályoknál nem kell elkerülni a balrekurziót, így a szabályaink egyszerűbbek lehetnek. Viszont az algoritmusnak megfelelően sorba kell állítani a szabályokat. Ezt meg is erősítjük a szabályok sorszámának a predikátumban szerepeltetésével.

    grm(1,'E',['E','+','T']).
    grm(2,'E',['T']).
    grm(3,'T',['T','*','F']).
    grm(4,'T',['F']).
    grm(5,'F',['i']).
    grm(6,'F',['(','E',')']).

    Az algoritmusnak megfelelően az indítás egy leheletnyit megváltozik.

    kezd:-
      kiir(0,'q',1,[],[]),
      lep('q',1,[],[]).

    Ezek után természetesen csak az algoritmus szabályainak megadásával maradtunk adósok.

    • (1) Redukálási kísérlet: (q,i,αγ,β) → (q,i,αA,jβ), ha van j sorszámú A→γ alakú helyettesítési szabály.

      lep(q,I,AmmaGAlfa,Beta):-
        grm(J,_,_),
        grm(J,A,Gamma),
        reverse(Gamma,AmmaG),
        append(AmmaG,Alfa,AmmaGAlfa),
        kiir(1,q,I,[A|Alfa],[J|Beta]),!,
        lep(q,I,[A|Alfa],[J|Beta]).
    • (2) Shiftelés: (q,i,α,β) → (q,i+1,αai,sβ), ha i≠n+1

      lep(q,I,Alfa,Beta):-
        input(L), length(L,InputHossz),
        nth1(I,L,KisA), I<InputHossz,
        I1 is I+1,
        kiir(2,q,I1,[KisA|Alfa],[s|Beta]),!,
        lep(q,I1,[KisA|Alfa],[s|Beta]).
    • (3) Elfogadás: (q,n+1,S,β) → (t,n+1,S,β)

      lep(q,InputHossz,['E'],_):-
        input(L), length(L,InputHossz).
    • (4) Átmenet backtrack állapotba: (q,n+1,α,β) → (b,n+1,α,β), ha α≠S

      lep(q,InputHossz,Alfa,Beta):-
        input(L), length(L,InputHossz),
        Alfa \= 'E',
        kiir(4,b,InputHossz,Alfa,Beta),!,
        lep(b,InputHossz,Alfa,Beta).
    • (5 i) (b,i,αA,jβ) → (q,i,α'B,kβ), ha a j-dik szabály A→γ, az azt követő első a k-dik szabály B→γ' melyre αγ=α'γ'.

      lep(b,I,[A|Alfa],[J|Beta]):-
        grm(J,A,Gamma), grm(K,B,Delta), K>J,
        reverse(Gamma,AmmaG),
        reverse(Delta,AlteD),
        append(AmmaG,Alfa,AmmaGAlfa),
        append(AlteD,Alfavesszo,AmmaGAlfa),
        kiir(5.1,q,I,[B|Alfavesszo],[K|Beta]),!,
        lep(q,I,[B|Alfavesszo],[K|Beta]).
    • (5 ii) (b,i,αA,jβ) → (q,i+1,αγai,sβ), ha A→γ a j-dik szabály és i≠n+1, és αγ nem redukálható másképp.

      lep(b,InputHossz,[A|Alfa],[J|Beta]):-
        input(L), length(L,InputHossz),
        grm(J,A,Gamma),
        reverse(Gamma,AmmaG),
        append(AmmaG,Alfa,AmmaGAlfa),
        \+ (grm(K,_,Delta),
            K>J,
            reverse(Delta,AlteD),
            append(AlteD,_,AmmaGAlfa)
        ),
        kiir(5.2,b,InputHossz,AmmaGAlfa,Beta),!,
        lep(b,InputHossz,AmmaGAlfa,Beta).
    • (5 iii) (b,n+1,αA,jβ) → (b,i+1,αγ,β), ha A→γ a j-dik szabály, és αγ nem redukálható másképp.

      lep(b,I,[A|Alfa],[J|Beta]):-
        input(L), length(L,InputHossz),
        I<InputHossz,
        grm(J,A,Gamma),
        reverse(Gamma,AmmaG),
        append(AmmaG,Alfa,AmmaGAlfa),
        \+ (grm(K,_,Delta),
            K>J,
            reverse(Delta,AlteD),
            append(AlteD,_,AmmaGAlfa)
        ),
        I1 is I+1, nth1(I,L,KisA),
        kiir(5.3,q,I1,[KisA|AmmaGAlfa],[s|Beta]),!,
        lep(q,I1,[KisA|AmmaGAlfa],[s|Beta]).
    • (5 iv) (b,i,αa,sβ) → (b,i-1,α,β)

      lep(b,I,[_|Alfa],[s|Beta]):-
        I1 is I-1,
        kiir(5.4,b,I1,Alfa,Beta),!,
        lep(b,I1,Alfa,Beta).
  3. Ennek és a következő feladatnak a megoldásában J. Cohen és T. J. Hickey megoldására támaszkodunk.

    Az előző feladat megoldásának formátumában adjuk meg a terminális, nemterminális szimbólumokat, valamint a nyelvtan helyettesítési szabályait (trm/1, ntrm/1, grm/3).

    First halmazt tetszőleges karaktersorozat esetén definiálhatjuk, így az input egy tetszőleges lista lesz. Ha ez a lista terminális karakterrel kezdődik, akkor kész is vagyunk.

    first([X|_],_,X):- trm(X).

    Ha a lista első karaktere nemterminális, akkor keressünk egy helyettesítési szabályt, melyre lecserélhetjük ezt a karaktert. Ennek a szabálynak a jobb oldala alkot egy új listát, melynek a First halmazát keressük. Természetesen a ciklust el kell kerülni, így nem használhatunk egy szabályt újra. A felhasznált szabályok sorszámát a második argumentumban gyűjtjük.

    first([X|_],Ns,First):-
      grm(N,X,L),
      \+ member(N,Ns),
      first(L,[N|Ns],First).

    Az is előfordulhat, hogy egy vagy több nemterminálisból is levezethető a λ. Ehhez kettéválasztjuk a listát, és teszteljük, hogy az első feléből levezethető-e a λ, amire az epsz_l/2 predikátumot használjuk. Ezek után a másik felének a First halmazát kell meghatározni.

    first(Xs,Ns,First):-
      append(X1,X2,Xs),
      X1 \= [],
      epsz_l(X1,[]),
      first(X2,Ns,First).

    Miután felsoroltuk az összes lehetőséget, a λ-ra levezethetőség programjára van szükség. Ha sikerült elfogyasztani a listát, kész is vagyunk.

    epsz_l([],_).

    Ha nemterminális karakterrel kezdődik a lista, akkor ennek a karakternek is λ-ra levezethetőnek kell lennie, akárcsak az utána következő résznek.

    epsz_l([X|Xs],Ns):-
      ntrm(X),
      epsz_n(X,[X|Ns]),
      epsz_l(Xs,Ns).

    A nemterminális karaktert akkor tudjuk λ-ra levezetni, ha van egy olyan szabály, melynek a jobb oldala λ-ra levezethető. Annak érdekében, hogy ne kerüljünk ciklusba, minden nemterminálist csak egyszer használhatunk fel. Ezeket gyűjtjük az Ns listában.

    epsz_n(X,Ns):-
      grm(_,X,L),
      \+ (member(Y,L),member(Y,Ns)),
      epsz_l(L,Ns).

    Az eredeti feladatunk az volt, hogy a First halmazokat megadjuk. Ehhez az adott listából megkapható First elemeket kell összegyűjteni.

    all_first(N,L):-  setof(X,first(N,[],X),L).
  4. Ebben az esetben is az előző kettő megoldásra támaszkodunk. A Follow halmaz meghatározásakor két eset lehetséges. Első esetben a keresett nemterminális egy szabály belsejében szerepel, és minket az ezt követő karaktersorozat kezdő karaktere érdekel.

    follow(X,Ns,Follow):-
      grm(N,_,Ys),
      \+ memberchk(N,Ns),
      append(_,[X|B],Ys),
      first(B,[],Follow).

    Másik esetben a szabály végén szerepel a keresett nemterminális, vagy mindaz ami mögötte szerepel, az λ-ra levezethető. Ebben az esetben a szabály bal oldalán szereplő nemterminálishoz tartozó Follow halmazt keressük.

    follow(X,Ns,Follow):-
      grm(N,Y,Ys),
      \+ memberchk(N,Ns),
      append(_,[X|B],Ys),
      epsz_l(B,[]),
      follow(Y,[N|Ns],Follow).

    A Follow halmazt az elemeiből rakhatjuk össze.

    all_follow(N,L):- setof(X,follow(N,[],X),L).
  5. A balrekurzivitás megszüntetése esetén először a közvetlen balrekurzivitástól kell megszabadulni a kettes számú transzformációval. Ezután a balrekurzivitástól szabadulhatunk meg az egyes számú transzformációval. Ha nincs se ilyen se olyan nemterminális szimbólum, akkor kész is vagyunk.

    balrekurziv(Szabalyok,Ujszabalyok):-
      ( kbr(X,Szabalyok) ->
        transz2(X,Szabalyok,Szabaly1),
        duplatorol(Szabaly1,[],Szabaly2),
        balrekurziv(Szabaly2,UjSzabalyok)
        ;
        ( br(X,Szabalyok) ->
          transz1(X,Szabalyok,Szabaly3),
          duplatorol(Szabaly3,[],Szabaly4),
          balrekurziv(Szabaly4,UjSzabalyok)
          ;
          Ujszabalyok=Szabalyok
        )
      ).

    Míg a terminális és nemterminális szimbólumok felismerésére használhatjuk a korábbi definíciókat, a szabályokat másképp kell tárolnunk, ugyanis ezek az algoritmus végrehajtása során folyamatosan változnak. A következő módszert követjük: egy szabálynak egy lista felel meg, melynek a feje a szabály bal oldala lesz, míg a farok a lista farka. Ezeknek a listáknak a listája fogja tárolni az összes szabályt.

    Ezek után nem lesz nehéz kideríteni, hogy valamely szimbólum közvetlen balrekurzív-e vagy sem, csak arra kell figyelnünk, hogy van-e olyan listaelem, melynek az első két eleme megegyezik-e.

    kbr(X,Szabalyok):-
      member([X,X|_],Szabalyok).

    Közvetlen balrekurzív szimbólum esetén összegyűjtjük azokat a szabályokat, melynek jobb oldala is ezzel a nemterminálissal kezdődik (Alfa), és azokat is, melyek nem (Beta). A kimaradott szabályok egy további listát alkotnak (Stb). Ezekből a szabályokból újabbakat kell generálni (Beta1, Beta2, Alfa1, Alfa2), és a kimaradt szabályok listájához fűzni.

    transz2(X,Szabalyok,UjSzabalyok):-
     szetoszt(X,Szabalyok,[],[],[],Alfa,Beta,Stb),!,
     atom_concat(X,'''',X1),
     eleje(Beta,X,[],Beta1),
     elejeVege(Beta,X,[X1],[],Beta2),
     elejeVege(Alfa,X1,[X1],[],Alfa1),
     eleje(Alfa,X1,[],Alfa2),
     append(Beta1,Beta2,Sz1),
     append(Sz1,Alfa1,Sz2),
     append(Sz2,Alfa2,Sz3),
     append(Sz3,Stb,UjSzabalyok).

    Ha a szabályok elfogytak, a szétválogatással végeztünk.

    szetoszt(_,[],L1,L2,L3,E1,E2,E3):-
      reverse(L1,E1),
      reverse(L2,E2),
      reverse(L3,E3).

    Ellenkező esetben az a kérdés, hogy a szabály bal oldalán az aktuális nemterminális áll-e, illetve ez a szimbólum áll a jobb oldal elején, vagy sem. (A későbbi predikátum egyszerűsítése miatt helyenként lenyeljük ezt a szimbólumot.)

    szetoszt(X,[R|Rs],L1,L2,L3,E1,E2,E3):-
        R = [X|_] ->
      ( R = [X,Y|Xs],
        (
          X=Y ->
        szetoszt(X,Rs,[Xs|L1],L2,L3,E1,E2,E3);
        szetoszt(X,Rs,L1,[[Y|Xs]|L2],L3,E1,E2,E3)
        )
      ) ;
      szetoszt(X,Rs,L1,L2,[R|L3],E1,E2,E3).

    Szükségünk lesz arra, hogy a szabály bal oldalán szereplő nemterminálist lecseréljük egy másikra. Viszont mivel lenyeltük ezt a szimbólumot, elég az új szimbólumot a lista elejére szúrni, és ezt kell tennünk a lista minden elemére.

    eleje([],_,Acc,XL):- reverse(Acc,XL).
    eleje([L|Ls],X,Acc,XL):-
      eleje(Ls,X,[[X|L]|Acc],XL).

    Bizonyos esetben nem csak a bal oldalt kell lecserélni, hanem a szabály jobb oldalát is folytatni kell. Erre szolgál a következő predikátum, amely igen hasonlít az előzőhöz, csupán egy append-del bővebb.

    elejeVege([],_,_,Acc,XLY):- reverse(Acc,XLY).
    elejeVege([L|Ls],X,Y,Acc,XL):-
      append(L,Y,LY),
      elejeVege(Ls,X,Y,[[X|LY]|Acc],XL).

    A feladat megoldása során fontos eldönteni, hogy van-e olyan predikátum, mely balrekurzív. Erre a könyv egy szép matematikai megközelítést használ, viszont mi egy más utat követünk. Számunkra az a kérdés, hogy a levezetés során előfordul-e, hogy a levezetett szimbólumsorozat ugyanazzal a szimbólummal kezdődik, melyből kiindultunk, vagy sem?

    br(X,Szabalyok):-
      hova(X,[],Szabalyok,X).

    Ehhez keresünk egy olyan szabályt, melynek a bal oldalán az aktuális nemterminális (X) szerepel, és Y szimbólummal folytatódik. Ha ez a kiindulási szimbólum (Z), akkor kész is vagyunk, mert a szimbólum balrekurzív. Ha nem, és az Y nemterminális, akkor elkezdhetünk ezzel kezdődő újabb szabályokat keresni, feltéve ha ez még nem került elő, mert akkor ciklusba kerülhetünk. A korábban előforduló nemterminálisokat a Volt listában tároljuk.

    hova(X,Volt,Szabalyok,Z):-
      member([X,Y|_],Szabalyok),
      ( Y=Z -> true;
        ntrm(Y),
        \+ memberchk(Y,Volt),
        hova(Y,[Y|Volt],Szabalyok,Z)
      ).

    Ha kiderült, hogy az X nemterminális szimbólum balrekurzív, akkor ki kell válogatni a hozzá tartozó szabályokat, majd azokban a szabályokban, melynek a jobboldala ezzel a szimbólummal kezdődik, le kell cserélni az előbb kiválogatott szabályok jobb oldalaival.

    transz1(X,Szabalyok,UjSzabalyok):-
      xSzabalyok(Szabalyok,X,[],Xs),
      cserel(Szabalyok,X,Xs,[],UjSzabalyok).

    A kiválogatáshoz a lista minden elemét ellenőrizni kell, hogy a megfelelő alakú-e vagy sem. Ha igen, akkor a segédváltozóban ezt a szabályt is tároljuk.

    xSzabalyok([],_,Acc,Acc).
    xSzabalyok([R|Rs],X,Acc,E):-
      R = [X|Xs] ->
      xSzabalyok(Rs,X,[Xs|Acc],E)
      ;
      xSzabalyok(Rs,X,Acc,E).

    Csere esetén is sorra kell menni az összes szabályon, és a jobb oldaluk kezdetét figyelni. Ha megfelelő szabályra akadunk, akkor igen sok szabállyal kell lecserélni. Ha alaposabban megfigyeljük a korábbi kódot, akkor kiderül, hogy akad egy predikátum, mellyel könnyedén megoldható a feladat, a balrekurzív szimbólummal kezdődő szabályok elé kell beírni baloldalra az aktuális baloldalt, és mögé a szabály hátsó részét.

    cserel([],_,_,Acc,E):-
      reverse(Acc,E).
    cserel([R|Rs],X,Xs,Acc,E):-
      R = [Y,X|Ys] ->
      elejeVege(Xs,Y,Ys,[],YXsYs),
      append(YXsYs,Acc,Acc1),
      cserel(Rs,X,Xs,Acc1,E)
      ;
      cserel(Rs,X,Xs,[R|Acc],E).

    Nem tartozik mereven a feladathoz, de nem árt kiírni a szabályokat valami olvasmányos formában. Ezt több predikátummal oldottuk meg. A kiir/1 megkapja a teljes szabálylistát, ebből az első szabály kezdő szimbólumához tartozó összes szabályt összeválogatja (szur/6), majd a writeline/1 predikátummal kiírja egy sorba. Ehhez felhasználja a writelist/1 predikátumot, amely egy lista kiírását vállalja fel.

    kiir([]):- nl.
    kiir([[X|Xs]|Rs]):-
      write(X), write(' -> '),
      szur(Rs,X,[Xs],[],E1,E2),
      writeline(E1),
      kiir(E2).
    szur([],_,Acc1,Acc2,E1,E2):-
      reverse(Acc1,E1),
      reverse(Acc2,E2).
    szur([R|Rs],X,Acc1,Acc2,E1,E2):-
      R=[X|Xs] ->
      szur(Rs,X,[Xs|Acc1],Acc2,E1,E2);
      szur(Rs,X,Acc1,[R|Acc2],E1,E2).
    writeline([[]]):-write(' lambda '),nl,!.
    writeline([Xs]):-writelist(Xs),nl,!.
    writeline([Xs|Xss]):-
      writelist(Xs), write('|'), writeline(Xss).
    writelist([]).
    writelist([X|Xs]):-
      write(X),
      writelist(Xs).

    Még egy apróság maradt hátra. Időnként előfordul, hogy egy szabály duplán is előfordul. A kiíráskor ez lehet zavaró, ezért a duplikátumokat kitöröljük a szabályok listájából:

    duplatorol([],Acc,E):-
      reverse(Acc,E),!.
    duplatorol([X|Xs],Acc,E):-
      member(X,Xs) ->
      duplatorol(Xs,Acc,E);
      duplatorol(Xs,[X|Acc],E).
  6. A nyelvtan λ-mentes alakjának elkészítésekor is megváltozhatnak a szabályok, éppúgy mint az előző feladatban, így az ott használt formátumot használjuk a szabályokra most is. Mi a teendő? Először is kiválogatjuk azokat a nemterminálisokat, melyekből levezethető a λ. Majd el kell készítenünk az eredeti szabályainknak azokat az alakjait, melyekből ezeket a nemterminálisokat el-elhagyogatjuk.

    lamda_mentes(Szabalyok,UjSzabalyok):-
      mind_ntrm(Szabalyok,[],Ns),
      nullazhato(Ns,[],Es,Szabalyok),
      torol(Es,Szabalyok,Uj),
      ( memberchk('S',Es)->
        UjSzabalyok=[['S''','S'],['S''']|Uj],
        kiir(UjSzabalyok)
        ;
        UjSzabalyok=Uj
      ).

    Elsőként összegyűjtjük a szabályok bal oldalán szereplő nemterminálisokat.

    mind_ntrm([],Ns,Ns):-!.
    mind_ntrm([[X|_]|Rs],Ns,E):-
      memberchk(X,Ns) ->
      mind_ntrm(Rs,Ns,E)
      ;
      mind_ntrm(Rs,[X|Ns],E).

    Majd a lista minden egyes elemére teszteljük, hogy levezethető-e belőle a λ.

    nullazhato([],Ns,Ns,_).
    nullazhato([X|Xs],Ns,Es,Szabalyok):-
      epsz_n(X,[],Szabalyok) ->
      nullazhato(Xs,[X|Ns],Es,Szabalyok)
      ;
      nullazhato(Xs,Ns,Es,Szabalyok).

    A levezethetőségre a korábban már szereplő predikátum egy variánsát használjuk. A korábbiakhoz képest, itt argumentumként szerepel a szabályok listája is.

    epsz_l([],_,_).
    epsz_l([X|Xs],Ns,Rs):-
      ntrm(X),
      epsz_n(X,[X|Ns],Rs),
      epsz_l(Xs,Ns,Rs).
    epsz_n(X,Ns,Rs):-
      member([X|L],Rs),
      \+ (member(Y,L),member(Y,Ns)),
      epsz_l(L,Ns,Rs).

    Ezek után sorra kell menni az így kiválogatott nemterminálisokon, majd ha az aktuális szimbólum valahol előfordul az aktuális szabályban, akkor annak elkészítjük egy másolatát, melyből ez a szimbólum kimarad. Ezek után az eljárás során duplán előforduló szabályokból csak az egyiket tartjuk meg.

    torol([],Rs,Rs).
    torol([N|Ns],[[X|Xs]|Rs],E):-
      %write(N), write(' torlese'),nl,
      duplaz([Xs|Rs],N,[X],[],R1),!,
      duplatorol(R1,[],R2),
      reverse(R2,R3),
      %kiir(R3),
      torol(Ns,R3,E).

    Ha végigmentünk az összes szabályon, és az utolsó szabály jobb oldala nem csak λ, akkor a szabályt hozzávesszük a már feldolgozottakhoz, és kész vagyunk; ellenkező esetben a szabályt kihagyjuk.

    duplaz([[]],_,Ls,Rs,[RL|Rs]):-
      Ls = [_,_|_],
      reverse(Ls,RL),!.
    duplaz([[]],_,[_],Rs,Rs).

    Ha nem az utolsó szabályról van szó, akkor csak hozzávesszük a korábban feldolgozattokhoz, feltéve, ha elég hosszú.

    duplaz([[],[X|Xs]|Rs],N,Acc1,Acc2,E):-
      Acc1 = [_,_|_] ->
      reverse(Acc1,R),
      duplaz([Xs|Rs],N,[X],[R|Acc2],E)
      ;
      duplaz([Xs|Rs],N,[X],Acc2,E).

    Általános esetben a szabály jobb oldalának egyik karakterénél tartunk. Ha ez az aktuális törlendő karakter, akkor el kell készíteni a szabály mását (feltéve, ha elég hosszú lesz az így kapott szabály). A karaktert áttesszük feldolgozott karakterek közé, és folytatjuk az eljárást.

    duplaz([[X|Xs]|Rs],N,Acc1,Acc2,E):-
      N = X ->
      reverse(Acc1,Ra),
      append(Ra,Xs,R),
      ( R  = [_,_|_] ->
        duplaz([Xs|Rs],N,[X|Acc1],[R|Acc2],E)
      ;
      duplaz([Xs|Rs],N,[X|Acc1],Acc2,E)
      )
      ;
      duplaz([Xs|Rs],N,[X|Acc1],Acc2,E).
  7. A grammatikában felesleges szimbólumok elhagyása a következőképpen történik. Először meghatározzuk, hogy mely szimbólumokból elindulva jutunk el a terminálisokig, majd csak az ezeket a szimbólumokat tartalmazó szabályokat hagyjuk meg. Ezután megkeressük, hogy mely szimbólumok érhetőek el a mondatszimbólumból. Végül csak az elérhető szimbólumokat tartalmazó szabályok maradnak meg a nyelvtanban.

    A programunk is ezt az elvet követi, annyi különbséggel, hogy a predikátumunk elején kinyerjük a szabályainkból a terminálisok és nemterminálisok listáját.

    felesleges(Rs,R3):-
      besorol(Rs,N,T),
      terminal(Rs,Rs,N,T,T1),
      szur(Rs,T1,[],R2), write(R2),
      elerheto(R2,T1,L),
      szur(R2,L,[],R3).

    Annak meghatározása, hogy valami terminális szimbólumokra levezethető a következőképpen megy, nyilvántartunk egy T halmazt az ilyen szimbólumoknak, és egy N halmazt az nyelvtanban felhasznált egyéb szimbólumoknak. Majd ezek után sorra megyünk a szabályokon. Ha valamelyikről az derül ki, hogy a szabály bal oldala az N halmazhoz tartozik, míg a jobb oldal már a T halmazhoz, akkor a bal oldalon szereplő szimbólumot töröljük az N halmazból, és beszúrjuk a T-be. Mivel ez a két halmaz megváltozott, újrakezdjük a helyettesítési szabályaink feldolgozását. Ha minden szabállyal végeztünk, akkor kész vagyunk.

    terminal([],_,_,T,T).
    terminal([[Rl|Rr]|Rs],L1,N,T,T1):-
      memberchk(Rl,T)->
        terminal(Rs,L1,N,T,T1)
        ;
        subset(Rr,T) ->
          subtract(N,[Rl],N1),
          terminal(L1,L1,N1,[Rl|T],T1)
          ;
          terminal(Rs,L1,N,T,T1).

    Fontos tudnunk, hogy a mondatszimbólum, amit ebben a programban az S jelöl termináló szimbólum-e vagy sem. Utóbbi esetben a nyelv üres, felesleges tovább dolgozni. Különben a megmaradt szimbólumok alkotják az új T halmazt, és egyelőre a S az elérhető szimbólumok N halmazát.

    elerheto(Rs,T,L):-
      memberchk('S',T) ->
        subtract(T,['S'],T1),
        elerheto(Rs,Rs,['S'],T1,L)
        ;
        write(' A nyelv ures!'),
        nl, fail.

    Itt ugyancsak sorra kell menni minden egyes szabályon, és ha a szabály bal oldala az N halmaz eleme, de van a jobb oldalán olyan szimbólum, amely a T halmaz eleme, akkor ezt onnan át kell tenni az N-be. Ilyen változtatás esetén újra és újra végignézzük a szabályainkat.

    elerheto([],_,E,_,E).
    elerheto([[Rl|Rr]|Rs],L,N,T,E):-
      memberchk(Rl,N) ->
        ( subset(Rr,N) ->
          elerheto(Rs,L,N,T,E)
          ;
          bovit(Rr,N,T,N1,T1),
          elerheto(L,L,N1,T1,E)
        )
        ;
        elerheto(Rs,L,N,T,E).

    Ezt az áthelyezést egy külön predikátummal oldottuk meg, a szabály jobb oldalát alkotó lista összes eleménél teszteljük, hogy át kell-e helyezni, mert ebben az esetben töröljük a régi helyéről, és a segédváltozóba eltároljuk.

    bovit([],N,T,N,T).
    bovit([X|Xs],N,T,N1,T1):-
      memberchk(X,T) ->
        subtract(T,[X],T2),
        bovit(Xs,[X|N],T2,N1,T1)
        ;
        bovit(Xs,N,T,N1,T1).

    Azokat a szabályokat, melyben valamely meg nem engedett szimbólum előfordul, el kell hagynunk. Tehát a szabály karakterei halmaza a megengedett szimbólumok halmazának részhalmaza kell, hogy legyen.

    szur([],_,Rs,Rs).
    szur([R|Rs],Hz,Acc,E):-
      subset(R,Hz) ->
        szur(Rs,Hz,[R|Acc],E)
        ;
        szur(Rs,Hz,Acc,E).

    A szimbólumok szétválogatásánál első lépésben kihasználjuk a rendszerrel kapott predikátumokat, az append/2 a szabályok listáinak listájából egy szimbólumlistát készít, melyből a sort segítségével az ismétlődő szimbólumokat eltüntetjük. Ezután minden megmaradt szimbólumra teszteljük, hogy melyik csoportba esik, és ennek megfelelően két segédváltozóba gyűjtjük.

    besorol(L,N,T):-
      append(L,L1),
      sort(L1,L2),
      besorol(L2,[],[],N,T).
    besorol([],N,T,N,T).
    besorol([X|Xs],Acc1,Acc2,N,T):-
      ntrm(X) ->
      besorol(Xs,[X|Acc1],Acc2,N,T)
      ;
      besorol(Xs,Acc1,[X|Acc2],N,T).
  8. Thompson algoritmusát követve a reguláris kifejezés szerkezetét kell követni. Szükségünk van az unió (+), konkatenáció (*) és a Kleene-csillag (#) jelére. Azért választottuk ezeket a szimbólumokat, mert így csak az alábbi definícióra van szükségünk.

    :-op(300,yf,'#').

    Unió esetén külön-külön el kell készíteni a részkifejezésekhez tartozó automatákat, és ezeket összekötni egy újabb kezdő és vég állapottal. Ez utóbbi λ-átmeneteket 0-val jelöljük a gráfban.

    re2nda(A+B,K,V,L):-
      Ka is K+1,
      re2nda(A,Ka,Va,La),
      Kb is Va+1,
      re2nda(B,Kb,Vb,Lb),
      V is Vb+1,
      append(La,Lb,Lab),
      L = [K/Ka/0,K/Kb/0,Va/V/0,Vb/V/0|Lab],!.

    Iteráció esetén csak egy részkifejezés van, ennek az automatáját kell λ-átmenetekkel bővíteni.

    re2nda(A#,K,V,L):-
      Ka is K+1,
      re2nda(A,Ka,Va,La),
      V is Va+1,
      L=[K/V/0,K/Ka/0,Va/V/0,Va/Ka/0|La],!.

    Konkatenációnál el kell készíteni ez két részkifejezéshez tartozó automatát, és ezt vagy λ-átmenettel összekötni, vagy ahogy mi tesszük összeolvasztani, hogy csökkentsük az állapotok számát.

    re2nda(A*B,K,V,L):-
      re2nda(A,K,Va,La),
      re2nda(B,Va,V,Lb),
      append(La,Lb,L),!.

    A terminális szimbólumok esetén csak egy átmenetre lesz szükség, valamint két állapotra.

    re2nda(A,K,V,L):-
      atomic(A),
      trm(A),
      V is K+1,
      L=[K/V/A],!.
  9. Az algoritmus alapján meghatározzuk a kiindulópont λ-lezártját, amely a kiinduló halmazunk lesz. Összegyűjtjük az automata információi alapján a terminálisok halmazát, és sorra minden megnézzük, hogy az aktuális halmazból merre indulhatunk tovább.

    determinizalas(NDA,FSA,Vs):-
      l_atmenet([1],Hz,NDA),
      L=[1/Hz],
      term_hz(NDA,[],Ts),
      sorlezar(1,Ts,NDA,L,[],FSA,Vs).

    A λ-lezárt meghatározásánál fontos, hogy csak λ-átmenet segítségével mely állapotokhoz juthatunk el az aktuálisokból. A λ-átmeneteket a nemdeterminisztikus automatában 0 számmal jelöljük. Az utakat pedig a legegyszerűbb programmal generáltuk, figyelve arra, hogy ne jöhessen létre ciklus.

    l_atmenet(L,Xs,NDA):-
      setof(V,(member(X,L),ut(X,V,[X],NDA)),Xs)-> true
      ;
      Xs=[].
    ut(K,V,NDA):-
      ut(K,V,[K],NDA).
    ut(V,V,_,_).
    ut(K,V,L,NDA):-
      member(K/M/0,NDA),
      \+ memberchk(M,L),
      ut(M,V,[M|L],NDA).

    Tetszőleges terminális esetén az a kérdés, hogy az aktuális csúcsokból merre jutunk tovább. Azaz van-e olyan él a nemdeterminisztikus automatában, melynek az innenső vége az aktuális halmazban van, és a címkéje az aktuális terminális?

    x_atmenet([],_,_,Vs,Vs).
    x_atmenet([K/V/X|NDA],X,Ks,Acc,Vs):-
      memberchk(K,Ks) ->
      ( memberchk(V,Acc) ->
        x_atmenet(NDA,X,Ks,Acc,Vs)
        ;
        x_atmenet(NDA,X,Ks,[V|Acc],Vs)
      )
      ;
      x_atmenet(NDA,X,Ks,Acc,Vs).
    x_atmenet([_/_/Y|NDA],X,Ks,Acc,Vs):-
        X\=Y,
        x_atmenet(NDA,X,Ks,Acc,Vs).

    A terminálisok halmazát ugyancsak a nemdeterminisztikus automatából határozzuk meg, megnézzük, hogy milyen cimkéi vannak az éleknek. Mivel egy terminális több élt is címkézhet, végül egy rendezést alkalmazunk, amely a többszörös elemekből csak egyet hagy meg.

    term_hz([],Acc,Ts):- sort(Acc,Ts).
    term_hz([_/_/T|NDA],Acc,Ts):-
      T\=0 -> term_hz(NDA,[T|Acc],Ts);
      term_hz(NDA,Acc,Ts).

    Annak megfelelően készül a táblázatunk, hogy az aktuális L halmazból a T terminálissal hova juthatunk tovább. Ehhez a T-átmenet meghatározása után (x-atmenet) szükség van egy λ-lezárásra is. A kérdés az, hogy az így megkapott Hz állapothalmaz előfordult-e már korábban is, vagy sem? Ha igen, akkor meghatározzuk a halmaz azonosítóját, és ezt az átmenetet eltároljuk az M véges determinisztikus automatánkban. Ellenkező esetben létre kell hozni egy új halmazt, betenni az állapothalmazaink közé (L), és ezután tárolni az átmenetet.

    lezar(_/_,[],_,L,L,M,M).
    lezar(N/L,[T|Ts],NDA,Ls,L1,M,E):-
      x_atmenet(NDA,T,L,[],V), l_atmenet(V,Hz,NDA),!,
      (memberchk(I/Hz,Ls) ->
        lezar(N/L,Ts,NDA,Ls,L1,[N/I/T|M],E)
        ;
        length(Ls,I),
        I1 is I+1,
        lezar(N/L,Ts,NDA,[I1/Hz|Ls],L1,[N/I1/T|M],E)
      ).

    Ezt természetesen nem csak egy-egy terminálissal kell megtennünk, hanem az összessel. Ha véletlenül már olyan nagy N sorszámnál tartunk, hogy nem tartozik hozzá állapothalmaz, akkor alapjában véve kész is lennénk, már csak annak a meghatározására van szükség, hogy a determinisztikus automatánknak mely állapotai végállapotok. Egyébként az adott sorszámhoz tartozó halmazra végre kell hajtani az előző predikátumot, és venni a soron következő halmazt.

    sorlezar(N,Ts,NDA,L,Acc,FSA,Vs):-
      member(N/Hz,L) ->
        lezar(N/Hz,Ts,NDA,L,LOut,Acc,E),!,
        N1 is N+1,
        sorlezar(N1,Ts,NDA,LOut,E,FSA,Vs)
        ;
        utolso(NDA,1,V),
        vegallapot(L,V,[],Vs),!
        % fejlec([' '|Ts]), tabla(1,N,Ts,L,Acc),
        Acc=FSA.

    Mivel cselesen úgy készítettük el a nemdeterminisztikus automatánkat, hogy a legmagasabb sorszámú állapota volt a végállapot, így annak leírásából kell kiválasztani a legmagasabb indexet.

    utolso([],V,V).
    utolso([_/N/_|NDA],Acc,V):-
      N>Acc -> utolso(NDA,N,V)
      ;
      utolso(NDA,Acc,V).

    Ezek után pedig az az állapothalmaz lesz a végállapot, melyben ez az index szerepel.

    vegallapot([],_,Vs,Vs).
    vegallapot([I/Hz|L],M,Acc,Vs):-
      memberchk(M,Hz) ->
      vegallapot(L,M,[I|Acc],Vs)
      ;
      vegallapot(L,M,Acc,Vs).

    Bár nem tartozik a megoldáshoz, de néha nem árt az eredményt is kiírni. A táblázat formázására nem fektettünk különösebb hangsúly, javasoljuk az alábbi program átírását úgy, hogy az HTML vagy TeX formátumban szolgáltassa az eredményt.

    fejlec([]):- nl.
    fejlec([T|Ts]):-
      write(T),write('|'),
      fejlec(Ts).
    tabla(I,N,Ts,Lout,FSA):-
      I<N ->
      member(I/Hz,Lout),
      nevez(Hz,[],Nev),
      writef('%s|',[Nev]),
      sor(I,Ts,Lout,FSA),
      I1 is I+1,
      tabla(I1,N,Ts,Lout,FSA)
      ;
      true.
    sor(_,[],_,_):-nl.
    sor(I,[T|Ts],Lout,FSA):-
      member(I/J/T,FSA),
      member(J/Hz,Lout),
      nevez(Hz,[],Nev),
      writef('%s|',[Nev]),
      sor(I,Ts,Lout,FSA).
    nevez([],L,R):- reverse(L,R).
    nevez([X|Xs],L,R):-
      neve(X,N),
      nevez(Xs,[N|L],R).
    neve(N,Nev):-
      nth1(N,['A','B','C','D','E','F','G','H','I','J','K','L','M',
              'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
              a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z],Nev).
  10. Adott a determinisztikus automatánk a végállapotaival, és minimalizálnunk kellene. Ehhez meg kell határozni a nem végállapotok halmazát is. Az automatából a korábban ismeretetett módszerrel kinyerjük a legnagyobb indexű csúcsát, amivel meghatározzuk a nem végállapotokat. Meghatározzuk a terminálisok halmazát is és kezdődhet a valódi minimalizáció.

    minimalizal(FSA,Vs,Min):-
      utolso(FSA,1,U),
      nemveg(1,U,Vs,[],Ns),
      term_hz(FSA,[],Ts),
      min2(Ts,[Vs,Ns],[],FSA,Min).

    Az automata állapotait folyamatosan növekvő számokkal jelöltük, és az U jelöli ezek közül az utolsót. Az I ciklusszámláló ezért halad egytől U-ig. Ha az adott szám nem szerepel a végállapotok között, akkor feljegyezzük, egyébként figyelmen kívül hagyjuk.

    nemveg(I,U,_,Ns,Ns):-
      I>U,!.
    nemveg(I,U,Vs,Acc,Ns):-
      I1 is I+1,
      ( memberchk(I,Vs) ->
        nemveg(I1,U,Vs,Acc,Ns);
        nemveg(I1,U,Vs,[I|Acc],Ns)
      ).

    A minimalizálás során az egymáshoz hasonló csúcsok halmazának halmazát járjuk végig. Ha az derül ki, hogy az egy halmazban szereplő csúcsok mégsem annyira hasonlítanak (csoportosit/6), akkor ezekből újabb halmazokat készítünk (darabol/4). Az egyelemű halmazokkal felesleges bármit is csinálni, ezt nem lehet tovább bontani.

    min2(_,[],L,_,E):-sort(L,E).
    min2(Ts,[L|Ls],LVolt,FSA,E):-
      length(L,Lh),
      (Lh = 1 ->
        min2(Ts,Ls,[L|LVolt],FSA,E)
        ;
        append([L|Ls],LVolt,Ossz),
        csoportosit(L,Ts,FSA,Ossz,[],Cs),
        Cs=[Hz/I|Cs1],
        darabol(Hz,Cs1,[[I]],Gs),
        length(Gs,Gh),
        ( Gh>1 ->
          append(Gs,Ls,GL),
          append(GL,LVolt,LUj),
          min2(Ts,LUj,[],FSA,E)
          ;
          sort(L,LL),
          min2(Ts,Ls,[LL|LVolt],FSA,E)
        )
      ).

    Csoportosításnál a halmaz minden elemére meg kell határozni, milyen állapotba mehetünk át az egyes nemterminálisokkal. Így ez a predikátum egy ciklust valósít meg a halmaz elemeire.

    csoportosit([],_,_,_,Acc,Cs):- sort(Acc,Cs),!.
    csoportosit([X|Xs],Ts,FSA,Ossz,Acc,Cs):-
      atmenet(Ts,X,FSA,Ossz,[],E),
      csoportosit(Xs,Ts,FSA,Ossz,[E/X|Acc],Cs).

    Az átmeneteket minden terminálisra meg kell adni, ez egy újabb ciklus lesz. Viszont hiába kaptuk meg az egyes új állapotokat, valójában nem erre vagyunk kíváncsiak, hanem hogy melyik halmazban van a keresett szám.

    atmenet([],_,_,_,Acc,Hz):-
      reverse(Acc,Hz),!.
    atmenet([T|Ts],X,FSA,Ossz,Acc,Hz):-
      member(X/S/T,FSA),
      melyik(Ossz,S,1,Db),
      atmenet(Ts,X,FSA,Ossz,[Db|Acc],Hz).

    Erre a kérdésre a melyik/4 válaszol, amely ciklusban végigmegy az összes halmazon, és megkeresi az elemet. Válaszul a halmaz sorszámát adja vissza.

    melyik([Os|Ossz],S,I,Db):-
      memberchk(S,Os) ->
      Db=I
      ;
      I1 is I+1,
      melyik(Ossz,S,I1,Db).

    A csoportosítás visszaadott egy halmaz-index párosokból álló listát, melyet sorba rendeztünk. Ezzel az azonos halmazok (átmenetek) egymás mellé kerültek. Ezért az egymástól különböző halmazoknál kell szétszabdalni a listát, és a halmazokat nyugodtan el is felejthetjük.

    darabol(_,[],Gs,Gs):-!.
    darabol(Hz,[Hz1/I1|L],[G|Gs],E):-
      Hz=Hz1 ->
      darabol(Hz,L,[[I1|G]|Gs],E)
      ;
      darabol(Hz1,L,[[I1],G|Gs],E).
  11. A megoldás során a korábban használt nyelvtani helyettesítések formátumát használjuk. Mivel minden generált szóra kíváncsiak vagyunk, felhasználjuk a Prolog beépített predikátumát.

    general(Hossz,L):-
      setof(X,gen2(['E'],Hossz,X),L).

    Így már csak 1-1 szót kell generálnunk. Ha ez a szó már hosszabb a korlátnál, akkor reménytelen a helyzet. Ellenkező esetben szétvágjuk a szót az első nem terminálisnál. Ha ilyen létezik, akkor ezt lecseréljük valamely helyettesítési szabály jobb oldalára. Összerakjuk a szó részeit, és újraindítjuk a generálást. Ha nem létezik nemterminális a szóban, akkor megvan a keresett szó, tehát kész vagyunk.

    gen2(L,Hossz,Lout):-
      length(L,Lh), Lh=<Hossz,
      bont(L,[],Kezd,Veg),
      ( Veg=[N|V] ->
        grm(_,N,Ns),
        append(Ns,V,L2),
        append(Kezd,L2,L0),
        gen2(L0,Hossz,Lout)
        ;
        Lout=Kezd
      ).
  12. A ciklus olyan levezetésből áll, ahol a kiinduló nemterminálisból több lépés során újra visszajutunk ebbe a nemterminálisba. A ciklus megkereséséhez meg kell találni, hogy melyik szimbólumból kell kiindulnunk, valamint érdemes nyilvántartani, hogy milyen nemterminálisok fordultak elő a levezetés során.

    ciklus:-
      ciklus(X,[]),!.

    Ha egy nemterminális újra előfordul, kész is a ciklus. Ebben az esetben kiírhatjuk a ciklust. Ehhez az összegyűjtött nemterminálisokat ki fogjuk írni, amihez a sorrendjüket vissza kell állítani.

    Ha még nem fordult elő, akkor keresünk egy olyan szabályt, mely a ciklusnak része lehet, és ennek a szabálynak a jobb oldalával haladunk tovább.

    ciklus(X,Xs):-
      memberchk(X,Xs) ->
      reverse([X|Xs],XXs),
      kiir(X,XXs)
      ;
      grm(_,X,[Y]),
      ciklus(Y,[X|Xs]).

    Kiírás során a ciklushoz nem tartozó részt ki kell hagyni, és a maradékok kiírni.

    kiir(X,[Y|Ys]):-
      X\=Y -> kiir(X,Ys)
      ;
      ki2([Y|Ys]).
    ki2([Y]):- write(Y),nl,!.
    ki2([Y|Ys]):- write(Y), write(' -> '), ki2(Ys).
    
  13. Ez a megoldás a Wikipédián található megoldásra támaszkodik. Jelölje q1 a kiinduló állapotot, és q0 a végállapotot. A Turing gép szabályait a következőképpen adhatjuk meg:

    szabaly(q1, 1, q1, 1, r).
    szabaly(q1, 0, q0, 1, s).

    A futáshoz meg kell adnunk, hogy mi szerepel az egyetlen szalagon, s azt kapjuk vissza, hogy mi a szalag tartalma: ?- turing([1,1,1], Ts).

    A szalag fej előtti részét fordítva fogjuk tárolni, hogy könnyen hozzáférjünk. Miután a futás véget ért, a kiíráshoz megfelelő irányba kell fordítani, és összerakni a darabokat.

    turing(Tape0, Tape) :-
        futas(q1, [], Tape0, Ls, Rs),
        reverse(Ls, Ls1),
        append(Ls1, Rs, Tape).

    q0 állapotba kerülve leállunk, ellenkező esetben megnézzük a fej alatt található karaktert (Rs0 első karaktere), és az Q0 állapothoz, ehhez a karakterhez tartozó szabályt. Ez megadja a helyettesítendő karaktert, valamint a lépés irányát. A fejet ennek megfelelően kell elmozdítani, és a következő állapotba lépni. Majd minden kezdődik előröl.

    futas(q0, Ls, Rs, Ls, Rs) :- !.
    futas(Q0, Ls0, Rs0, Ls, Rs) :-
        karakter(Rs0, Sym, RsRest),
        szabaly(Q0, Sym, Q1, NewSym, Irany),
        lepes(Irany, Ls0, [NewSym|RsRest], Ls1,  Rs1),!,
        futas(Q1, Ls1, Rs1, Ls, Rs).

    A mindkét irányban végtelen szalagot csak szimulálni tudjuk. Ha már kiürült a szalag mögött álló rész listája, akkor üres jelet, azaz a 0-t mindig beszúrhatjuk.

    karakter([], 0, []).
    karakter([Sym|Rs], Sym, Rs).

    A helyben maradás egyszerű, jobbra lépésnél a jobb oldali halmazból az aktuális karaktert át kell rajni a bal halmazba.

    lepes(l, Ls0, Rs0, Ls, Rs) :-
      balra(Ls0, Rs0, Ls, Rs).
    lepes(s, Ls, Rs, Ls, Rs).
    lepes(r, Ls0, [Sym|Rs], [Sym|Ls0],  Rs).

    Balra lépésnél előfordulhat, hogy nincs hova lépni, ekkor is beszúrhatunk egy üres karaktert. Más esetben pedig a bal listából kell áthozni az első jelet a jobb lista elejére.

    balra([], Rs0, [], [0|Rs0]).
    balra([L|Ls], Rs, Ls, [L|Rs]).
  14. Az egyszerűség kedvéért a szabályaink egy listában találhatóak, ahol egy elemet a szabály bal és jobb oldala, valamint annak jelzése alkotja, hogy a helyettesítés záróhelyettesítés-e, vagy sem.

    mszabaly([[x,0]/[0,x,x]/nem,[1]/[0,x]/nem,[0]/[]/nem]). 

    Ebből a listából meghatározzuk, hány szabályunk is van, és ezzel indítjuk a programunk.

    markov(Lin,Lout):-
     mszabaly(L),
     length(L,N),
     m2(1,N,Lin,Lout).

    Ha túlléptünk az utolsó szabályon is, akkor az algoritmus véget ért. Ellenkező esetben kikeressük az aktuális szabályt, és teszteljük, hogy ennek baloldala szerepel-e a szóban. Ha igen, akkor lecseréljük ezt a részt a jobb oldallal, és már csak azt kell ellenőrizni, hogy záróhelyettesítésről volt-e szó, vagy sem.

    m2(I,N,Szo,Veg):-
      I =< N ->
      mszabaly(L),
      nth1(I,L,A/B/V),
      I1 is I+1,
      ( resze(A,Szo,E,H)->
        append(E,B,EB),
        append(EB,H,UjSzo),
        ( V=igen ->
          Veg=UjSzo
          ;
          m2(1,N,UjSzo,Veg)
        )
        ;
        m2(I1,N,Szo,Veg)
      )
      ;
      Veg=Szo.

    Nem hatékony, de egyszerűen az alábbi tesztelése a részsztringnek.

    resze(X,AXB,A,B):-
      append(X,B,XB),
      append(A,XB,AXB).

Bibliográfia

[bizam] Bizám, György és Herzeg, János. Játék és logika 85 feladatban. Műszaki Könyvkiadó, Budapest. 1972.

[parsing] Cohen, Jacques és Hickey, Timothy J.. „Parsing and Compiling Using Prolog”. 125-163. ACM Transaction on Programming Languages and Systems. 9. 2. April, 1987.

[fulop] Fülöp, Zoltán. Formális nyelvek és szintaktikus elemzésük. Polygon, Szeged. 1999.

Thompson, Ken. „Regular expression search algorithm”. 419-422. Comm. Assoc. Comp. Mach.. 11. 6. 1968.

[knuth] Donald Ervin Knuth. The art of computer programming. 1. Algorithm P (1.3.2). 1981.

[p99] Hett, Weerner. P-99: Ninety-Nine Prolog Problems. 2009.